Skip to content

v0.3.4

Latest

Choose a tag to compare

@github-actions github-actions released this 30 May 13:57
509b62d

[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)

  • reachability check (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's upstream_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 rule service_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 in src/bazaar/diagnose-rules.md.
  • bazaar.probe_attempt JSONL event discriminant (src/decoder/schema.md) — first stateful event added by bazaar-check. Records every reachability probe (success or fail) to enable cross-invocation multi-probe consensus.
  • First stateful verdict in x402traceservice_unreachable requires 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_5xx is intentionally out-of-band — server-malfunction signal, NOT unreachability. Classified via in-probe bounded retry (3 attempts × 500ms); rolls up to existing upstream_issue, NOT service_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-level service_unreachable never 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).
  • Clock abstraction (src/bazaar/clock.ts) — injectable for deterministic probe-history timestamps + window computations in tests. Production uses realClock; tests use createMockClock().
  • 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-check JSON 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.
  • BazaarVerdict discriminated union now includes the service_unreachable variant alongside looks_correct | implementation_issue | upstream_issue | upstream_stuck. New required fields on the service_unreachable variant: unreachableCause, consensusThreshold, probeCount. Strictly additive — existing variants unchanged.
  • verdict.ts UPSTREAM_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.md documents the new check + facet + verdict shape + the precedence rule.
  • src/bazaar/diagnose-rules.md replaces the "pending implementation" placeholder with the full reachability spec.
  • src/decoder/schema.md documents the new bazaar.probe_attempt event 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 beyond readFileSync on the JSONL log.
  • src/bazaar/clock.ts — new module. Clock interface, realClock, createMockClock.
  • src/bazaar/types.tsReachabilityState, UnreachableCause, ReachabilityFacet, ProbeAttemptRecord types 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 to results[] (was 7 in G cycle). New optional reachability.detail.{state, unreachable_cause, probe_count, consensus_threshold, consensus_met, consensus_window_ms, diagnostic} shape. New service_unreachable variant on BazaarVerdict discriminated union with required fields unreachableCause, 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-fitness check (X402-51, per ADR-005). New diagnose-rule that probes the merchant's declared extensions.bazaar.facilitator against 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_cdp correctly 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_fitness facet on facilitator-fitness check — per-rail array ({ rail, network, facilitator, identity_source, fitness, diagnostic? }) + summary counts. Strictly additive per X402-44. Verdict synthesizer treats info-status facilitator-fitness as upstream signal — any unreachable rail rolls up to upstream_issue (exit 3); degraded rails surface in facet without flipping the verdict.
  • Identity-source attribution — declared / inferred-from-tx / unknown. v0.3.4 MVP supports declared only (read from manifest.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 tx from is the gasless relayer, not the facilitator.
  • Bounded retry + backoff on /verify probe — 3 attempts at 500ms / 1s / 2s per the @mkmkkkkk #1065 pattern. 4xx responses read as ok (facilitator is responsive; probe-payload rejection is expected). Cached per facilitator URL for the duration of a single bazaar-check run.
  • anchor-x402-facilitator-fitness-ok-multi-rail.json new 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-check JSON 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.ts UPSTREAM_CHECKS set extended to include "facilitator-fitness" — info-status from G rolls up to upstream_issue verdict when any rail is unreachable. Existing v0.3.2 verdict-rollup semantics unchanged for indexing/propagation.
  • src/bazaar/json-api.md documents the new check + facet shape + identity-source attribution.
  • src/bazaar/diagnose-rules.md replaces 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.tsFacilitatorFitnessState, FacilitatorFitnessIdentitySource, FacilitatorFitnessRailEntry, FacilitatorFitnessFacet types 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.ts cases + 3 new integration assertions from the multi-rail fixture.

JSON API (G — X402-51)

  • Additive: new 7th check "facilitator-fitness" appended to results[] (was 6 in K cycle, 5 in v0.3.3). New optional facilitator-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 existing upstream_stuck verdict 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 the test-echo-cdp-stuck-cause-unknown.json fixture.
  • New verdict.cause discriminator on the upstream_stuck variant — 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.settle blocks so fixtures can simulate K's buyer-side input data. The fixture harness threads these into runBazaarCheck via the new paymentPayloadCapture + settleCapture options on BazaarCheckOptions. Existing fixtures (d2/d3/d5/host-pollution) work unchanged — K rules defer when no capture is supplied.
  • agentoracle-upstream-stuck-body-discovery.json pre-fix fixture extended with paymentPayload (bare-string resource, triggers Rule 1) + settle (e30= header, triggers Rule 2) + new expected.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.json new fixture — @AsaiShota-style contrast voice. Payload is well-formed object, extensions properly echoed, but indexer still shows processing. Asserts verdict.cause === "unknown" — the canonical false-positive sentinel for K's attribution precision.

Changed

  • bazaar-check JSON output: verdict.cause field added to the upstream_stuck variant. Additive per X402-44 contract (no field renamed/removed/reordered). Snapshot fixture regenerated.
  • Challenge check's detail field now carries challengeExtensions on the pass path — the parsed extensions block 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 — documents verdict.cause discriminator + 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 with evaluateMissingResourceObject, evaluateExtensionsNotEchoed, and the aggregator evaluatePaymentPayloadEchoGap. 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.tsUpstreamStuckCause, PaymentPayloadCapture, SettleResponseCapture types added. BazaarVerdict.upstream_stuck variant gains the required cause field.
  • src/bazaar/verdict.tssynthesiseVerdict accepts an optional SynthesiseVerdictOptions arg carrying K-rule capture inputs. Computes cause via 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 (or unknown).
  • 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.cause field on the upstream_stuck variant. New optional detail.challengeExtensions on 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's agenttrust.uk/v1/reputation API require no changes; the new field appears alongside existing data on the upstream_stuck variant 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-pollution check (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.com indexes 25 entries for one payTo across 3 hosts, with 9 resource paths each appearing on 2 distinct hostnames. /v1/anchor for example lives under both api.anchor-x402.com and 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-pollution JSON API facet under results[].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 include host-pollution in its upstream-checks set — looks_correct continues 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 existing src/bazaar/json-api.md referenced 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_fitness facet via declared-identity-first (not tx-from inference, 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_unreachable verdict 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 new bazaar.probe_attempt event 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: H was reserved for @RipperMercs' #85 directory-only-manifest signal (still 1 voice, held); the new candidate is filed as L.
  • src/bazaar/json-api.md updated to document the new host-pollution check entry + host_pollution facet shape + the verdict-rollup rule (warning-only, no exit-code change). Updated the regeneration instructions to point at the new dedicated script.

Internal

  • CLAUDE.md hard rule #5 + Branching strategy section updated 2026-05-29: branch off main (not v1). Reflects v0.3.x cycle practice through #66#99; v1/staging are 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) + new bazaar.probe_attempt event discriminant + diagnose-rules.md + facilitator-registry.json across 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 to results[] (was 5 in v0.3.3). New optional host-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's agenttrust.uk/v1/reputation API (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-pollution unit-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-stack handle and the Discord Ferj (cdp-verified) handle are the same operator, working on anchor-x402.com. Sustained-velocity contributor pattern.