fix: stop crawl external refs and the local base retriever from doubling a path segment - #72
Open
vishkaty wants to merge 2 commits into
Conversation
…ts one level deeper than disk Adds two cases matching the shape reported against a real ucp v2026-04-08 source checkout: discovery/profile_schema.json declares an $id that nests discovery under schemas in URL terms, while the two sit as siblings on disk. A relative ref written correctly for disk (../shared/mid.json in the fixture, ../schemas/ucp.json in the real tree) doubles the shared segment when resolved by URL join against the declared $id instead. Both cases fail on current main, each with the failure shape of its own site: - sibling_directory_nested_under_id_resolves_without_doubling exercises bundle_refs, the path used by validate --schema --schema-local-base with no remote base set, and fails with the Resource ... is not present in a registry shape the real repro produces. - sibling_directory_nested_under_id_resolves_without_doubling_with_url_mapping exercises bundle_refs_with_url_mapping (--schema-local-base plus --schema-remote-base, also used internally by compose) and fails with a doubled path file not found error at the explicit mapping step. That entry point had no direct test before this; its only prior coverage came through CLI fixtures whose declared $id and disk layout agree.
…ing a path segment Introduced by Universal-Commerce-Protocol#66 (9f91c3d): crawl_external_refs resolves the relative refs of a fetched document against its declared $id, which is correct in isolation, but upstream jsonschema::dereference resolves those same refs against the URI the document was retrieved under, not its declared $id. When a relative ref is written correctly for the referrer disk location but the referrer $id nests it one level deeper than a sibling directory, the URL join off the retrieval position doubles the shared segment, and only the correct, undoubled key from our own crawl had ever been registered, so upstream dereference fails the moment it independently rederives the doubled key for a further hop. Universal-Commerce-Protocol#66 is otherwise a sound rework that closed Universal-Commerce-Protocol#43, Universal-Commerce-Protocol#45 and Universal-Commerce-Protocol#46; the four Draft 2020 12 conformance cases it added still pass with this fix. A related site: the UcpRetriever explicit --schema-local-base plus --schema-remote-base mapping hits the same doubled join and returns a hard file not found error instead of falling through to the learned anchor relocation that already recovers from this mismatch for the no remote base case. Fix, both changes additive: - crawl_external_refs also crawls the refs of a fetched document under the URI it was retrieved by, in addition to its declared $id, when the two differ, so resources reachable through either base end up registered. Guarded on that difference, so it is a no op when the two agree. - UcpRetriever::retrieve only takes the explicit mapping candidate when the mapped path exists, falling through to the remaining steps otherwise, exactly as an unmapped URI already does. A mapped URI whose local file is missing therefore now reaches the same fallthrough, including the remote fetch step when the remote feature is built in, instead of failing hard at the mapping step. Verified against the exact reported command on a real ucp v2026-04-08 source checkout (exit 2 before, exit 0 after). cargo test --all-targets: 355 passed, 0 failed, 0 ignored. The Universal-Commerce-Protocol#45 and Universal-Commerce-Protocol#46 regression fixtures and the four Draft 2020 12 conformance cases Universal-Commerce-Protocol#66 itself added still pass. The released v2026-08-25 tree is unaffected: lint over its source directory and def selection on common/types/payment_instrument.json behave identically on main and on this branch in both the accept and the reject direction. Reverting src/loader.rs alone with the tests kept turns both new tests red, each with the failure shape of its own site, confirming they test the fix rather than passing on their own. cargo fmt --check and cargo clippy --all-targets -- -D warnings both clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
Introduced by #66, commit 9f91c3d. Bisected the range between 9b5c320 (good) and b52518f (bad) commit by commit; 9f91c3d is the first bad one, and the two docs only commits after it do not change the result. #66 is otherwise a sound rework that closed #43, #45 and #46; this is one layout shape it did not cover.
crawl_external_refs in src/loader.rs resolves the relative refs of a fetched document against its declared $id (fetched_base), which is correct on its own, but upstream jsonschema::dereference resolves the relative refs of a fetched resource against the URI it was retrieved under, not its declared $id. When a relative ref is written correctly for the referrer disk location, but the referrer $id nests it one level deeper than a sibling directory (discovery beside schemas, both under https://ucp.dev/schemas/...), the URL join off the retrieval position doubles the shared segment. Only the correct, undoubled key from our own crawl had ever been registered, so upstream dereference fails with not present in a registry the moment it independently rederives the doubled key for a further hop.
A related site: the UcpRetriever explicit schema local base plus schema remote base mapping (step 1) hits the same doubled join and returns a hard file not found error instead of falling through to the learned anchor relocation in step 3, which already recovers from this mismatch for the no remote base case.
Fix
Both changes are additive.
Tests
Added to tests/conformance_test.rs, matching the bundle and oracle_is_valid idiom already used in the file (accept and reject verdicts checked through the jsonschema crate, never our own emitted schema text):
Each fails before the fix with the failure shape of its own site: the first with the same not present in a registry error the real repro produces, the second with a doubled path file not found error at the explicit mapping step. Both pass after. Reverting src/loader.rs alone with the tests kept turns both red again with those same shapes, confirming they test the fix rather than passing on their own.
Verification
Fixes #71.