diff --git a/PRODUCT.md b/PRODUCT.md index 7a395ac..8e2ee01 100644 --- a/PRODUCT.md +++ b/PRODUCT.md @@ -38,7 +38,7 @@ The demo thesis, in the team's own words: *"We gave our AI business zero dollars - Nine surfaces: Overview, Jobs, job detail, Approvals, Float, Portal (customer), Audit, Workforce, Settings. - Money is always atomic USDC — six decimal places, carried as base-10 strings or integers. Never a float, anywhere, including in display code. - Contracts are frozen: JobVault (escrow), FloatPool (advance vault), AuditAnchor. An advance is capped at 10% of pool TVL per org and 80% utilisation pool-wide; the fee is 2% of principal and 20% of that fee goes to a first-loss reserve. -- **No payment has settled through the x402 agent-purchase path, and no end-to-end run has ever been logged.** The facilitator client exists but has never run against Circle. Surfaces must not imply otherwise. +- **A 0.04 USDC agent purchase has settled end to end through the x402 path, self-facilitated (`docs/spine-runs/2026-08-08-first-x402-settlement.md`). Separately, the Circle Gateway facilitator client is built and has never run against Circle's live service.** These are two independent facts — settlement did not require Gateway — and surfaces must state both, implying neither's reverse. - Two figures on Overview are read live from the chain and are real today (pool TVL, advance rate). Most other panels have no data until a run happens. - Sample data is permitted **only** behind an explicit flag stripped from the deployed artifact, following the existing `SNAPFALL_DEMO_STREAM` precedent. This was a deliberate operator decision, taken after the risk was raised. @@ -53,7 +53,7 @@ The demo thesis, in the team's own words: *"We gave our AI business zero dollars - Real settlement transactions on Arc testnet, with block numbers and explorer links (`docs/CP2-SUBMISSION.md`, `docs/addresses.md`). Two jobs have run the full on-chain lifecycle: funded, worked, delivered, accepted, settled. - Live contract reads: pool TVL (~20.02 USDC at time of writing), advance rate, utilisation, fees accrued. -- **Absences that must never be fabricated:** no x402 purchase has settled; no spine run has been logged (`docs/spine-runs/` holds only `.gitkeep`); the compliance screen is a labelled stub returning `not-screened`; discovery is a local TF-IDF ranker, not the Circle Agent Marketplace; USYC is a mock strategy. These are documented as honest gaps and the interface must not contradict them. +- **Absences that must never be fabricated:** the Circle Gateway facilitator has never run against the live service (settlement so far is self-facilitated); the compliance screen is a labelled stub returning `not-screened`; discovery is a local TF-IDF ranker, not the Circle Agent Marketplace; USYC is a mock strategy. These are documented as honest gaps and the interface must not contradict them. ## Product Principles diff --git a/dashboard/app/page.tsx b/dashboard/app/page.tsx index fcbf63c..edcf085 100644 --- a/dashboard/app/page.tsx +++ b/dashboard/app/page.tsx @@ -188,10 +188,12 @@ export default function LandingPage() { What is not proven yet

- No agent purchase has settled through the x402 payment path, no end-to-end run has been - logged, and compliance screening is a labelled stub that fabricates no result. A product - about verifiability should be easiest to check where it is weakest, so the full list is - on the audit page beside the evidence. + A 0.04 USDC agent purchase has settled end to end on Arc through the x402 path, + self-facilitated — its transaction is on the audit page. What is still not proven: the + Circle Gateway facilitator path is built and has never run against the live service, and + compliance screening is a labelled stub that fabricates no result. A product about + verifiability should be easiest to check where it is weakest, so the full list is on the + audit page beside the evidence.

