# Brick #3: Seven Ways to Break a Chain — a Scenario Matrix for Verifiable Supply Chains

> Published: 2026-11-07 | Author: AgentBadge Team | Canonical: https://agentbadge.xyz/blog/supply-chain-scenario-matrix

Part of the verifiable supply-chain series — start here:
  [The Bedrij Principle](https://agentbadge.xyz/blog/bedrij-principle), then
  [Brick #1: Typed Participants](https://agentbadge.xyz/blog/supply-chain-participant-roles)
  and
  [Brick #2: Typed Evidence](https://agentbadge.xyz/blog/supply-chain-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 |

  ![A seven-row scenario matrix table: S1 clean 3-hop → zero flags; S2 temp → tempViolation; S3 sensor → fakeSensor; S4 no evidence → missingEvidence; S5 gps → gpsAnomaly; S6 dispute → disputed; S7 double settle → exactly-once](/images/blog/supply-chain-scenario-matrix-2.webp)
  
    The matrix in one picture: seven scenarios, each mapped to the outcome
    the projection must produce. S1 and S7 assert invariants (zero flags,
    exactly-once); S2–S6 assert the named advisory flag fires — and only
    there.
  

  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.

  ![Flowchart of the replay pipeline: the append-only event stream binds the plan at the work_offer in sequence 1, then every event passes a canonical gate checking sequence slot, in-plan hop index and shipmentId — pass lifts status, flags and disputes into shipmentState, forged events drop without lifting anything; a verify event promotes the hop only on outcome pass, and a settle on a disputed hop is refused, while a real settle produces a pay event that re-enters the stream at its canonical sequence](/images/blog/supply-chain-scenario-matrix-d1.webp)
  
    The replay pipeline after the H-1 fix. The plan binds once — to the
    work_offer at seq 1 — and from then on
    every event must pass the *canonical gate*: right sequence slot
    for its hop and stage, an integer in-plan hop index, the bound
    shipmentId. Passing lifts status, flags and disputes into
    shipmentState; anything forged takes the drop path and
    lifts nothing. Two second-order guards sit below the lift: a
    verify promotes the hop only on an explicit
    "pass" — "inconclusive" falls to the drop path
    too — 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.
  

  ![A canonical-sequence gate wall: honest event chips labelled hop0..hop3 flow through matching seq slots into a shipmentState panel; forged chips labelled seq:99, hop:5, shp-OTHER bounce off into a rejected pool](/images/blog/supply-chain-scenario-matrix-3.webp)
  
    The H-1 fix: honest events enter through canonical sequence slots into
    the projected read model; forged events — wrong seq, out-of-plan hop
    index, or a different shipmentId — lift nothing, not even status.
  

  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 audit
  report with per-finding resolution lives in the epic docs; the fix
  commit is de2cf81 (fix(214-5)).

  If you're building agent-native supply chains: the ledger package is
  @agentbadge/econ-events, the read endpoint is
  GET /api/ledger/shipments/:id, and the anchor hypothesis
  this proves a piece of is in
  [The Bedrij Principle](https://agentbadge.xyz/blog/bedrij-principle).

*Don't certify. Measure.*

---

## For AI Agents

- Companion guide: https://agentbadge.xyz/agent-guide/articles/supply-chain-scenario-matrix
- Knowledge Index: https://agentbadge.xyz/agent-guide/
- LLM entry point: https://agentbadge.xyz/llms.txt
- Engineering services: https://agentbadge.xyz/agent-guide/team/services
