[0.3.4] — 2026-05-30
v0.3.4 cycle complete: four committed items shipped across L + K + G + I. bazaar-check grows from 5 checks to 8: host-pollution (L, #101), payment-payload echo gap rule pair with new upstream_stuck.cause discriminator (K, #102), facilitator-fitness per-rail probe with declared-identity-first attribution (G, #103), and the first top-level new verdict since v0.3.2 — service_unreachable with multi-probe consensus + per-cause windows + the first stateful event discriminant in x402trace (I, #104 + determinism fix #105). ADRs 005 / 006 / 007 / 008 record the architectural decisions. JSON API X402-44 contract preserved across all four additions — every shape change is strictly additive. Cohort: 4 named external contributors credited (@hypeprinter007-stack / Ferj 🙏, @RipperMercs 🙏, @TKCollective 🙏, @AsaiShota 🙏) plus 5 additional named voices behind the design refinements (@Cryptor, @TomSmart_ai, @Cinderwright, @evanatpizzarobot, @poteshniy).
Added (I — X402-52)
reachabilitycheck (X402-52, per ADR-006). 8th and final v0.3.4 diagnose-rule. Probes the service URL at the network layer (DNS / TCP / TLS / HTTP) with bounded timeout + bounded retry. Classifies failures into a 5-state axis:dns_failure | tcp_refused | tls_error | timeout | persistent_5xx. Voices: divigent probe (2026-05-23 DNS-fail real example) + @TomSmart_ai (mapper.db cohort + 2026-05-28 traceroute anti-evidence + per-cause-window endorsement 2026-05-29). @AsaiShota's test-echo-cdp as false-positive sentinel pattern (carried from K).- NEW top-level verdict
service_unreachable— first new top-level discriminator since v0.3.2'supstream_stuck. Exit 3 (same bucket as upstream_issue / upstream_stuck — exit-code contract per ADR-004 Pillar 2 preserved). Pre-empts all other verdict paths via the precedence ruleservice_unreachable > upstream_stuck.cause (K) > upstream_issue > facilitator_fitness facet (G) > host_pollution facet (L) > looks_correct— a DNS-failing service doesn't reach the surfaces those diagnose. Documented insrc/bazaar/diagnose-rules.md. bazaar.probe_attemptJSONL event discriminant (src/decoder/schema.md) — first stateful event added bybazaar-check. Records every reachability probe (success or fail) to enable cross-invocation multi-probe consensus.- First stateful verdict in x402trace —
service_unreachablerequires N consecutive matching probes within the per-cause window. Probe history is read from the JSONL log supplied via--probe-history-log <file>, current probe appended back. Preserves local-first stateless property: no external state directory; probe history lives in the same JSONL log operators already manage. - Per-cause consensus windows table (locked 2026-05-29 with @TomSmart_ai endorsement). DNS 5min / TCP 15min / TLS 30min / timeout 15min / persistent_5xx out-of-band. Operators scale uniformly via
--unreachable-interval-multiplier <n>. Per-cause individual flags deferred to v0.4+. persistent_5xxis intentionally out-of-band — server-malfunction signal, NOT unreachability. Classified via in-probe bounded retry (3 attempts × 500ms); rolls up to existingupstream_issue, NOTservice_unreachable. Per ADR-006 explicit design.- 3 new CLI flags on
bazaar-check:--probe-history-log <path>— JSONL log for cross-invocation probe history. Without it, single-probe-only mode (top-levelservice_unreachablenever fires).--unreachable-consensus-count <n>— consecutive matching probes required for top-level promotion (default 3).--unreachable-interval-multiplier <n>— uniform scalar over the per-cause windows table (default 1).
Clockabstraction (src/bazaar/clock.ts) — injectable for deterministic probe-history timestamps + window computations in tests. Production usesrealClock; tests usecreateMockClock().- Anti-pattern documented: "don't key verdicts on third-party single-snapshot status fields" — per @TomSmart_ai's 2026-05-28 traceroute analysis (13/15 mapper.db-labeled-unreachable endpoints were HTTP-reachable on re-probe; status field is stale by design). x402trace always uses its own multi-probe consensus.
Changed (I — X402-52)
bazaar-checkJSON output: results[] now contains 8 entries (was 7 after G). New 8th entry"reachability". No existing field renamed, removed, or reordered — X402-44 contract preserved.BazaarVerdictdiscriminated union now includes theservice_unreachablevariant alongsidelooks_correct | implementation_issue | upstream_issue | upstream_stuck. New required fields on theservice_unreachablevariant:unreachableCause,consensusThreshold,probeCount. Strictly additive — existing variants unchanged.verdict.tsUPSTREAM_CHECKS set extended to include"reachability"— info-status from I rolls to upstream signal when consensus not met. Verdict synthesizer checks reachability consensus FIRST and pre-empts the entire downstream verdict chain when fired.src/bazaar/json-api.mddocuments the new check + facet + verdict shape + the precedence rule.src/bazaar/diagnose-rules.mdreplaces the "pending implementation" placeholder with the full reachability spec.src/decoder/schema.mddocuments the newbazaar.probe_attemptevent discriminant.
Internal (I — X402-52)
src/bazaar/reachability.ts— new check module. Public surface:checkReachability,classifyFetchError. Single-probe + history-read + consensus-compute + facet emission + optional log append in one orchestration.src/bazaar/probe-history.ts— new module. Public surface:readProbeHistory,consensusReached,nextAttemptSeq,PER_CAUSE_INTERVAL_MS. Pure functions; no I/O beyondreadFileSyncon the JSONL log.src/bazaar/clock.ts— new module.Clockinterface,realClock,createMockClock.src/bazaar/types.ts—ReachabilityState,UnreachableCause,ReachabilityFacet,ProbeAttemptRecordtypes added.- Test count: G cycle shipped 598 → 646 passed + 4 skipped (650 total) in this I cycle. +48 from 37 new reachability/probe-history/clock unit tests + 11 integration assertions.
- Publish-surface cap raised 540 KB → 600 KB + file count 110 → 120 in
scripts/check-publish-surface.mjs. I added ~52 KB across 3 new source modules + their .d.ts companions in dist/.
JSON API (I — X402-52)
- Additive: new 8th check
"reachability"appended toresults[](was 7 in G cycle). New optionalreachability.detail.{state, unreachable_cause, probe_count, consensus_threshold, consensus_met, consensus_window_ms, diagnostic}shape. Newservice_unreachablevariant onBazaarVerdictdiscriminated union with required fieldsunreachableCause,consensusThreshold,probeCount. No existing field renamed, removed, reordered, or retyped. X402-44 contract preserved (additive change, minor-version-eligible per ADR-004 Pillar 2). Snapshot fixture regenerated.
Added (G — X402-51)
facilitator-fitnesscheck (X402-51, per ADR-005). New diagnose-rule that probes the merchant's declaredextensions.bazaar.facilitatoragainst a built-in registry (CDP, PayAI, x402.org/facilitator) and emits a per-rail fitness facet. Closes the v0.3.2 gap:indexing.indexer_state: not_applicable_non_cdpcorrectly avoided misattribution but offered no positive signal for non-CDP services. G fills the silence with a per-rail health probe. Voices: @Cryptor (CDP-only-by-design correction) + @TomSmart_ai (mapper-integration consumer) + @Cinderwright 3rd-touch (#1065 PayAI alternative). Canonical multi-rail fixture from Ferj/@hypeprinter007-stack's anchor-x402 (3 rails: Base USDC CDP + Solana USDC CDP + JPY Coin Polygon).src/bazaar/facilitator-registry.json— built-in registry of 3 facilitators (CDP, PayAI, x402.org/facilitator) with declared URLs, name aliases, probe endpoints. Per ADR-005 § Risks #1: registry is data, not code; new facilitators land as PRs adding entries. Operator override via--facilitator-registry <path>deferred to v0.4+.- New
facilitator_fitnessfacet onfacilitator-fitnesscheck — per-rail array ({ rail, network, facilitator, identity_source, fitness, diagnostic? }) + summary counts. Strictly additive per X402-44. Verdict synthesizer treats info-statusfacilitator-fitnessas upstream signal — any unreachable rail rolls up toupstream_issue(exit 3);degradedrails surface in facet without flipping the verdict. - Identity-source attribution — declared / inferred-from-tx / unknown. v0.3.4 MVP supports
declaredonly (read frommanifest.extensions.bazaar.facilitator); tx-from inference reserved as a v0.4+ slot. Per ADR-005: declared-identity-first is load-bearing for gasless rails (SKALE+PayAI per TKCollective's fixture offer) where buyer-side txfromis the gasless relayer, not the facilitator. - Bounded retry + backoff on
/verifyprobe — 3 attempts at 500ms / 1s / 2s per the @mkmkkkkk #1065 pattern. 4xx responses read asok(facilitator is responsive; probe-payload rejection is expected). Cached per facilitator URL for the duration of a singlebazaar-checkrun. anchor-x402-facilitator-fitness-ok-multi-rail.jsonnew fixture — Ferj/@hypeprinter007-stack's 3-rail capture adapted to G acceptance. Tests per-rail emission + summary aggregation across Base/Solana/Polygon when CDP is declared and the facilitator probe returns 2xx.
Changed (G — X402-51)
bazaar-checkJSON output: results[] now contains 7 entries (was 6 after L). New 7th entry"facilitator-fitness"appended. No existing field renamed, removed, or reordered — X402-44 contract preserved.verdict.tsUPSTREAM_CHECKS set extended to include"facilitator-fitness"— info-status from G rolls up toupstream_issueverdict when any rail is unreachable. Existing v0.3.2 verdict-rollup semantics unchanged for indexing/propagation.src/bazaar/json-api.mddocuments the new check + facet shape + identity-source attribution.src/bazaar/diagnose-rules.mdreplaces the previous "pending implementation" placeholder with the full G design + states + facet shape + multi-rail synthesis rule + cross-facet precedence.
Internal (G — X402-51)
src/bazaar/facilitator-fitness.ts— new check module. Public surface:checkFacilitatorFitness,probeFacilitatorWithRetry,loadFacilitatorRegistry,resolveFacilitator. Pure-function rule pair + a single network probe with bounded retry.src/bazaar/types.ts—FacilitatorFitnessState,FacilitatorFitnessIdentitySource,FacilitatorFitnessRailEntry,FacilitatorFitnessFacettypes added.- Publish-surface cap raised 480 KB → 540 KB in
scripts/check-publish-surface.mjs. v0.3.4 K shipped at ~476 KB (~4 KB headroom); G adds ~30 KB (registry JSON + module + 25 unit tests + new multi-rail fixture). 540 KB gives ~32 KB headroom for I (probe-history state + 5+ new reachability fixtures + JSONL probe_attempt event discriminant). - Test count: K cycle shipped 570 passed → 598 passed + 4 skipped (602 total) in this G cycle. +28 from the 25 new
tests/unit/bazaar-facilitator-fitness.test.tscases + 3 new integration assertions from the multi-rail fixture.
JSON API (G — X402-51)
- Additive: new 7th check
"facilitator-fitness"appended toresults[](was 6 in K cycle, 5 in v0.3.3). New optionalfacilitator-fitness.detail.facilitator_fitness.{rails, summary}shape. No existing field renamed, removed, reordered, or retyped. X402-44 contract preserved (additive change, minor-version-eligible per ADR-004 Pillar 2). Snapshot fixture regenerated.
Added (K — X402-50)
- K rule pair:
payment_payload_missing_resource_object+extensions_not_echoed(X402-50, per ADR-007). Two diagnose rules that refine the existingupstream_stuckverdict with attribution to a specific root cause. Voices: @RipperMercs (TensorFeed canonical writeup, 1 → 29 indexed in <1hr) + @TKCollective (44-line patch, 16+d stuck → 22min indexed). @AsaiShota's contrast case (payload-correct, still stuck) baked into AC as the false-positive sentinel via thetest-echo-cdp-stuck-cause-unknown.jsonfixture. - New
verdict.causediscriminator on theupstream_stuckvariant — values:payload_echo_gap | indexer_state_processing | indexer_state_terminal | unknown. Strictly additive per X402-44; exit-code surface unchanged (still 3 for upstream_stuck regardless of cause). Downstream consumers (TomSmart_ai's mapper, @poteshniy's/v1/reputation) get a discriminator they can bucket stuck listings on without re-implementing payload introspection. - Captured-response fixture schema extended with optional
mocks.paymentPayload+mocks.settleblocks so fixtures can simulate K's buyer-side input data. The fixture harness threads these intorunBazaarCheckvia the newpaymentPayloadCapture+settleCaptureoptions onBazaarCheckOptions. Existing fixtures (d2/d3/d5/host-pollution) work unchanged — K rules defer when no capture is supplied. agentoracle-upstream-stuck-body-discovery.jsonpre-fix fixture extended with paymentPayload (bare-string resource, triggers Rule 1) + settle (e30=header, triggers Rule 2) + newexpected.verdictCause: "payload_echo_gap"assertion. Paired with @TKCollective's post-fix delta-row fixture in PR #99 (held against K's landing).test-echo-cdp-stuck-cause-unknown.jsonnew fixture — @AsaiShota-style contrast voice. Payload is well-formed object, extensions properly echoed, but indexer still shows processing. Assertsverdict.cause === "unknown"— the canonical false-positive sentinel for K's attribution precision.
Changed
bazaar-checkJSON output:verdict.causefield added to theupstream_stuckvariant. Additive per X402-44 contract (no field renamed/removed/reordered). Snapshot fixture regenerated.- Challenge check's
detailfield now carrieschallengeExtensionson the pass path — the parsedextensionsblock from the 402 body, exposed so the verdict synthesizer can read it for Rule 2's challenge-side non-empty check without re-parsing the raw body. Additive per X402-44.
Documentation
src/bazaar/diagnose-rules.md— new K rule pair section + the contrast-voice false-positive sentinel pattern. Updates the documented anti-patterns alongside ADR-006's "don't key on third-party single-snapshot status fields."src/bazaar/json-api.md— documentsverdict.causediscriminator + the four cause values + K-rule capture inputs + the capture-checked-sentinel rule for AsaiShota's case.
Internal (K — X402-50)
src/bazaar/payment-payload-rules.ts— new module withevaluateMissingResourceObject,evaluateExtensionsNotEchoed, and the aggregatorevaluatePaymentPayloadEchoGap. Pure functions; no fetch/IO. Reference impl pattern lives in repo (linked from the docs); x402trace does NOT import or depend on @RipperMercs's tensorfeed code.src/bazaar/types.ts—UpstreamStuckCause,PaymentPayloadCapture,SettleResponseCapturetypes added.BazaarVerdict.upstream_stuckvariant gains the requiredcausefield.src/bazaar/verdict.ts—synthesiseVerdictaccepts an optionalSynthesiseVerdictOptionsarg carrying K-rule capture inputs. Computescausevia the precedence rule: K fired →payload_echo_gap; both rules ran and returned false →unknown(capture-checked sentinel); else indexer-state-derived →indexer_state_processing(orunknown).- Test count: v0.3.4 L cycle shipped 544 passed → 570 passed + 4 skipped (574 total) in this K cycle. +26 from the 21 new unit-test cases in
tests/unit/bazaar-payment-payload-rules.test.ts+ 5 new integration assertions (K verdict-cause checks + new contrast fixture).
JSON API (K — X402-50)
- Additive: new optional
verdict.causefield on theupstream_stuckvariant. New optionaldetail.challengeExtensionson the challenge check's pass-path result. No existing field renamed, removed, reordered, or retyped. X402-44 contract preserved (additive change, minor-version-eligible per ADR-004 Pillar 2). TomSmart_ai's mapper-integration + @poteshniy'sagenttrust.uk/v1/reputationAPI require no changes; the new field appears alongside existing data on theupstream_stuckvariant only.
L — X402-53 (host_pollution) — shipped via #101 earlier in the v0.3.4 cycle; entries below documented at that time.
Added (L — X402-53)
host-pollutioncheck (X402-53, thanks @hypeprinter007-stack / Ferj 🙏 — same operator, identity-merge confirmed 2026-05-29). New diagnose-rule per ADR-008 (single-voice bypass under the D.5 precedent). The check queries CDP's merchant discovery endpoint (/platform/v2/x402/discovery/merchant?payTo=<addr>&limit=50) and groups returned resources by canonical path; when a path appears under more than one hostname for the same payTo, fires a listing-hygiene warning. Canonical case (per the contributed fixture at https://github.com/hypeprinter007-stack/anchor-x402/tree/main/fixtures/x402trace-L):anchor-x402.comindexes 25 entries for one payTo across 3 hosts, with 9 resource paths each appearing on 2 distinct hostnames./v1/anchorfor example lives under bothapi.anchor-x402.comand the raw API Gateway URL (1c09pdnrx1.execute-api.us-east-1.amazonaws.com). Code is correct, ops are leaky — same Lambda answers multiple hostnames, CDP captures the URL the buyer hit (not the canonical resource URL), the merchant index shows duplicates.- New
host-pollutionJSON API facet underresults[].detail(additive per X402-44 contract — see src/bazaar/json-api.md). Shape:{ state: "no_pollution" | "polluted" | "unknown" | "not_applicable_non_cdp", polluted_paths?: [{ resource_path, hosts[] }], polluted_path_count?, total_entries?, distinct_hosts?, queryUrl? }. The verdict synthesizer intentionally does NOT includehost-pollutionin its upstream-checks set —looks_correctcontinues to roll up to exit code 0 even when this facet fires. Exit-code surface stays the three-value contract per ADR-004 Pillar 2; downstream consumers grep the facet for the listing-hygiene warning. src/bazaar/diagnose-rules.md— new implementation-companion doc for the bazaar-check rule patterns, the documented anti-patterns (specifically "don't key verdicts on third-party single-snapshot status fields" per TomSmart_ai's 2026-05-28 traceroute anti-evidence absorbed into ADR-006), and the checklist for adding new diagnose rules. ADR-005 / ADR-006 / ADR-007 / ADR-008 all reference this file.tests/fixtures/bazaar/regenerate-json-api-snapshot.ts— dedicated regenerator script for the X402-44 JSON API snapshot fixture. The existingsrc/bazaar/json-api.mdreferenced this path; lands here for the first time as part of X402-53 since this is the first change that needed to extend the snapshot.
Documentation
- ADR-005 (G facilitator-fitness) in DECISIONS.md — accepted 2026-05-29. Per-rail
facilitator_fitnessfacet via declared-identity-first (not tx-frominference, load-bearing for gasless rails like SKALE+PayAI per @TKCollective's offered fixture). Built-in registry: CDP, PayAI, x402.org/facilitator. Anchored to X402-51. Voices: @Cryptor + @TomSmart_ai + @Cinderwright 3rd-touch (#1065 PayAI workaround 2026-05-29). - ADR-006 (I service_unreachable + probe-history) in DECISIONS.md — accepted 2026-05-29. Top-level
service_unreachableverdict gated on multi-probe consensus (default 3 probes ≥5m apart) + failure-mode classification axis (dns_failure | tcp_refused | tls_error | timeout | persistent_5xx). Persistent-NXDOMAIN-over-N-probes ships first as cleanest subset. Probe-history inlined in JSONL log via newbazaar.probe_attemptevent discriminant — first stateful verdict; preserves the local-first stateless property (no external state dir). Anchored to X402-52. Absorbs @TomSmart_ai's 2026-05-28 traceroute anti-evidence (13/15 mapper.db-labeled-unreachable endpoints actually HTTP-reachable on re-probe). - ADR-008 (L host_pollution) in DECISIONS.md — accepted 2026-05-29. Single-voice bypass under the D.5 precedent (ADR-004 / X402-43). Voice: @ferj === @hypeprinter007-stack (identity-merge confirmed 2026-05-29). Naming-clash resolved:
Hwas reserved for @RipperMercs' #85 directory-only-manifest signal (still 1 voice, held); the new candidate is filed asL. src/bazaar/json-api.mdupdated to document the newhost-pollutioncheck entry +host_pollutionfacet shape + the verdict-rollup rule (warning-only, no exit-code change). Updated the regeneration instructions to point at the new dedicated script.
Internal
CLAUDE.mdhard rule #5 + Branching strategy section updated 2026-05-29: branch offmain(notv1). Reflects v0.3.x cycle practice through #66–#99;v1/stagingare historical v0.1/v0.2 conventions no longer in use.- Publish-surface cap raised 440 KB → 480 KB in
scripts/check-publish-surface.mjs. v0.3.3 shipped at ~430 KB (~10 KB headroom); v0.3.4 adds 4 new facets (host_pollution+facilitator_fitness+reachability+upstream_stuck_cause) + newbazaar.probe_attemptevent discriminant +diagnose-rules.md+facilitator-registry.jsonacross K/G/I/L. 480 KB gives ~30 KB headroom for the K + G + I implementation cycle after L lands.
JSON API
- Additive: new 6th check
"host-pollution"appended toresults[](was 5 in v0.3.3). New optionalhost-pollution.detail.*keys (state,polluted_paths,polluted_path_count,total_entries,distinct_hosts). Snapshot fixture regenerated. No existing field renamed, removed, or reordered — X402-44 contract preserved (additive change, minor-version-eligible per ADR-004 Pillar 2). TomSmart_ai's mapper-integration + @poteshniy'sagenttrust.uk/v1/reputationAPI (the named JSON API consumers per X402-44) require no changes; the new facet appears alongside existing data.
Notes
- Test count: v0.3.3 ran 514 passed + 4 skipped (518 total) → 544 passed + 4 skipped (548 total), +30 tests from the 24 new
host-pollutionunit-test cases + 4 new fixture-driven assertions + 2 small adjustments to existing pipeline + json-api integration tests for the new check. - Contributors: @hypeprinter007-stack / Ferj 🙏 — fifth+ contribution in the v0.3.x window (PR #66 v2 parse fix v0.3.1 + PR #70 D.1 manifest hygiene v0.3.2 + bazaar-indexing-spec correction v0.3.2 + #2207 anchor-x402 multi-rail fixture offer + L voice + same-day capture-fixture delivery for X402-53). Identity-merge confirmed 2026-05-29: the GitHub
@hypeprinter007-stackhandle and the DiscordFerj(cdp-verified) handle are the same operator, working onanchor-x402.com. Sustained-velocity contributor pattern.