Skip to content

Port resolution logic from SecID-Service - #18

Merged
kurtseifried merged 5 commits into
mainfrom
fix/resolver-parity
Oct 31, 2026
Merged

kurtseifried merged 5 commits into
mainfrom
fix/resolver-parity

Conversation

@kurtseifried

Copy link
Copy Markdown
Contributor

Stacked on #17 (the real-registry harness), with base set to test/real-registry-harness. Merge #17 first; this PR then retargets to main. It removes every strict xfail #17 added, so CI stays green at each merge.

Summary

resolver.py is now a port of the live resolver: SecID-Service parser.ts, resolver.ts, kv-resolve.ts and identity.ts. Function names follow the TypeScript so the two can be read side by side. It covers the review findings:

H1 unmatched subpath. Returns related with "Subpath ... did not match any known pattern" and the source description, never the parent as found. This fixes aaa-services#V-999999 and cve#NOTACVE. top10@2025#... now gives not_found "Version 2025 not found", which is what live returns.

H2 open_pattern / known_values. Ported isOpenPattern (sentinel tokens), isNodeOpen and nodeMatches. Open nodes are skipped in unscoped search, and known_values is a closed set when the pattern is open. secid:advisory/HADOOP-1234, secid:reference/torvalds and secid:reference/ismap now give not_found and no longer invent CSA artifact URLs. Namespace-scoped apache.org/jira#HADOOP-1234 still resolves, and namespace identity search (secid:control/ismap returns ismap.go.jp) is ported.

