Conformance
Conformance behavior is pinned by two packs in crates/conformance,
because the same category of question has to be asked of two different
systems and the answer means something different in each case.
| Pack | IDs | Judged by | Asserts |
|---|---|---|---|
conformance | CT-CONF-001..005 | the in-memory double + fixture prover | the double’s vocabulary: public balances, fixture commitment ids, ct_* event codes |
conformance-real | CT-CONF-R01..R05 | the real crucible-simulator flow engine | only facts the engine publishes: op.<id>.accepted, its own event codes, its public state counts |
Which harness judges which pack
A contract’s tag decides, never a heuristic. Every conformance-real
scenario carries the real-engine tag, MockHarness::run_real_simulator
judges it, and the CLI enforces the pairing:
crucible-scenarios run --tag real-engine --real-engine # the engine judges these
crucible-scenarios run --category conformance # skips them, with a note
crucible-scenarios report # runs each pack on its own harness
run refuses to judge a double-backed contract with the real engine (that
contract’s vocabulary does not exist there, so a “failure” would say nothing
about the engine) and refuses a real-engine-only selection without the flag.
A test asserts the packs are not interchangeable: the real-engine contracts
are not all satisfiable by the double, so the distinction is real rather
than nominal.
Honest scope
Simulate: real. The conformance-real pack is judged by the actual
crucible-simulator flow engine, compiled from the simulator repository at a
pinned revision. A pass there is evidence about that engine.
Prove: mock. Both packs run against crucible-prover’s hermetic mock
backend. Nothing here is yet a cryptographic conformance check; real
UltraHonk/bb proving is heavyweight and opt-in in the prover repository.
That is the remaining gap, and it is gated on the same external audit the
prover repository needs — not on work in this one.
Anti-circular oracle design
The pack avoids the “simulator says X → ask the simulator → confirm X” trap. Expected outcomes are derived independently:
- balances are computed from the declared deposit/transfer/withdraw amounts against the fixture starting ledger, by plain arithmetic in the scenario,
- ownership is derived from the transfer’s sender/recipient,
- proof validity expectations come from the pinned circuit version and the statement, not from the verifier’s own verdict,
- every scenario also runs with all seven built-in invariants attached, so the pack judges global properties (conservation, ownership, commitment consistency) across whole runs, not just per-step effects.
What is covered (CT-CONF-001..005)
- Register, deposit, merge, transfer, withdraw as independent conformance checks, each asserting events, balance deltas, ownership, and state effects.
- A full-lifecycle conformance run (register → fund → merge → prove-and-transfer → withdraw) judged with every invariant attached.
What the engine pack covers (CT-CONF-R01..R05)
The expected counts are derived from the operation sequence alone, so they are an independent oracle rather than a restatement of the engine’s answers:
| ID | Contract | Expected from the sequence |
|---|---|---|
| R01 | a 40-unit deposit | exactly 1 live commitment, 2 accounts, 1 token, 2 transactions |
| R02 | two deposits then a merge | the two commitments consolidate to exactly 1 |
| R03 | a funded 30-unit confidential transfer | the spent commitment is consumed and two are created (live: 1 → 2), and no per-holder public balance is published |
| R04 | a 30-unit withdrawal | completes under the engine’s withdrawal_completed event |
| R05 | the full lifecycle | all five lifecycle event codes across seven steps, no rejection |
Boundary
Conformance scenarios implement no state or proof logic. They compose the adapter services and assert on observed effects against independently computed expectations.