Read the gaps diff --git a/docs/CIRCLE-FEEDBACK.md b/docs/CIRCLE-FEEDBACK.md index 5632573..59ce88b 100644 --- a/docs/CIRCLE-FEEDBACK.md +++ b/docs/CIRCLE-FEEDBACK.md @@ -115,15 +115,21 @@ itself the feedback. loop was the easy part; the settlement onboarding is where we stalled. The broadcast path is built — `sidecar/src/facilitator.ts` (landed 2 Aug) calls Circle's Gateway `settle` endpoint and reads `CIRCLE_API_KEY` — but it has **never run against the live Circle service**. It is -dormant without a key, so our seller reports `settlement: NOT_BROADCAST` and **no x402 purchase -has ever settled**; the loop is cryptographically end to end and financially a dry run, stated -plainly in `docs/CP2-SUBMISSION.md` and `docs/PRD.md` §0.1. Two honest consequences: the wire +dormant without a key, so with Gateway as the configured facilitator the seller reports +`settlement: NOT_BROADCAST` and **the Gateway path has never settled a payment**. We reached +SETTLED a different way — **self-facilitating the EIP-3009 authorization** (broadcasting +`transferWithAuthorization` directly, no Gateway): a real 0.04 USDC x402 payment settled on Arc +(`docs/spine-runs/2026-08-08-first-x402-settlement.md`). So the loop is now end to end both +cryptographically and financially; what has never run is the Gateway integration specifically, +stated plainly in `docs/CP2-SUBMISSION.md` and `docs/PRD.md` §0.1. Two honest consequences: the wire contract we coded against Circle's documented interface — the request/response field names — is an **assumption until that first live call**, which is the actual test; and a *green CI check on the facilitator path is not evidence the broadcast works*, because that path has never touched -the real endpoint. Getting to a live settlement needs a human-gated setup we could not complete +the real endpoint. Getting to a live *Gateway* settlement needs a human-gated setup we could not complete in the timebox: a Circle account, terms acceptance, an API key, a funded Agent Wallet, and a -spend policy (`docs/V3-CIRCLE-SETUP.md`). Two smaller frictions compound it: the protocol ships +spend policy (`docs/V3-CIRCLE-SETUP.md`). **x402 settlement itself required none of that** — an +EIP-3009 authorization is a bearer instrument, so we broadcast it ourselves; the gate is on +Gateway, not on x402. Two smaller frictions compound it: the protocol ships in **two coexisting transport versions** (v1 carries the challenge in the 402 *body*, v2 in a `payment-required` *header*) that a seller must emit both of; and Circle's Gateway facilitator lives at a **distinct endpoint** (`gateway-api-testnet.circle.com/gateway/v1/x402/*`) separate diff --git a/docs/CP2-SUBMISSION.md b/docs/CP2-SUBMISSION.md index a7a7442..2e09d52 100644 --- a/docs/CP2-SUBMISSION.md +++ b/docs/CP2-SUBMISSION.md @@ -110,9 +110,10 @@ movement and the hashes that anchor it. - **USDC** — escrow, advances, waterfall settlement, agent purchases - **x402 nanopayments** — agents pay per-call for data through the sidecar, under deterministic per-category spending caps with human approval escalation -- **Circle facilitator** — the sidecar calls Circle's Gateway x402 `verify` and `settle` - endpoints, and the endpoints themselves are contract-asserted in CI (AT-18). **No x402 payment - has settled on chain yet**; see gaps. +- **Circle facilitator** — the sidecar's Gateway client calls Circle's Gateway x402 `verify` and + `settle` endpoints, and the endpoints themselves are contract-asserted in CI (AT-18). **A 0.04 + USDC x402 payment has settled on chain — self-facilitated, not through Gateway; the Gateway + `settle` call is built and has never run against the live service. See gaps.** - **USYC** — idle-float yield strategy (currently a mock strategy; see gaps) ### Both tracks, one system @@ -149,14 +150,18 @@ We would rather state these than have them found. expense, but it is not yet joined to the daemon's own purchase provenance — so an expense recorded via CLI surfaces correctly as outside-policy in the invoice. Settlement ordering is enforced; expense origination is not yet fully attested. -- **No x402 purchase has settled.** This is the honest state of the agent-payment leg. The full - 402 → sign → retry → 200 loop is real and runs end to end, with the EIP-3009 authorization - signed by a real key and verified by the seller. What has never happened is the broadcast: the - Circle facilitator client was written on 2 Aug 2026 (`sidecar/src/facilitator.ts`) and has - never run against the live service, because that needs a Circle account and API key we do not - have yet. Without a key the seller reports `settlement: NOT_BROADCAST` and the fixture capture - refuses to write evidence. So the loop is cryptographically end-to-end and financially a dry - run, and the settlement code is unproven rather than proven. +- **A 0.04 USDC x402 purchase has settled, self-facilitated. The Circle Gateway path is built and + has never run.** Two independent facts. The full 402 → sign → retry → 200 loop is real and runs + end to end, with the EIP-3009 authorization signed by a real key and verified by the seller — + and that authorization was then **broadcast directly** (`transferWithAuthorization`), settling + 0.04 USDC operator → seller on Arc ([`0x0d39b5…dccc`](https://testnet.arcscan.app/tx/0x0d39b5738f7042ae82ae0a17f24474e67c27db0cd837b791c112f8d264b6dccc); + `docs/spine-runs/2026-08-08-first-x402-settlement.md`). Settlement did not require Gateway. + Separately, the Circle Gateway facilitator client (written 2 Aug, `sidecar/src/facilitator.ts`) + has never run against the live service — that path needs a Circle account, key, and Gateway + allowlisting we do not have. A self-facilitated settlement is marked as such (`self:` facilitator + markers), and the fixture capture (AT-18) refuses it as *Circle* evidence. So the loop is now + end to end both cryptographically and financially; what stays unexercised is the Gateway path + specifically, not settlement itself. - **No end-to-end spine run has been logged.** `docs/spine-runs/` is empty. The harness exists (`scripts/spine_run`) and can now run against the pool we already hold (`--scaled`), but the claim "green on N consecutive days" is not one we can make. diff --git a/docs/HARD-QUESTIONS.md b/docs/HARD-QUESTIONS.md index 535751e..c157810 100644 --- a/docs/HARD-QUESTIONS.md +++ b/docs/HARD-QUESTIONS.md @@ -134,9 +134,12 @@ you are asked. ## 5. Can we see it run? -Be straight about the state of this. As of 3 Aug 2026 no end-to-end spine run has been logged -(`docs/spine-runs/` holds only `.gitkeep`), and no agent purchase has settled through x402 — the -facilitator client exists and has never run against Circle, so the seller records `NOT_BROADCAST`. +Be straight about the state of this. As of 8 Aug 2026 spine runs have been logged (`docs/spine-runs/`; +first automated settlement 2026-08-07), and a 0.04 USDC agent purchase has settled through x402, +self-facilitated (`docs/spine-runs/2026-08-08-first-x402-settlement.md`) — the authorization was +broadcast directly, not through Gateway. Separately, the Circle Gateway facilitator client is built +and has never run against Circle's live service; under that facilitator with no key the seller +records `NOT_BROADCAST`. Two independent facts: settlement happened, and it did not use Gateway. What *has* happened, on chain, is the full money lifecycle for two jobs: fund → advance → expense → delivery → accept → waterfall → rate rise. What has not happened is the agent-payment leg settling diff --git a/docs/PRD.md b/docs/PRD.md index 28bfbbf..e605dda 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -56,7 +56,7 @@ a **committed artifact**. Code cannot satisfy those. A green CI run is not evide | V1 real four-cent purchase + committed fixture | V3, then a live capture. `capture-v1-fixture.ts` refuses to write a fixture without a real transaction hash from Circle's endpoints (refusals 5–7). | | V7 / V10 / V11 "renders live" | One green spine run with a human watching. | | V12 reset → spine → reset → spine | Two consecutive green runs. | -| Spine runs (schedule, §16) | **First automated settlement logged 2026-08-07, verdict UNVERIFIED** — the waterfall settled on chain and only the x402 beat is unproven (settlement rests at `NOT_BROADCAST`, pending a funded Circle Gateway deposit). **Three prior FAIL runs the same day** are recorded in `docs/spine-runs/spine-runs.tsv` and kept deliberately: the live runs surfaced what CI could not — the missing `startWork`/`submitDelivery` caller, and an operator-owned pool. **No daily cadence exists**: the Wed 29 Jul gate was missed; runs are on-demand, not scheduled. | +| Spine runs (schedule, §16) | **First automated settlement logged 2026-08-07, verdict UNVERIFIED** — the waterfall settled on chain; the x402 beat rested at `NOT_BROADCAST` in that run (Gateway facilitator, no key). **The x402 beat has since settled separately**: a real 0.04 USDC x402 payment settled on Arc 2026-08-08, **self-facilitated** — broadcasting the EIP-3009 authorization directly, not through Circle Gateway, which is built and has never run against the live service (`docs/spine-runs/2026-08-08-first-x402-settlement.md`). **Three prior FAIL runs the same day** are recorded in `docs/spine-runs/spine-runs.tsv` and kept deliberately: the live runs surfaced what CI could not — the missing `startWork`/`submitDelivery` caller, and an operator-owned pool. **No daily cadence exists**: the Wed 29 Jul gate was missed; runs are on-demand, not scheduled. | **Known gaps carried deliberately** @@ -317,7 +317,7 @@ Worker need = a fuzzy description, vectorised and similarity-matched against a s |---|---| | Brain (control plane) | Circle CLI + Circle Skills | | Funding agent | Circle Agent Wallet spend-policy (outer guard) + deterministic policy engine (inner) | -| DD-worker discovery + purchases | Circle Agent Marketplace *(discovery: **roadmap** — the shipped code embedding-matches against a local stand-in catalog, our own V2 paid API, behind a `Catalog` seam built for the marketplace; no marketplace API is integrated today)* + **Circle's Gateway x402 facilitator** — **never** the generic x402.org facilitator or another vendor's (Coinbase/Stripe/Cloudflare also implement x402; judges score *Circle's* tools). *Integration detail, stated precisely because it is judge-facing: we call Circle's documented Gateway x402 `verify` and `settle` endpoints directly over HTTP (`sidecar/src/facilitator.ts`, endpoints pinned in `circle-facilitator-fixture.ts` and asserted by AT-18). We do **not** depend on the `@circle-fin/x402-batching` package, which earlier drafts of this table named. As of 2 Aug 2026 that client is written but has never run against the live service — it is dormant without `CIRCLE_API_KEY`, and no payment has settled.* | +| DD-worker discovery + purchases | Circle Agent Marketplace *(discovery: **roadmap** — the shipped code embedding-matches against a local stand-in catalog, our own V2 paid API, behind a `Catalog` seam built for the marketplace; no marketplace API is integrated today)* + **Circle's Gateway x402 facilitator** — **never** the generic x402.org facilitator or another vendor's (Coinbase/Stripe/Cloudflare also implement x402; judges score *Circle's* tools). *Integration detail, stated precisely because it is judge-facing: we call Circle's documented Gateway x402 `verify` and `settle` endpoints directly over HTTP (`sidecar/src/facilitator.ts`, endpoints pinned in `circle-facilitator-fixture.ts` and asserted by AT-18). We do **not** depend on the `@circle-fin/x402-batching` package, which earlier drafts of this table named. As of 2 Aug 2026 that client is written but has never run against the live service — it is dormant without `CIRCLE_API_KEY`. Settlement itself is not blocked on it: a 0.04 USDC x402 payment has settled on Arc self-facilitated (2026-08-08), broadcasting the authorization directly; the Gateway client specifically remains unexercised.* | | Compliance step | Circle Compliance Engine *(**seam only — not integrated**. The shipped screen is `worker.StubCompliance` (`daemon/internal/worker/worker.go:178`), wired at `daemon/cmd/snapfall/main.go:759`, which fabricates nothing: decision `not-screened`, provider `stub`, `stub: true`. No Compliance Engine client or endpoint exists in the tree. What IS real: the screen runs as a task step, the report carries "evidence, not a guarantee", and QA reads the stub flag and attaches a disclosure note rather than passing it silently. Unlike the facilitator this is not credential-blocked — no client has been written.)* | | Billing agent | Arc Explorer + Gateway settlement records | | JobVault / FloatPool / Waterfall | **Ours** — Arc supplies sub-second finality + USDC-native gas; the primitive is our own engineering | @@ -470,7 +470,7 @@ AT-01..15 remain in force from the v4 SRS annex, with AT-10 extended (restart re - Restart recovery demonstrated, including Brain. - Secret audit clean. - Full reset rehearsed twice (reset ×2). -- Spine runs: first automated settlement 2026-08-07, verdict UNVERIFIED (the waterfall settled on chain; only the x402 beat is unproven). Three prior FAIL runs the same day are kept in `docs/spine-runs/spine-runs.tsv` — a live run found what CI could not. No daily cadence: on-demand, not scheduled. See §0.1. +- Spine runs: first automated settlement 2026-08-07, verdict UNVERIFIED (waterfall settled on chain; the x402 beat rested at `NOT_BROADCAST` then). The x402 beat has since settled on Arc 2026-08-08, self-facilitated (not through Gateway); the Circle Gateway path is built and has never run. Three prior FAIL runs the same day are kept in `docs/spine-runs/spine-runs.tsv` — a live run found what CI could not. No daily cadence: on-demand, not scheduled. See §0.1. - Recording integrity: replay of real runs, live transactions, disclosed caption. Record Thu; edit + deck + README Fri (**Teammate 2 owns final README/deck review**). - Submission Sat 8 Aug, evening IST; all links verified incognito. Sun 9 Aug = AoE contingency only, not build time. diff --git a/docs/SPINE-RUNS.md b/docs/SPINE-RUNS.md index 4bc38ce..6f3313d 100644 --- a/docs/SPINE-RUNS.md +++ b/docs/SPINE-RUNS.md @@ -10,7 +10,7 @@ that no unit test can close, because the clause names a live run: | Task | Done when (WORK-SPLIT §2) | Closed by | |---|---|---| -| V1 | "a real four-cent purchase completes on testnet **and the raw request/response pair is committed as a fixture**" | **NOT closed by any beat yet.** Beat 3 proves the x402 handshake, the EIP-3009 signature and the resource delivery are real, and stops at UNVERIFIED because `seller.ts:217` returns `settlement: NOT_BROADCAST`, so nothing reaches Arc and `SETTLED` is unreachable (H3 section 8). Both halves of the clause need the facilitator broadcast plus a funded wallet: see `docs/V3-CIRCLE-SETUP.md`. | +| V1 | "a real four-cent purchase completes on testnet **and the raw request/response pair is committed as a fixture**" | **Settlement half proven 2026-08-08; the Circle-specific fixture half stays open.** Beat 3 proves the x402 handshake, the EIP-3009 signature and the resource delivery are real. It rested at UNVERIFIED in the 2026-08-07 run because, under the (unconfigured) Gateway facilitator, `seller.ts` returned `settlement: NOT_BROADCAST`. `SETTLED` is **not** unreachable: a real 0.04 USDC x402 payment has since settled on Arc, **self-facilitated** — broadcasting the EIP-3009 authorization directly, no Gateway (tx [`0x0d39b5…dccc`](https://testnet.arcscan.app/tx/0x0d39b5738f7042ae82ae0a17f24474e67c27db0cd837b791c112f8d264b6dccc); `docs/spine-runs/2026-08-08-first-x402-settlement.md`). The open half is the Circle-specific one: the committed fixture (`capture-v1-fixture`, AT-18) requires Circle's endpoints, and the Gateway path is built but has never run against the live service (`docs/V3-CIRCLE-SETUP.md`). | | V7 | "a full spine run renders every state transition live" | a human watching during a green run | | V9 | "clicking Accept produces the on-chain settlement tx" | beat 6, from JobVault status on chain | | V10 | "a live spine run animates correctly with zero manual triggers" | a human watching during a green run | diff --git a/docs/handshakes/H3-sidecar-api.md b/docs/handshakes/H3-sidecar-api.md index db925b8..d613f2f 100644 --- a/docs/handshakes/H3-sidecar-api.md +++ b/docs/handshakes/H3-sidecar-api.md @@ -218,12 +218,16 @@ codes to `RECONCILING`, so the distinction holds the moment a key exists.) | 401 | `UNAUTHENTICATED` | bad bearer token | | 404 | `PAYMENT_NOT_FOUND` | no record for `paymentId` | -`status` is safe to poll. **Reality as of 2 Aug 2026:** the facilitator broadcast is wired but -dormant without `CIRCLE_API_KEY`, and no payment has settled yet. With no key, `DELIVERED` is the -committed-final success state (§5, §7) and consumers MUST NOT treat `SETTLED` as reachable. -With a key, `SETTLED` becomes reachable — but the reconciliation transitions -(`RECONCILING → SETTLED|FAILED`) still need a settlement-status read the client does not have, so -an unconfirmed settlement is reconciled by an operator rather than automatically. +`status` is safe to poll. **Reality of the Gateway facilitator path:** the Gateway broadcast is +wired but dormant without `CIRCLE_API_KEY`. With no key, `DELIVERED` is the committed-final success +state (§5, §7) and consumers MUST NOT treat `SETTLED` as reachable *through Gateway*. With a key, +`SETTLED` becomes reachable — but the reconciliation transitions (`RECONCILING → SETTLED|FAILED`) +still need a settlement-status read the client does not have, so an unconfirmed settlement is +reconciled by an operator rather than automatically. **Update (2026-08-08):** a payment **has** +settled — through the *self-facilitator* (`SNAPFALL_SELF_FACILITATOR=1`), a different, permissionless +path that broadcasts the EIP-3009 authorization directly and reaches `SETTLED` with a real tx hash +(`docs/spine-runs/2026-08-08-first-x402-settlement.md`). The Gateway-path statements above are +unchanged: that facilitator specifically has still never run. ### 2.4 Stable error-code enum (frozen) `UNAUTHENTICATED`, `BAD_REQUEST`, `RESOURCE_NOT_FOUND`, `NO_MATCHING_NETWORK`, `CHALLENGE_UNAVAILABLE`, `UPSTREAM_UNREACHABLE`, `PAYMENT_IN_PROGRESS`, `APPROVAL_TOKEN_INVALID`, `INTENT_HASH_MISMATCH`, `INTENT_NOT_APPROVED`, `APPROVAL_EXPIRED`, `APPROVED_AMOUNT_MISMATCH`, `MERCHANT_CHANGED`, `ASSET_CHANGED`, `PRICE_CHANGED`, `PRICE_EXCEEDS_RESERVED`, `PAYMENT_REJECTED`, `FACILITATOR_ERROR`, `PAYMENT_NOT_FOUND`, `INTERNAL`. @@ -363,10 +367,11 @@ fact: with no key the sidecar behaves exactly as the dry run described below. - **SIGNED** — EIP-3009 authorization signed once (deterministic `authNonce`). A key has been used exactly once. **First persisted state.** - **SIGNED** — EIP-3009 authorization signed once (deterministic `authNonce`). A key has been used exactly once. - **SUBMITTED** — `X-PAYMENT` sent; awaiting seller/facilitator response. -- **DELIVERED** — seller returned `200` with `{data, receipt}`; goods in hand. Without a Circle - key `settlement == "NOT_BROADCAST"` and **this is the committed-final success state** (see §7). - With a key the seller only returns `200` once the facilitator has confirmed settlement, so - `settlement` carries the transaction hash. +- **DELIVERED** — seller returned `200` with `{data, receipt}`; goods in hand. With no settlement + facilitator configured (no Circle key, `SNAPFALL_SELF_FACILITATOR` unset) `settlement == + "NOT_BROADCAST"` and **this is the committed-final success state** (see §7). With a facilitator — + the Circle key, or the self-facilitator that broadcasts the authorization directly — the seller + only returns `200` once settlement is confirmed, so `settlement` carries the transaction hash. - **RECONCILING** — a transport failure occurred **after** submit; the authorization may or may not have settled. Non-terminal; requires facilitator reconciliation before any budget release. Reachable once a key is configured. **Both** `FACILITATOR_ERROR` and `PAYMENT_REJECTED` route @@ -469,7 +474,7 @@ This is exactly the AT-03 (`INTENT_NOT_APPROVED`, immediate full release, no sig A signed EIP-3009 `transferWithAuthorization` is a bearer instrument: once `pay` hands the `X-PAYMENT` to the resource/seller host, that host can submit it to any facilitator/relayer. H3 at the buyer layer **cannot** constrain which facilitator settles. Two consequences the spec states plainly rather than papering over: 1. **"Circle-facilitator-only" is enforced by the OUTER guard**, not H3: Circle's Agent-Wallet spend policy (payee/amount allowlist at the wallet layer) is what actually binds settlement to the approved merchant and Circle's rails. H3's contribution is the pre-sign payee-equality check (§2.2 step 8), which ensures the treasury only ever signs an authorization payable to the approved `intent.merchant`. Fixing the AT-05 payee hole is therefore a **prerequisite** for this invariant to mean anything. -2. **Facilitator reconciliation is unimplemented in the dry run.** `DELIVERED → SETTLED` and `RECONCILING → SETTLED|FAILED` require a real broadcast; until it is wired, `settlement` stays `NOT_BROADCAST` and `SETTLED` is unreachable. §2.3/§5 mark this; consumers must not treat `SETTLED` as reachable yet. +2. **Facilitator reconciliation is unimplemented in the *Gateway* dry run.** `DELIVERED → SETTLED` and `RECONCILING → SETTLED|FAILED` require a real broadcast. With no facilitator configured, `settlement` stays `NOT_BROADCAST`. This is not a claim that `SETTLED` is out of reach: the self-facilitator (`SNAPFALL_SELF_FACILITATOR=1`) broadcasts the authorization directly and a real payment has reached `SETTLED` that way (`docs/spine-runs/2026-08-08-first-x402-settlement.md`) — consistent with point 1, since a bearer authorization can be settled by *any* facilitator. What remains unexercised is Gateway-confirmed reconciliation specifically: the Circle path is built and has never run against the live service. This matches the two-layer authorization model in §0: inner guard (H3 + policy + approval) proposes and signs only approved terms; outer guard (Agent-Wallet policy) is the settlement-route and payee enforcer. diff --git a/docs/spine-runs/2026-08-07-first-automated-settlement.md b/docs/spine-runs/2026-08-07-first-automated-settlement.md index 165fab6..6efc263 100644 --- a/docs/spine-runs/2026-08-07-first-automated-settlement.md +++ b/docs/spine-runs/2026-08-07-first-automated-settlement.md @@ -81,8 +81,13 @@ accepted job and does not depend on the amount. It does **not** close the done-when clauses that name PRD figures or a settled purchase: -- **V1** (a real four-cent purchase that *settles*) stays open — beats 3/5 are `NOT_BROADCAST` - without a Circle facilitator key (`docs/V3-CIRCLE-SETUP.md`). +- **V1** (a real four-cent purchase that *settles*) was open at this run — beats 3/5 rested at + `NOT_BROADCAST`. **Update (2026-08-08):** the settlement half is now proven — a real 0.04 USDC + x402 payment settled on Arc, self-facilitated (tx [`0x0d39b5…dccc`](https://testnet.arcscan.app/tx/0x0d39b5738f7042ae82ae0a17f24474e67c27db0cd837b791c112f8d264b6dccc), + seller `0 → 40000` atomic); see [`2026-08-08-first-x402-settlement.md`](2026-08-08-first-x402-settlement.md). + That settlement was self-facilitated, not through Circle Gateway — the Gateway path is built and + has never run against the live service, so the Circle-specific V1 fixture (`capture-v1-fixture`, + which requires Circle's endpoints) stays open. - **V7 / V10 / V11 / V12** name the 25.00 PRD figures or two consecutive clean runs; this is a single 0.60 run, logged in the scale column so it is never counted as a PRD-figure proof. diff --git a/docs/spine-runs/2026-08-08-first-x402-settlement.md b/docs/spine-runs/2026-08-08-first-x402-settlement.md new file mode 100644 index 0000000..1993fa3 --- /dev/null +++ b/docs/spine-runs/2026-08-08-first-x402-settlement.md @@ -0,0 +1,65 @@ +# First x402 settlement — 8 Aug 2026 + +The x402 agent-payment leg reached **SETTLED** on Arc testnet for the first time. Until now it +rested at `NOT_BROADCAST`: the handshake was real, but nothing was ever submitted, so no payment +had settled and the 2026-08-07 spine run's verdict was UNVERIFIED for exactly that beat. This +closes that gap. + +## What settled + +| | | +| --- | --- | +| Transaction | [`0x0d39b5738f7042ae82ae0a17f24474e67c27db0cd837b791c112f8d264b6dccc`](https://testnet.arcscan.app/tx/0x0d39b5738f7042ae82ae0a17f24474e67c27db0cd837b791c112f8d264b6dccc) | +| Status / block / gas | success (`1`) · block 55818900 · gas 111,821 | +| Method call | `USDC.transferWithAuthorization` (EIP-3009, `v,r,s` overload) on `0x3600…0000` | +| Payer (`from`) | `0x99B723eD097721036C08dd9DEe307286Df3A792D` (operator) | +| Payee (`to`) | `0xb30AD5579e9d2c193ACFaE243F5915Bbf0F1869E` (seller) | +| Value | `40000` atomic — **0.04 USDC** | +| Seller balance before → after | **`0` → `40000`** atomic, read from chain before and after | +| Receipt `facilitator` field | `{"verify":"self:eip3009-local-recover","settle":"self:transferWithAuthorization@0x3600…0000"}` | + +**The payer and payee are two wallets the operator controls** — the seller is a fresh, receive-only +address minted for this. That does not weaken the proof of the *mechanism*: the point is that a +signed EIP-3009 authorization was broadcast and USDC moved on chain between two distinct addresses, +which is what `transferWithAuthorization` settling looks like regardless of who owns the wallets. +A zero-to-a-real-number balance change is the cleanest evidence the transfer executed. + +## The method: simulate, then broadcast the same payload + +The operator signed an `operator → seller` EIP-3009 authorization, then ran +`transferWithAuthorization` as an `eth_call` (a read — no transaction). Only after that simulation +came back clean was the **same signed payload** broadcast through the real self-facilitator +(`sidecar/src/self-facilitator.ts`). Simulating the exact payload first means a clean simulation +and a reverting send can only differ if something changed on chain between the two — and it did +not. Reproducible via `x402-simulate.ts` / `x402-broadcast.ts`. + +## The bug the live run surfaced (the instructive part) + +Settlement was blocked by a signature bug that **a green test suite could not have caught**, and +it was hidden precisely because settlement had never run. + +We signed `transferWithAuthorization` with EIP-712 domain name **`"USD Coin"`** — Ethereum +*mainnet* USDC's name. Arc's precompile uses **`"USDC"`**. The mismatch is invisible during the +handshake: the buyer signs with the seller's advertised name and the seller verifies locally with +the same name, so 402 → sign → verify → 200 passes. But `transferWithAuthorization` recovers the +signer against the precompile's *own* domain at settle — so a `"USD Coin"` signature recovers to a +different address, and every broadcast we could have made would have reverted. On Circle Gateway +or a self-facilitator alike. + +`NOT_BROADCAST` was reading as *"the Circle integration isn't wired yet."* It was also hiding a bug +that would have reverted the first real settlement on any facilitator. Confirmed by computing the +domain separator for each candidate name/version and matching it against the deployed +`DOMAIN_SEPARATOR` — only `"USDC"/"2"` matches (`sidecar/src/usdc-domain-chain-test.ts` re-derives +it from chain). **This is the second time this session a live run found what CI could not** — the +first was the missing `startWork`/`submitDelivery` caller in the 2026-08-07 run. + +## What this proves, and what it does not + +- **Proven, end to end on Arc:** the x402 handshake, the EIP-3009 signature over the correct USDC + domain, and on-chain settlement. +- **Not proven:** the Circle Gateway integration. Settlement here was **self-facilitated** — the + authorization broadcast directly, not through Gateway. The `CircleFacilitator` client is built + and has never run against Circle's live service. The two are independent facts: a payment has + settled; the Gateway path has not run. The receipt's `self:` facilitator markers make a + self-facilitated settlement impossible to mistake for Circle evidence, and `capture-v1-fixture` + (AT-18) refuses it as such. diff --git a/docs/spine-runs/spine-runs.tsv b/docs/spine-runs/spine-runs.tsv index 89034a0..4d8dcda 100644 --- a/docs/spine-runs/spine-runs.tsv +++ b/docs/spine-runs/spine-runs.tsv @@ -4,3 +4,4 @@ 2026-08-07T06:14:17Z 695d1a8+dirty FAIL unknown 1-FUND - seed_demo failed; nothing was escrowed, so no later beat can mean anything 2026-08-07T06:30:45Z 695d1a8+dirty FAIL price=1.00 3-SPEND 0xd37ec9bb2ab90be9c3d911634082b61ad104571537a923f00a7aad8bb5675fc5 the 0.04 payment rests at DELIVERED, not SETTLED (id pay_f962cb080f58ae5d): seller.ts:217 returns settlement NOT_BROADCAST, so the x402 handshake and signature are real but nothing reached Arc. V1's clause stays open until a facilitator broadcast is wired; see docs/V3-CIRCLE-SETUP.md 2026-08-07T07:12:03Z 695d1a8+dirty UNVERIFIED price=0.60 3-SPEND 0xb02580600dbcb1025b892f6251d3c91f36ff54fb4319df4b046b167c10c20bc8 the 0.04 payment rests at DELIVERED, not SETTLED (id pay_f962cb080f58ae5d): seller.ts:217 returns settlement NOT_BROADCAST, so the x402 handshake and signature are real but nothing reached Arc. V1's clause stays open until a facilitator broadcast is wired; see docs/V3-CIRCLE-SETUP.md +# 2026-08-08: the x402 gap that left the runs above at NOT_BROADCAST is closed. First x402 settlement on Arc, self-facilitated (NOT Circle Gateway): tx 0x0d39b5738f7042ae82ae0a17f24474e67c27db0cd837b791c112f8d264b6dccc, seller 0->40000 atomic. Standalone settlement, not a spine_run row. See 2026-08-08-first-x402-settlement.md. The Circle Gateway path is built and has never run against the live service.