Skip to content

feat(testkit): offline settlement harness, restart durability, and fixture capture (B03) - #14

Merged
selezenart merged 2 commits into
milestone/b02-request-policy-receiptfrom
milestone/b03-live-settlement-harness
Sep 7, 2026
Merged

selezenart merged 2 commits into
milestone/b02-request-policy-receiptfrom
milestone/b03-live-settlement-harness

Conversation

@selezenart

Copy link
Copy Markdown
Collaborator

Summary

Implements B03 — offline-complete and live-ready settlement harness for the
Coder B lane.

Live status is LIVE_NOT_RUN: no Privy application, wallet, policy, or
funded testnet account has been provisioned, because provisioning is a human
action. The packet explicitly closes on its offline criteria; live execution is
tracked as Gate P4 evidence.

Stacked on B02 (PR #12), which is stacked on B01 (PR #11). Review those
first.

Scope and acceptance criteria

  • The change is limited to the stated milestone or issue.

  • Acceptance criteria are listed and satisfied.

  • No unrelated cleanup is included.

  • B03.1 human setup guide — docs/settlement/PROVIDER_SETUP.md. Account
    creation, secret handling, policy attachment, and funding stay with a person;
    only the read-only probe is automatable.

  • B03.2 standalone harness — accepts a frozen intent, persists request
    identity to durable state before the provider call, emits only sanitized
    evidence.

  • B03.3 policy negative suite — every denial family asserted against a
    deterministic broadcast counter.

  • B03.4 allowed settlement — offline path only. LIVE_NOT_RUN.

  • B03.5 live-to-fixture conversion — allowlisted capture, redaction, and a
    round-trip proving fixtures reproduce the original verifier result.

Product and security invariants

  • Tenant isolation remains fail-closed.
  • Sponsor authorization, auditability, and daily caps remain enforced where applicable.
  • Recipients cannot modify sponsor controls or access sponsor-only data.
  • No secret, token, production identifier, or personal data is committed.
  • Any non-applicable invariant is explained below.

Invariant notes:

Tenant isolation and sponsor-control rows are not applicable: no durable
product state or tenant boundary exists in this diff. What is proven here:

  • Every denial family produces zero external broadcasts, asserted against a
    counter rather than the returned enum. A denial that returned the right value
    while still broadcasting has not denied anything.
  • Exactly one broadcast across duplicate delivery, ten sequential retries,
    ten parallel workers sharing durable state, and ten restarted workers.
  • An ambiguous outcome grants no fresh submission right, including after an
    outcome that was never learned. That retry is the double payment the product
    exists to prevent.
  • Ambiguous scenarios deliberately increment the broadcast counter.
    Counting them as non-broadcasts would understate exposure.

Validation

packages/arc-adapter        : lint, typecheck, build PASS; 146 tests PASS
packages/privy-adapter      : lint, typecheck, build PASS;  66 tests PASS
packages/testkit-settlement : lint, typecheck, build PASS;  51 tests PASS
total                       : 263 tests PASS
markdownlint-cli2           : 0 errors over 57 files
npm run probe (no config)   : fails closed, exit 1, names the missing variable

Independent review evidence

Two FreePi reviews, free-pi-cli 0.2.19 on glm-5.3-flash.

First review — tree 5aacf1713f4ea260cdab0ba5843b1b7e281cfce5 — VERDICT: FAIL.
Two blocking findings, both correct:

  1. PROVIDER_SETUP.md §7 told a human to run npm run check and called it the
    readiness probe. It is not: that runs unit tests against stubs. A human
    following the guide would have believed their setup was verified when
    nothing had contacted it.
  2. AttemptLog was in-memory, so B03.2's "persist identity before submission"
    was unmet and the at-most-once claims were an artifact of a shared process
    rather than a demonstration of durability.

Fixes: added createViemProbe and an npm run probe command that actually
contacts the configured endpoint read-only; added FileAttemptStore writing
through to JSON under an ignored tmp/ directory, with restart tests that
build a fresh store from the same file as a restarted process would.

Second review — tree 9927eb51246f1c69e49f5cdc60677b0063a28993 — RECHECK_PASS,
no blocking findings.
Asked specifically to judge whether the fixes were
genuine rather than papered over, whether the probe can mutate or leak, and
whether the restart tests prove durability or merely re-read a file.

Note on evidence quality: the reviewer's literal VERDICT: line was lost to
terminal viewport truncation in the second run; the captured evidence is the
terminal RECHECK_PASS token and an empty blocking-findings section.

Risk and rollback

  • CI still does not lint or test these packages. The ESLint and TypeScript
    job finds no root pnpm-lock.yaml and skips. All 263 tests are local only.
  • npm versus pnpm remains unresolved; the three package-local lockfiles
    should be dropped when Coder A scaffolds the pnpm workspace root.
  • Simulators prove adapter logic, not provider behaviour. The Privy policy
    shape is modelled from documentation and has never been exercised against a
    live tenant, and Arc receipt shapes are assumed. Per the
    sponsor-qualification skill, no Privy or Arc claim may be made from
    fixtures alone; both remain NOT VERIFIED until LIVE_EVIDENCE.md records a
    sanitized live transaction.
  • FileAttemptStore is a test stand-in for the PostgreSQL state Coder A owns,
    not a production store.

Rollback: additive packages and docs on a short-lived branch. Revert or close.

Human merge

  • A human owner has reviewed the evidence and will perform the merge.

Implements B03 offline closure for the Coder B lane. Live execution is not
performed and is recorded as LIVE_NOT_RUN in docs/settlement/LIVE_EVIDENCE.md,
which the packet explicitly permits.

Harness (B03.2). Runs the whole adapter workflow against simulated providers
with no network and no credential. Request identity is persisted before the
provider call rather than after: the window between that write and the
provider returning is exactly where a crash produces UNKNOWN, and the record is
what makes it recoverable.

Negative suite (B03.3). Every denial family asserts against a broadcast
counter, not just the returned enum, because a denial that returned the right
value while still broadcasting has not denied anything. Duplicate delivery, ten
sequential retries, and ten parallel workers sharing durable state each produce
exactly one broadcast. An ambiguous outcome does not grant a fresh submission
right, so the dangerous retry after a possible payment cannot happen. Ambiguous
scenarios deliberately increment the broadcast counter: counting them as
non-broadcasts would understate exposure.

Fixture capture (B03.5). Allowlists the fields needed to reproduce a decision
so a new provider field cannot silently start appearing in committed fixtures,
then redacts what survives and asserts no secrets at capture time.

Fixes a redaction bug the round-trip test caught. The rule treating any
32-byte hex value as secret-shaped also matched transaction hashes, block
hashes, event topics, ABI-encoded words, and payload fingerprints, so
sanitizing a receipt destroyed the very evidence it was captured to preserve
and the fixture no longer confirmed the settlement. Shape alone cannot separate
a private key from a keccak hash, so the rule now applies only outside named
hash-bearing fields; key material is still caught by field name regardless of
shape, and a credential hidden inside a hash-bearing field is still redacted.

Also adds the human-run provider setup guide, which keeps account creation,
secret handling, policy mutation, and funding with a person and leaves only the
read-only readiness check automatable.
An independent FreePi review of tree 5aacf17 returned VERDICT: FAIL with two
blocking findings. Both were correct.

1. The setup guide told a human to run `npm run check` in packages/arc-adapter
   and called it "the readiness probe". It is not. That command runs lint,
   typecheck, and unit tests against stubbed endpoints; it proves the probe's
   logic and says nothing about whether a particular operator's endpoint,
   chain, and token are correct. A human following the guide would have
   believed their setup was verified when nothing had contacted it.

   Adds a real read-only probe. createViemProbe implements the RpcProbe
   interface over viem, and `npm run probe` loads configuration from the
   environment, asks the configured endpoint which chain it is actually on,
   checks the USDC address holds bytecode, and prints a per-check report. It
   cannot sign, send, or mutate, and it prints no credential. Section 7 now
   points at it and explains why `npm run check` is not a substitute.

2. AttemptLog was in-memory, so B03.2's requirement to persist request identity
   in ignored test runtime state before submission was not met, and the
   at-most-once claims were an artifact of a shared process rather than a
   demonstration of durability. Restart is a boundary the failure-injection
   skill requires be exercised.

   Adds FileAttemptStore, writing through to JSON under an ignored tmp/
   directory, and moves the store behind an AttemptStore interface. Writes are
   immediate: buffering would reopen the crash window the store exists to
   close. New restart tests build a fresh store from the same file, as a
   restarted process would, and prove a restarted worker is refused a second
   submission right, including after an outcome that was never learned, and
   that ten restarted workers still broadcast exactly once.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
oneshot b6fcd25 Sep 07 2026, 02:49 PM

@selezenart
selezenart merged commit 63af09b into milestone/b02-request-policy-receipt Sep 7, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant