Part of the verifiable supply-chain series — start here: The Bedrij Principle, then Brick #1: Typed Participants and Brick #2: Typed Evidence. The hypothesis under test: a complex economic system can be composed from verifiable state transitions.
A primitive that only works in demos is not a primitive. Before a supply-chain transition contract ever touches a testnet, you want to know exactly which violations it survives — and which it doesn't, yet.
Bricks #1 and #2 built the happy path: typed participants and typed evidence travelling inside the handoff event itself. This brick goes the other direction. Instead of asking "does it work?", we asked "how does it break?" — seven scenarios, each one a violation a real chain will eventually see: undeclared telemetry, a rogue sensor, a missing inspection photo, a forged replay, a receiver dispute, a double settle racing the lock.
This is Brick #3 — the third experimental proof in the Bedrij-series hypothesis, and the first that proves resilience rather than shape: the primitive has a regression contract. Every violation is exercised on an in-memory stack, its expected outcome is asserted, and after an adversarial AI audit found six real High-severity holes, every one of them got its own regression test.
What is a supply-chain scenario matrix?
Short answer: a scenario matrix is a fixed set of end-to-end test scenarios — happy path plus violations — that run a shipment through the full lifecycle (offer → handoff → settle → verify) on an in-memory ledger and executor, then assert the replayed state and advisory flags. Ours is one file, tests/supply-chain/scenario-matrix.test.ts, 18 tests covering seven scenarios S1–S7 plus the audit hardening cases. Each scenario answers one question: if this goes wrong, does the state machine stay honest?
The stack is deliberately boring. makeChain() — a shared fixture from Brick #2's audit — builds a shipment with N hops, typed roles, per-hop evidence, then drives accept → handoff → settle → verify on an InMemoryHopExecutor with deterministic job IDs. No chain, no RPC, no wall clock: the matrix replays the same events a production ledger would store, which is the point — the regression contract covers the projection, the part that turns an append-only log into answers like "who holds custody" and "is this hop paid".
What do the seven scenarios actually prove?
Short answer: S1 proves the happy path end-to-end; S2–S5 prove each advisory flag fires on exactly the violation it names — and only there; S6 proves a receiver can halt the chain by disputing; S7 proves payment is exactly-once under concurrency. Every assertion checks both the write payload and the replayed read model — a scenario that only verifies the call succeeded would prove nothing about the projection.
| Scenario | Violation | Expected outcome |
|---|---|---|
| S1 happy path | none — 3-hop chain, declared device, party-issued telemetry | all hops verified, zero advisory flags |
| S2 temp violation | temperature ref from a source that is neither a hop party nor the declared device |
tempViolation flag — settle still executes (advisory, not enforcement) |
| S3 fake sensor | telemetry-typed ref whose issuer address ≠ declared roles.device — including an issuer claiming type "org"
|
fakeSensor flag; with no declared device the flag stays off (cannot judge → don't guess) |
| S4 missing telemetry | a hop handoff carrying no evidence at all |
missingEvidence flag, chain remains valid |
| S5 GPS anomaly | a gps ref from a non-party, non-device issuer |
gpsAnomaly flag; a party-issued GPS ref stays clean |
| S6 receiver dispute | receiver rejects the handoff |
disputed flag, settleHop refuses while disputed, chain halts at that hop |
| S7 double settle | two concurrent settleHop calls on the same hop |
exactly-once: one executor call, one pay event — the loser queues on the per-hop lock |
Three details in that table matter more than the flags themselves.
First, flags are advisory, never blocking — that's a decision, not an omission. S2's temperature violation does not hold the payment; it records that a violation was asserted. Automatic hold is a dispute-engine problem (a later brick), and pretending v1 enforces it would be a lie printed in our own docs.
Second, S3 and S5 assert absences, not just presences. A scenario that only checks "the flag fired" is half a test — the audit showed exactly why (see below). Each violation scenario also proves the flag does not fire on the clean variant, and S1 genuinely exercises all five flags by giving the chain a declared device and real telemetry.
Third, S7 proves contention, not just outcome. A naive exactly-once test asserts settled.length === 1 after Promise.all — but if the lock never actually engaged, the test passes vacuously. Ours uses a deferred executor: while the winner is in-flight, we assert the loser is queued (executor unsettled, ledger unchanged), then release and assert one settle + one pay event.
What did the adversarial audit break?
Short answer: we ran the whole matrix through an external adversarial AI audit before calling it done — verdict REQUEST CHANGES: 0 Critical, 6 High, 7 Medium. The Highs were not style complaints: the projection trusted forged events, an "inconclusive" verdict silently promoted a hop to verified, verifyHop had no evaluator check or lock, and a concurrent createShipment could mint orphan funded jobs. All six are fixed with regression tests; three Mediums were deliberately accepted and documented instead of silently patched.
The deepest finding — H-1 — is the one worth remembering. The replay projection lifted state from any event with a well-formed payload: a pay event forged at sequence 41 for a non-plan hop index could mint a phantom paid hop, which then unlocked the next handoff. A fake handoff could even advance a hop's status without a single real delivery. The fix splits the projection into a two-pass, canonical-sequence replay: the plan binds to the work_offer at seq === 1 (first writer wins — later offers can't redirect the payout target), and a handoff/pay/verify event lifts state only if it lands in the canonical sequence slot for its hop and stage, has an integer in-plan hop index, and is bound to the current shipmentId. A forged event now lifts nothing — not even status.
The pipeline the fix leaves behind (diagram below): the plan binds once — to the work_offer at seq 1 — and every event then passes the canonical gate: right sequence slot for its hop and stage, an integer in-plan hop index, the bound shipmentId. Passing lifts into shipmentState; forged takes the drop path. Two second-order guards sit below the lift: verify promotes only on an explicit "pass" — "inconclusive" falls to the same drop path — and settleHop refuses while the hop stands disputed. A real settle emits its pay event back into the same stream, which is what makes the whole read model replayable from the ledger alone.
The other Highs were subtler trust holes: H-2 — outcome:"inconclusive" (or any unknown outcome) lifted a hop to terminal verified; now only an explicit "pass" promotes. H-3 — verifyHop was a paid-gate only: any actor could write the verdict, and it bypassed every guard disputeHop had; now it runs inside the hop lock, refuses disputed hops, and requires the actor to be the evaluator (the next hop's receiver, or the client on the final hop). H-4 — createShipment checked existence, created funded onchain jobs, then appended the offer with no lock — two concurrent calls could each create a funded job set; now the whole check→create→append is atomic under the shipment lock. H-5 — a settle that succeeded onchain but failed to append to the ledger diverged silently; the error now carries the job ID loudly, and a regression test pins the visible divergence. H-6 — S5's negative case and S1's "zero flags" assertion were vacuous: a GPS ref asserted by the very party it accused could never prove anomaly detection, and S1 had no declared device so fakeSensor could never even run. The tests were rebuilt to be non-vacuous.
The Mediums we accepted are written down, not hidden: settle doesn't compare the caller-supplied amount against the plan (the onchain budget is the backstop — flagged for the hardening epic), acceptShipment is a decorative v1 marker, and all authorization is a caller-supplied actor string — fine inside the server, as long as HTTP routes derive actor from auth, which is now a documented boundary rule.
What does v1 NOT do — the honest boundary
Short answer: the matrix proves the primitive's state machine is honest — it does not prove enforcement. Advisory flags warn and never block; disputes halt the chain only while the flag stands; the hop lock is single-process; and there is no per-source ledger ACL — the projection trusts that the ledger's writers are honest, which is a boundary the hardening epic owns.
That honesty is the point of the exercise. A regression contract that claims more than the code does is worse than none — it's a false floor. The matrix instead pins exactly what v1 guarantees: canonical-sequence projection, evaluator-gated verification, pre-settle disputes, atomic shipment creation, exactly-once settle, and five advisory flags that fire on real violations and stay quiet on honest traffic.
What's deliberately out of scope: issuer registries (who may call themselves a sensor), oracle attestations (verifying the artifact, not the reference), a real dispute engine with resolution outcomes, distributed locks for multi-instance deployment, and plan-amount binding for settle calls. Each is a named future brick, not a hidden gap.
How do you reuse the matrix?
Short answer: the whole thing is one test file plus a fixture — tests/supply-chain/scenario-matrix.test.ts and tests/supply-chain/helpers/chain-fixture.ts. makeChain({hops, roles, evidencePerHop, executor, lifecycle}) builds and drives any chain shape; failOnSettleJobId injects executor failures; the replay projection lives in src/server/lib/econ-ledger/supply-chain/project.ts. If you're building transition-contract primitives, the pattern copies over: one file of named violations, an in-memory executor, assertions on both write and replay.
The matrix is also the reason the Bedrij hypothesis can keep stacking bricks. Brick #1 proved participants compose; Brick #2 proved evidence composes. Brick #3 proves the composition holds under violations — which is what makes the next layer (enforcement) a tractable problem instead of an open-ended one.
Current state: 72/72 supply-chain tests green, 18/18 in the matrix itself, zero open Critical or High audit findings. The fix commit is de2cf81.
Building agent-native supply chains? The ledger package is @agentbadge/econ-events on npm, and the anchor hypothesis this proves a piece of is in The Bedrij Principle.
Don't certify. Measure.



Top comments (0)