| name | freeze |
| description | Turn a vague vision into a signed, hashed acceptance contract in EARS notation with invariants, an operational profile, and an explicit out-of-scope list. Use at the start of any blackbox run, or standalone whenever someone wants testable acceptance criteria out of a fuzzy request. |
/freeze — the contract
The root of trust. Everything downstream inherits this document's quality, and nothing downstream can discover a requirement the human never had.
Useful on its own: a frozen contract can be handed to any coding agent, with or without the rest of the protocol.
Step 1 — Elicit
Delegate to the intent-elicitor agent. It interviews for the eight defect classes Rolls-Royce catalogued in free-form requirements: ambiguity, duplication, vagueness, complexity, omission, wordiness, inappropriate implementation, untestability.
Push hardest on omission, because the tooling cannot catch it. The three questions that surface most of it:
- What happens on the unhappy path? (Every criterion needs its
If ... then ... twin.)
- What must be true after — that was true before? (This becomes an invariant.)
- What would make you say this was built wrong even though it works?
Step 2 — Draft
Write draft.json:
{
"project": "...",
"criteria": [{
"id": "R1",
"ears": "When a refund is requested for a settled order, the system shall credit the original payment method.",
"criticality": "critical | standard",
"evidence_tier": 1,
"inputs": [{
"name": "amount",
"classes": [
{ "id": "valid", "examples": [1, 50, 999] },
{ "id": "over-order-total", "examples": [100000] }
],
"boundaries": [0, 1, -1]
}],
"invariants": ["money-conserved"]
}],
"invariants": [{ "id": "money-conserved",
"relation": "sum(credits) == sum(debits) across any two runs",
"transform": "refund N times, read the ledger before and after",
"kind": "conservation" }],
"operational_profile": { "R1": 0.7, "R2": 0.3 },
"out_of_scope": ["partial refunds", "multi-currency"],
"live_session_budget": 12
}
Give every equivalence class at least three examples where you can. One is drawn per run — that is what makes acceptance held-out, so a single example silently disables the mechanism.
Step 3 — Check, and expect to be refused
bb check draft.json
This refuses rather than warns, by design. Common rejections and what they mean:
| Code | Fix |
|---|
PATTERN | Not EARS. Use When <trigger>, the <system> shall <response>. |
VAGUE | "quickly", "robust", "properly" — a blackbox verifier cannot execute an adjective. Replace with a threshold |
MODAL | "should"/"may" — EARS commits with shall only |
IMPLEMENTATION | Names a mechanism (postgres, a cache, a class). The verifier is blind to mechanism, so it cannot check this |
COMPOUND | One criterion, one obligation. Split it |
Do not work around a rejection by weakening the criterion until it passes. That is the exact defect — silent scope reduction — the freeze exists to prevent.
Step 4 — Sign
Show the human intent/CONTRACT.md, not the JSON. Then:
bb freeze draft.json --signed-by "<name>" \
--statement "I have read these criteria. Anything not listed here will not be verified."
The signature is part of the hash. Re-signing produces a different contract and voids any certificate already issued.
Match the ceremony to the change
Three anchors. All three produce a real contract and a real certificate; they differ only in what the certificate is entitled to claim, and it states which one it got. Offer the cheapest one that fits, rather than demanding the most expensive one every time.
| When | Cost to the human |
|---|
--signed-by X --statement Y | New system, anything critical, anything they will be on the hook for | Read the contract once |
--express | A small change on a surface they already trust | Approve a summary. One flag |
--unsigned | CI, batch work, exploration — nobody is available | Nothing |
For an amendment, show them intent/AMENDMENT.md, not the whole contract. It lists only the new criteria and counts the carried-forward ones. Re-reading forty criteria to approve two is how a signature turns into a click, which costs the protocol its root of trust while looking more rigorous.
Never claim a signature that did not happen. Running --signed-by on the human's behalf, without them reading anything, is the one move that breaks the protocol outright: it produces a certificate asserting a person stood behind criteria they never saw. If they are not there, --unsigned is honest and --signed-by is a lie. The tooling cannot tell the difference; you can.
Closing
Report the hash and criterion count. Offer /build, or hand the contract off if they are using their own build process.