Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### Added

- **`bazaar-check --log json` is now a documented public API contract** ([X402-44](https://vahdatfardin.atlassian.net/browse/X402-44), ADR-004 Pillar 2). Three deliverables landed together: the contract doc at [`src/bazaar/json-api.md`](./src/bazaar/json-api.md) (envelope shape + per-check `detail` keys + verdict discriminator + stability rules + regeneration workflow), a frozen exemplar at [`tests/fixtures/bazaar/json-api-snapshot.json`](./tests/fixtures/bazaar/json-api-snapshot.json), and a snapshot test at [`tests/integration/bazaar-check-json-api.test.ts`](./tests/integration/bazaar-check-json-api.test.ts) (6 tests catch field renames, removals, additions, and reordering against the exemplar). **Versioning rule** committed: additive changes (new optional fields, new check names, new `verdict.kind` values, new `detail.*` keys) ship in MINOR versions with a `### JSON API` CHANGELOG entry; shape-breaking changes (renames, removals, type changes, fixed-position reordering) require a MAJOR version + integrator notice. Exit-code contract preserved across all minor versions (D.3's `upstream_stuck` will roll up to exit code 3, not a new code). README "JSON API" section added under the `bazaar-check` documentation; `CONTRIBUTING.md` "JSON API discipline" section added with a PR self-check. TomSmart_ai's mapper-integration is the named consumer this contract is committed to.
- **`bazaar-check --endpoint <paid-url>` per-route 402 probe mode** ([X402-42](https://vahdatfardin.atlassian.net/browse/X402-42), D.4). For services that publish per-route instead of at root `/.well-known/x402` — the [#2207](https://github.com/x402-foundation/x402/issues/2207) pattern documented by @AsaiShota (test-echo-cdp), @evanatpizzarobot (TensorFeed), and @0xdespot (hyperD.ai). When `--endpoint` is supplied, the bazaar-check pipeline skips the root well-known probe entirely and fetches the 402 challenge directly from the given paid URL; manifest-shape validations run against the 402 body instead of the root manifest. Self-payment guard + CDP indexing query continue to run unchanged (they consume the challenge body's `payTo` regardless of how the challenge was obtained). UX: a one-line info note prints when `--endpoint` is used (`ℹ skipping root /.well-known/x402 probe per --endpoint. Note: services that DO publish at root signal extra discoverability hygiene; consider both.`); routes to stdout in `--log human` and stderr in `--log json` so stdout stays JSON-parseable. New `endpoint?: string` field on `BazaarCheckOptions` (programmatic API); new `endpoint?: string` field on `BazaarCheckCommandOptions`. URL-validated at command entry; non-URL values exit 1 with a clear error.

### Fixed
Expand All @@ -19,10 +20,11 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### JSON API

Two additive changes to the `--log json` output this cycle, both preserving the existing envelope shape per ADR-004 Pillar 2:
X402-44 establishes the formal `### JSON API` subsection discipline going forward — every PR that touches `--log json` output must add an entry here. Two additive changes are folded in retrospectively for the v0.3.2 cycle, both preserving the existing envelope shape per ADR-004 Pillar 2:

- The `bazaar-check` JSON output now includes a `detail.variant` field on extensions.bazaar-related check results from the D.5 variant-aware refactor (value: `"mcp-discovery"`, `"body-discovery"`, or `"unknown"`).
- When `--endpoint` is supplied, the `well-known` result shape is preserved with a different `message`: `{ check: "well-known", status: "pass", message: "skipped per --endpoint (probing <url> directly instead of /.well-known/x402)" }`. The four-result envelope shape is preserved; the well-known slot's `message` field is the only carrier of the skip signal.
- No other shape changes this cycle. The frozen exemplar at `tests/fixtures/bazaar/json-api-snapshot.json` captures the canonical envelope after D.4 + D.5 land.

## [0.3.1] — 2026-05-20

Expand Down
23 changes: 23 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,29 @@ Not required, but helps when scanning history.

---

## JSON API discipline (`bazaar-check --log json`)

The `bazaar-check --log json` output is a **public API contract** as of v0.3.2 (ADR-004 Pillar 2). Downstream consumers (mapper integrations, agent filters) take a runtime dependency on the shape.

**Before merging any change that touches the JSON envelope, ask:**

1. Does this rename, remove, or reorder a field? → **shape-breaking**; requires a major-version bump + deprecation cycle. Do NOT merge without discussion.
2. Does this add a new OPTIONAL field, new check, or new `verdict.kind` value? → **additive**; OK in a minor version. Required steps:
- Regenerate the snapshot fixture: see [`src/bazaar/json-api.md`](./src/bazaar/json-api.md#regenerating-the-snapshot-fixture)
- Add a `### JSON API` subsection to `CHANGELOG.md` `[Unreleased]` documenting what changed
3. Did the snapshot test (`tests/integration/bazaar-check-json-api.test.ts`) fail unexpectedly? → the shape changed accidentally. Either fix the code (preferred) OR if the change was intentional, follow step 2.

**PR description self-check:**

- [ ] Did this PR change the `--log json` output shape (any way)?
- [ ] If yes, is the change additive (new optional field) or shape-breaking (rename/removal)?
- [ ] If additive: regenerated snapshot fixture + added `### JSON API` CHANGELOG entry?
- [ ] If shape-breaking: opened a deprecation issue + notified named consumers (TomSmart_ai mapper, etc.)?

See [`src/bazaar/json-api.md`](./src/bazaar/json-api.md) for the full contract.

---

## Reporting bugs

Open a GitHub issue with:
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,23 @@ Four read-only checks compose into a single bottom-line verdict:
- Read-only. Never signs, never broadcasts.
- Each HTTP probe has a 10s default timeout; use `--timeout-ms` to shorten it in CI or when checking flaky endpoints.
- The opt-in paid-pass mode (`--with-wallet`) is **deferred** — see [ADR-003](./DECISIONS.md). The static-analysis-only checks shipped here cover the dominant Discord pain (Bazaar indexing failure) without needing signing infrastructure.
- **`--endpoint <paid-url>` (v0.3.2+)** — skip the root `/.well-known/x402` probe and read the 402 challenge directly from a paid route. Use this for services that publish per-route only (the [#2207](https://github.com/x402-foundation/x402/issues/2207) shape — AsaiShota's test-echo-cdp, evanatpizzarobot's TensorFeed, 0xdespot's hyperD).

### JSON API (v0.3.2+) — `--log json` is a public contract

`bazaar-check --log json` emits a JSON envelope downstream consumers can take a runtime dependency on. Per [ADR-004 Pillar 2](./DECISIONS.md), the maintainer commits to:

- **Additive changes** (new optional fields, new optional facets) — ship in MINOR versions
- **Shape-breaking changes** (renames, removals, type changes, fixed-position reordering) — require a MAJOR version + integrator notice
- Every change to the `--log json` output is documented in `CHANGELOG.md` under a `### JSON API` subsection

The full contract — envelope shape, per-check `detail` keys, verdict discriminator, regeneration workflow — lives at [`src/bazaar/json-api.md`](./src/bazaar/json-api.md). A frozen exemplar lives at [`tests/fixtures/bazaar/json-api-snapshot.json`](./tests/fixtures/bazaar/json-api-snapshot.json) and is enforced by [`tests/integration/bazaar-check-json-api.test.ts`](./tests/integration/bazaar-check-json-api.test.ts).

```bash
# Pipe the JSON into your own consumer
x402trace bazaar-check https://your-service.example.com --log json | jq '.verdict.kind'
# → "looks_correct" (or "implementation_issue" / "upstream_issue")
```

### Chain selection (Base mainnet, v0.3+)

Expand Down
148 changes: 148 additions & 0 deletions src/bazaar/json-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# `bazaar-check --log json` — Public JSON API contract

This document is the **public API contract** for `bazaar-check --log json` output. Downstream consumers (TomSmart_ai's mapper-integration, agent-side filters, future bazaar-aware tools) take a runtime dependency on the shape described here. Per [ADR-004 Pillar 2](../../DECISIONS.md), the maintainer commits to the stability rules below.

The frozen exemplar lives at [`tests/fixtures/bazaar/json-api-snapshot.json`](../../tests/fixtures/bazaar/json-api-snapshot.json); the test that enforces it lives at [`tests/integration/bazaar-check-json-api.test.ts`](../../tests/integration/bazaar-check-json-api.test.ts).

## Envelope

A single JSON object per `bazaar-check` invocation, written to stdout when `--log json` is supplied. Four top-level keys, in this order:

```jsonc
{
"serviceUrl": "https://...", // string — the service URL the run targets
"chain": "base-sepolia", // "base-sepolia" | "base"
"results": [...], // CheckResult[] — exactly 4 entries
"verdict": {...} // BazaarVerdict — discriminated union
}
```

## `results[]` — the four checks

Always four entries, in this fixed order:

1. `"well-known"` — root `/.well-known/x402` manifest probe
2. `"challenge"` — 402 challenge structure probe (uses `--endpoint` URL when supplied)
3. `"self-payment"` — payer ≠ payTo guard (informational when `--payer-hint` absent)
4. `"indexing"` — CDP discovery query

Each entry is a `CheckResult`:

```jsonc
{
"check": "well-known", // stable identifier; downstream tools grep/filter on this
"status": "pass" | "fail" | "info",
"message": "...", // one-line human-readable description
"fix": "...", // OPTIONAL — present when status is "fail" or "info"
"detail": { ... } // OPTIONAL — per-check structured detail
}
```

### `status` semantics

- **`pass`** — the check found no issue in your implementation
- **`fail`** — the check found a concrete issue you can fix
- **`info`** — the check observed an upstream-bound signal that is NOT your fault but worth surfacing (e.g. CDP indexing stuck on "processing" — the canonical [#2207](https://github.com/x402-foundation/x402/issues/2207) pattern)

### `detail` fields per check (additive over time)

These fields are present when the check fires the corresponding code path. Consumers should treat `detail` as additive — never assume absence means "not applicable"; always check for key presence.

| Check | Detail keys |
| ----------- | -------------------------------------------------------------------------------------------- |
| `well-known` | `issues[]` (when status=fail), `httpStatus` (when status=fail and HTTP error) |
| `challenge` | `httpStatus` (when fetch fails), `missingFields[]` + `variant` (when extensions.bazaar fail) |
| `self-payment` | (no detail fields today) |
| `indexing` | `queryUrl`, `status` (one of `"indexed" \| "processing" \| "not_found" \| "error"`), `count` (when indexed), `httpStatus` (when HTTP error) |

The `challenge.detail.variant` field carries the detected discovery-extension variant (`"mcp-discovery"` \| `"body-discovery"` \| `"unknown"`) on failed validations. See ADR-004 Pillar 3 for the variant model.

## `verdict` — the discriminated union

```jsonc
{
"kind": "looks_correct" | "implementation_issue" | "upstream_issue",
"exitCode": 0 | 2 | 3,
"message": "...",
// Per-kind additional fields:
"failedChecks": [...] // ONLY on "implementation_issue"
"upstreamChecks": [...] // ONLY on "upstream_issue"
}
```

### Exit-code contract (preserved unchanged across all minor versions)

- `0` ↔ `looks_correct`
- `2` ↔ `implementation_issue`
- `3` ↔ `upstream_issue`

D.3's `upstream_stuck` composite (from ADR-004 Pillar 1, landing in X402-46) rolls up to exit code 3 — the verdict prose names the distinction; the exit-code surface stays a 3-value contract. Consumers grepping exit codes don't break.

## Stability rules

Per [ADR-004 Pillar 2](../../DECISIONS.md):

### ✅ Additive changes — ship in MINOR versions

- New OPTIONAL fields at any level (e.g. `verdict.severity`, `results[].detail.warningHint`)
- New CHECK names (e.g. a 5th check beyond the canonical 4)
- New `verdict.kind` discriminator values (e.g. `upstream_stuck` per D.3)
- New `detail.*` keys on existing checks (e.g. `indexer_state` for D.3)
- New OPTIONAL top-level fields

CHANGELOG `### JSON API` entry required even for additive changes — downstream consumers track the shape, not just behavior.

### 🚫 Shape-breaking changes — require a MAJOR version + integrator notice

- Field RENAMES (`verdict.kind` → `verdict.type`)
- Field REMOVALS
- Type changes (`exitCode: number` → `exitCode: string`)
- REORDERING of fixed-position fields (results array order, top-level key order)
- Removing a `verdict.kind` value
- Removing a CHECK name

Pre-major-bump steps:

1. Open a deprecation issue, comment for at least 2 weeks
2. Notify named downstream consumers (TomSmart_ai's mapper, etc.) via DM
3. Cut a deprecation release that emits BOTH old and new shapes side-by-side under a feature flag, if the affected consumers can't update quickly
4. Major version bump + CHANGELOG `### JSON API` entry with migration notes

## Regenerating the snapshot fixture

When the JSON shape is intentionally changing (additive OR shape-breaking), regenerate the fixture:

```bash
# Option A — hand-edit if you know the exact new shape
$EDITOR tests/fixtures/bazaar/json-api-snapshot.json

# Option B — capture from a live deterministic run (recommended)
pnpm tsx -e '
import { runBazaarCheck } from "./src/bazaar/index.js";
// ... wire up the deterministic fetcher from the test file ...
const report = await runBazaarCheck({...});
console.log(JSON.stringify(report, null, 2));
' > tests/fixtures/bazaar/json-api-snapshot.json
```

Then add a `### JSON API` entry to `CHANGELOG.md` `[Unreleased]` documenting:

- What field(s) changed (added / renamed / removed)
- Whether the change is additive (minor) or shape-breaking (major)
- Migration notes if shape-breaking

## Worked example

Running `bazaar-check https://api.example.test/api/snapshot --log json` against a service that passes all four checks produces the fixture in [`tests/fixtures/bazaar/json-api-snapshot.json`](../../tests/fixtures/bazaar/json-api-snapshot.json) verbatim.

When `--endpoint <paid-url>` is supplied, the `well-known` slot keeps the same shape but carries a different `message`:

```jsonc
{
"check": "well-known",
"status": "pass",
"message": "skipped per --endpoint (probing <url> directly instead of /.well-known/x402)"
}
```

The four-result envelope shape is preserved — the well-known slot's `message` field is the only carrier of the skip signal. This is additive per Pillar 2 (no fields changed; only `message` content differs).
36 changes: 36 additions & 0 deletions tests/fixtures/bazaar/json-api-snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"serviceUrl": "https://api.example.test/api/snapshot",
"chain": "base-sepolia",
"results": [
{
"check": "well-known",
"status": "pass",
"message": "manifest at https://api.example.test/.well-known/x402 is well-formed (name=\"Snapshot API\", description set, accepts [0], extensions.bazaar populated)"
},
{
"check": "challenge",
"status": "pass",
"message": "extensions.bazaar present (name=\"Snapshot API\", description set)"
},
{
"check": "self-payment",
"status": "pass",
"message": "no payer-hint supplied; self-payment guard skipped (pass by default)"
},
{
"check": "indexing",
"status": "pass",
"message": "discovery returned 1 resource(s) for payTo=0x1111111111111111111111111111111111111111 — indexed",
"detail": {
"queryUrl": "https://test-discovery.example.test/v2/x402/discovery/resources?payTo=0x1111111111111111111111111111111111111111",
"status": "indexed",
"count": 1
}
}
],
"verdict": {
"kind": "looks_correct",
"exitCode": 0,
"message": "all checks pass. Your bazaar integration looks correct; if you're seeing a Bazaar indexing delay, give CDP 24-48h before assuming a problem."
}
}
Loading