Skip to content

feat(X402-45): D.2 metadata propagation diff (5th check)#79

Merged
fardinvahdat merged 1 commit into
mainfrom
feat/X402-45-d2-propagation-diff
May 22, 2026
Merged

feat(X402-45): D.2 metadata propagation diff (5th check)#79
fardinvahdat merged 1 commit into
mainfrom
feat/X402-45-d2-propagation-diff

Conversation

@fardinvahdat
Copy link
Copy Markdown
Owner

Summary

X402-45 (D.2) — adds the propagation check (5th in the results array). Closes the @zev / TheRoosters / GM pain pattern: manifest is correctly shaped, indexer drops fields, listing renders blank. v0.3.0/0.3.1 had no signal for this — the verdict came back looks_correct (since well-known + challenge passed) even though the service was effectively invisible on Bazaar.

What this PR adds

New module src/bazaar/propagation.ts exporting checkPropagation(manifest?, payTo?, opts). The orchestrator runs it as a 5th step after well-known / challenge / self-payment / indexing.

Diff scope (narrow on purpose): name, description. These are the fields the three named pain reports all hit. Extending the field set is a future refinement when more pain shapes surface.

Emits detail.metadata_propagation with one of four states:

State Status When
ok pass All diffed fields match between manifest and CDP discovery
partial info Some drift; detail.diff[] lists per-field mismatches
missing info Indexer surfaces none of the manifest's declared fields — the @zev pattern, matches #2207
unknown pass Defensive default: --endpoint mode (no manifest), no payTo, network error, non-JSON response, or empty resources

The info statuses (partial / missing) roll up via the existing verdict synthesizer to upstream_issue exit code 3 — same path as the existing indexing check.

Facilitator-aware semantics — layered split

Per ADR-004 Pillar 3, non-CDP services should produce metadata_propagation: not_applicable_non_cdp. This PR keeps D.2 narrow and returns unknown for services not in CDP discovery; D.3 (X402-46) layers the explicit not_applicable_non_cdp state on top via its facilitator-detection pass. The two tickets stay independently testable.

Snapshot test workflow — demonstrated end-to-end

X402-44 (JSON API stability, just merged) designed the snapshot test to fail on intentional shape changes. Adding the 5th check fired the snapshot test as designed:

  1. Implemented D.2 + wired in 5th check
  2. Ran tests → snapshot test failed: expected 4 results, got 5 + key-order tests failed
  3. Regenerated tests/fixtures/bazaar/json-api-snapshot.json to include the 5th result
  4. Updated the "4 canonical checks in fixed order" invariant test to "5 canonical checks"
  5. Added a ### JSON API CHANGELOG subsection documenting the additive change

This is the maintainer workflow X402-44 was built for, working as intended.

Acceptance criteria (X402-45)

  • metadata_propagation facet implemented with four states (ok, partial, missing, unknown)
  • detail.diff populated with field-by-field mismatches on partial/missing states
  • @zev's manifest-correct-but-indexer-empty pattern produces metadata_propagation: missing with diff showing the dropped fields
  • JSON API snapshot fixture updated per the X402-44 contract; ### JSON API CHANGELOG entry added
  • Unit tests: 15 (≥6 AC threshold — covers ok / partial / missing / unknown / no-payTo / no-manifest / non-2xx / non-JSON / network-error / whitespace-fields / multi-resource / 4× computePropagationStatus helper)
  • Integration tests cover D.2 propagation pass-through end-to-end
  • CHANGELOG [Unreleased] ### Added entry documenting the new check

Strict audit gate — abbreviated (user-in-loop mode)

  • Stage 1 pre-work: branched off 61de55e (PR feat(X402-44): JSON API stability — snapshot test + contract doc + versioning rule #78 X402-44 merged HEAD); .gitignore WIP not staged
  • Stage 2 implementation: 1 new module + orchestrator hookup + verdict synthesizer extension + 2 test fixtures updated + snapshot regenerated + CHANGELOG entries
  • Stage 3 correctness: ✅ typecheck + lint + 477 tests pass (+15 propagation unit; pipeline + snapshot tests updated for the additive 5th-check shape) + build + check:publish-surface 98 files / 370 KB / 0 devDep imports
  • Stage 4 edge cases: ≥10 — missing manifest (--endpoint mode), missing payTo, network error, 5xx, non-JSON body, empty resources, whitespace-only manifest fields (treated as null), multi-resource (uses first), partial drift, all-fields-dropped (missing path)
  • Stage 5 gap check: all 7 X402-45 AC items satisfied; ADR-004 Pillar 3 split documented in module header + CHANGELOG (D.2 returns unknown for non-CDP; D.3 layers not_applicable_non_cdp on top); cross-references to X402-46 + ADR-004 present
  • Stage 6 ship: this PR

What unblocks on merge

  • X402-46 (D.3 indexer-state probe) — can now add the indexer_state facet alongside D.2's metadata_propagation; D.3's facilitator-detection pass can decorate D.2's unknown result with not_applicable_non_cdp when detection succeeds
  • X402-47 (fixture consumption)@zev's api.lastlookdata.com becomes the canonical fixture for the missing path; TomSmart's cdp-mature drop will cover the ok path across multiple cdp-verified services

🤖 Generated with Claude Code

Closes the @zev / TheRoosters / GM pain pattern: manifest is
correctly shaped, indexer drops fields, listing renders blank.
v0.3.0/0.3.1 had no signal for this — the verdict came back
looks_correct (since well-known + challenge passed), even though
the operator's service was effectively invisible on Bazaar.

New `propagation` check (5th in results):
- Queries CDP discovery for the service's payTo (reuses the same
  endpoint as the existing indexing check)
- Diffs manifest fields (name, description) vs resource fields
  in the discovery response
- Emits detail.metadata_propagation: ok | partial | missing | unknown
- ok → status pass (all diffed fields match)
- partial → status info + detail.diff[] (some drift, surface to operator)
- missing → status info (the @zev pattern: manifest correct, indexer
  shows nothing — matches the canonical #2207 indexer-state cluster)
- unknown → status pass (defensive default for --endpoint mode,
  missing payTo, network errors, non-JSON responses, empty resources)

Facilitator-aware semantics deferred to D.3 (X402-46) per ADR-004
Pillar 3: D.2 returns `unknown` for services not in CDP discovery
rather than attributing non-CDP without explicit detection logic.
D.3 layers the explicit `not_applicable_non_cdp` state on top.

Snapshot test workflow demonstrated:
- Adding the 5th check fired the snapshot test (intentional shape
  change) — exactly what X402-44 designed it to do
- Snapshot fixture regenerated to include the 5th result
- CHANGELOG ### JSON API subsection documents the additive change
- The "5 canonical checks in fixed order" invariant test was
  updated (was "all four canonical")
- Pipeline integration test's mock discovery response was updated
  to include name + description so propagation returns ok by
  default (preserves the existing happy-path assertions)

Tests: 477 pass (was 466); +15 propagation unit + ≥3 integration
adjustments. 98 files / 370 KB / 0 devDep imports (under cap).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
x402trace-dogfood Ready Ready Preview, Comment May 22, 2026 10:25pm

@fardinvahdat fardinvahdat merged commit 7972da3 into main May 22, 2026
4 checks passed
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.

1 participant