H3 depth.

  • version_required 3-level resolution: top10@2021#A01 resolves, and top10#A01 gives related "requires a version" with versions_available, version_disambiguation and unversioned_behavior.
  • The @version is kept in result secids (ccm@4.0#IAM-12).
  • lookup_table uses own-key semantics at any depth. A missing key returns available_items.
  • range_table works (Debian DSA/DLA URLs now resolve).
  • {id_lower}/{id_upper} are supported, and ?lang works with default-language weight +1 and a not-available message.
  • ?content_type and ?parsability qualifiers work, with source-level and item-level ?qualifiers both parsed.
  • Templates in data.urls[] are filled in on registry-data results.

H4 blocking I/O. Handlers are plain def, so they run in the threadpool. RegistryIndex builds each type's namespace map once, lazily or at startup in bulk mode, instead of re-reading every file per unscoped query.

H5 overlay precedence. One rule everywhere (load_namespaces): a later --registry dir overrides an earlier one by namespace. It is used by bulk_load, the index, load_single and reload. There is a test for every query order in both modes.

Medium

  • A 256+ char namespace segment is not_found: the per-type index does no filesystem joins, and load_single catches OSError.
  • Regexes compile with re.ASCII, so CVE-٢٠٢١-٤٤٢٢٨ no longer matches, and re.search is used to mirror RegExp.test.
  • corrected is returned where the Service returns it (redhat.com/RHSA-2026:0932).
  • Bare secid: lists all 10 types, and secid:<type> / secid:<type>/* return the type listing, in both lazy and bulk mode.
  • /admin/reload now diffs against the commit last loaded, not "the last 24h". It handles D (old blob via git show) and R/C (both sides, -M), re-resolves every changed namespace against all dirs (so overlay precedence holds), and invalidates the index. With no git or no base commit, it falls back to a full reload that evicts deleted namespaces.

Low

  • URL templates must be absolute http(s).
  • Substituted values are percent-encoded. / : @ ! $ & ' ( ) * + , ; = stay as they are, so RHSA-2026:0932 and DOIs are unchanged.
  • An unknown type gives not_found "Invalid type", per spec and live.
  • not_found responses carry submission guidance pointing at the SecID issue tracker.
  • The ReDoS comment no longer overstates the length cap.

Deliberate differences from live (documented in the module docstring)

  • Absolute-http(s) template requirement.
  • Percent-encoding of values.
  • data.urls[] substitution.
  • Issue-tracker guidance instead of the submit_feedback MCP tool.
  • One behaviour now matches live but may surprise: github.com/users#torvalds has a namespace and a subpath but no source name, so live (and now this server) lists the sources and ignores the subpath.

Test results

Real-registry suite (#17), both load modes: 54 passed / 62 xfailed before, 124 passed / 0 xfailed after. The suite gained 3 targeted cases (tie order, bare source name, prefix-optional) and a test that every structured registry example resolves to its URL (200/200). Full suite: 174 passed.

Live parity. A scratch script compared status plus the full ordered (secid, url) result list against secid.cloudsecurityalliance.org on 1,941 queries. These were sampled from 560 random namespaces: namespace listings, sources, example IDs scoped and unscoped, bogus subpaths, and bare identifiers. All of them are identical. The one difference found during that run was the weight-tie ordering: JS localeCompare puts - before #. It is fixed with an ICU-order sort key and covered by a test.

Regex safety

No registry patterns change. Patterns are compiled with re.ASCII and cached, and input stays capped at 256 chars per component.

@kurtseifried
kurtseifried changed the base branch from test/real-registry-harness to main September 25, 2026 03:34
Rewrites resolver.py as a port of SecID-Service parser.ts, resolver.ts, kv-resolve.ts and identity.ts: unmatched subpaths return related, open_pattern and known_values are honoured, version_required sources, lookup tables, range_table, {id_lower}, ?lang and other qualifiers work, and corrected is returned where the Service returns it. Registry regexes compile with re.ASCII. A per-type index is built once instead of re-reading files per query, handlers run in the threadpool, overlay precedence is deterministic, oversized namespace segments are not_found, and /admin/reload diffs against the loaded commit and handles deletes and renames.
Comment thread python/registry_loader.py Fixed
Comment thread python/registry_loader.py Fixed
Comment thread python/registry_loader.py Fixed
Comment thread python/test_smoke.py Fixed
CodeQL flagged load_namespaces/load_type_info as building paths from query data. Callers already pass only validated types, but the functions now substitute the SECID_TYPES constant so no caller-supplied string reaches the filesystem. Also replaces a URL-substring assertion in a test with an exact comparison.
@kurtseifried

Copy link
Copy Markdown
Contributor Author

Rebased onto main after the #17 squash; only this PR's commit remains, plus one follow-up: Pin registry type paths to the fixed type list. CodeQL (which runs now that the base is main) flagged load_namespaces/load_type_info as building filesystem paths from query data. Every caller already passed a type validated against the 10-type list, but those functions now substitute the SECID_TYPES constant, so no caller-supplied string reaches a path. The same commit replaces a URL-substring assertion in a test with an exact comparison. CodeQL is green. Real-registry suite: 124 passed (unchanged); full suite 174 passed.

…5/#207

- Skip synthetic RFC 2606 example.org client mock fixtures in _client_fixture_cases()
  (added in SecID-Client-SDK#27 for client-side percent-encoding and hostile-response
  parsing unit tests).
- Add LIVE_OVERRIDES best_url support for encoding_ampersand (ccm@4.0#A&A-01 now
  resolves to a data-only control description after SecID#207).
- Update cross-source tie-order test to secid:regulation/art-32 and add an explicit
  test for cross-source skipping of version_required sources (secid:control/IAM-12)
  plus a unit test in test_smoke.py for _locale_key '-' before '#' collation.

AI-Agent: Gemini
…rename

- Validate @Version against version_required children and versions_available;
  canonicalize version aliases per ADR-015 (SecID-Service#33 / c8b3a93).
- Partition tight vs open child patterns in _determine_namespaces and include
  source-name and namespace-identity candidates so open child patterns do not
  suppress cross-source lookups like secid:reference/arxiv (SecID-Service#38).
- Add literal-first, single-percent-decode fallback in RegistryIndex.resolve
  and friendly bare-search not_found messages (SecID-Service#33).
- Include child data.note on URL-less cross-source hits.
- In _changed_namespaces, inspect both base and head for modified files (M/T)
  so in-place namespace field changes evict the old namespace key on reload.

AI-Agent: Gemini
…SSUES_URL in version miss

- Match JS decodeURIComponent URIError behavior by skipping the percent-decode
  fallback when any % in the query is not followed by two hex digits (2,060/2,060
  parity with SecID-Service).
- Point _version_not_found guidance to ISSUES_URL without mentioning submit_feedback
  (which SecID-Server-API does not expose).

AI-Agent: Gemini
@kurtseifried

Copy link
Copy Markdown
Contributor Author

Agent: Gemini (SecID governing agent)

Review Summary (Gemini + OpenAI Codex gpt-6-astra)

  1. Primary Review (Gemini):
    • Verified the port of resolution logic from SecID-Service, fixed-list type path pinning (F-05-01), and git diff incremental reload in registry_loader.py.
    • Aligned test_real_registry.py with SecID-Client-SDK#27 and SecID#195/#207 (caaaee1).
  2. Independent Second-Vendor Review (OpenAI Codex gpt-6-astra):
    • Finding 1 (Version validation & aliases): SecID-Service#33 (c8b3a93) added @version validation against version_required children and versions_available plus ADR-015 version alias canonicalization after this branch was cut.
      • Fix (6020c34, 738be72): Ported _check_version, _resolve_version_alias, _version_redirect, _version_not_found (pointing to ISSUES_URL), _version_required_miss, and _version_mismatch to python/resolver.py.
    • Finding 2 (Unscoped cross-source lookup & tight vs open child patterns): SecID-Service#38 (5517fa9) separated tight vs open child patterns in _determine_namespaces and added source-name and namespace-identity candidates so open child patterns do not shadow source/namespace searches like secid:reference/arxiv or secid:reference/oecd.
      • Fix (6020c34): Ported is_tight_child_pattern, partitioned tight vs open child hits, added source-name and namespace-identity candidates, and preserved child data.note on URL-less cross-source hits.
    • Finding 3 (Single-percent-decode fallback): SecID-Service#33 added literal-first resolution with a single-percent-decode fallback when callers double-encode %23/%40.
      • Fix (6020c34, 738be72): Added literal-first resolution with single-percent-decode fallback in RegistryIndex.resolve (skipping malformed % escapes to match JS decodeURIComponent URIError behavior — verified 2,060 / 2,060 query parity with SecID-Service) and friendly bare-search not_found guidance.
    • Finding 4 (In-place namespace rename on reload): _changed_namespaces only read head for modified files (M/T), leaving the old namespace key in the store if a file's namespace field was edited in place without renaming the file.
      • Fix (6020c34): Updated _changed_namespaces in python/registry_loader.py to read both base and head for M/T entries, with regression coverage in test_smoke.py.

All 195 tests in python/ pass across Python 3.10–3.13.

@kurtseifried
kurtseifried merged commit 80ef2d3 into main Oct 31, 2026
7 checks passed
@kurtseifried
kurtseifried deleted the fix/resolver-parity branch October 31, 2026 02:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants