Quick Start
1
Run the contracts against your adapter
Each port ships a runnable contract. Register your adapter and both the fake and the shipping adapter are held to the same cases.
2
Prove the contract can still fail
The fixture spawns the real contract against a deliberately broken adapter, one break at a time. It takes a single mode string.The six break modes plus the
none control:3
Check the floor and the control
contracts.test.ts counts passing cases from a real none run and asserts a floor per contract: secrets โฅ 9, storage โฅ 11, time โฅ 8. The none control must pass green, so a fixture that failed for an unrelated reason cannot masquerade as proof.This runs automatically inside
contracts.test.ts under npm test. There is no flag to enable and no opt-in.Why Two Layers
The contracts assert on adapter behaviour; the fixture asserts the contracts still contain those assertions. A contract with no broken-implementation test is documentation with atest() around it. Deleting assert.ok(fired >= 2) from the time contract โ the one assertion that catches setInterval becoming setTimeout, which stops every polling loop in the app after a single tick โ left the suite at 1035 pass, 0 fail. Fourteen assertions across all four contracts could be deleted for free.
The Six Break Modes
Each mode breaks the adapter in one named way, and the matching contract case must go red by name.
A
none control runs unbroken, so a fixture that failed for an unrelated reason โ a syntax error, a missing import โ cannot masquerade as proof.
The Shrink Floor
A case with no break mode could still be deleted, socontracts.test.ts counts passing cases from a real run of the fixture โ not a regex over source text โ and asserts a floor.
Raise these numbers when you add cases. A drop means a contract lost coverage, and that is exactly the event worth a red build.
Why The Fixture Builds Broken Adapters Inline
adapters may not import testing โ enforced by tools/depgraph.mjs โ so the fixture builds its broken adapters inline, the same way engines/src/contract-fixture.ts does.
Adding A New Case Or Break Mode
1
Add the contract case with a stable name
The name is matched by regex, so keep it stable once other code depends on it.
2
Add the break mode in contract-fixture.ts
Guard the defect behind a
mode === "..." branch.3
Add the pair to the ADAPTER_BREAKS table
contracts.test.ts maps each mode to the case name it must redden.4
Bump the shrink floor for that contract by one
A new case raises the floor by one, so a later deletion is caught.
Best Practices
Pin every contract with a break mode, not just a re-implemented assertion
Pin every contract with a break mode, not just a re-implemented assertion
An inline broken adapter that re-implements the assertion proves an assertion of that shape would catch the defect โ not that the contract still contains it. Spawn the real contract instead.
Keep the none control green
Keep the none control green
Without the unbroken control, a fixture that failed everything โ a syntax error, a runner that cannot start โ would satisfy every break mode while proving nothing.
Count the floor from a real run, not from source text
Count the floor from a real run, not from source text
A regex over
test( is satisfied by a case that asserts nothing. Counting passing cases from a real none run pins behaviour, not shape.Force the reporter
Force the reporter
Spawn the fixture with
--test-reporter=tap so a test grepping for not ok behaves identically on Node 22 and Node 24.Related
Storage & Secrets
The two ports the secrets and storage break modes pin.
Time & Pacing
The port the two time break modes pin.
Shell & Adapters
The shell contract that sits in the same file.
Mobile Engines
The parallel fixture pattern one directory over.

