Try to get a price without a grade.
The protocol page explains the ordering rule and the rubric page derives a grade. This page is the enforcement, and it is the only page where you are invited to attack it. Below is a working station: seal both observation sheets, reveal them, publish a grade, open the price window. Or skip a step, amend a sealed sheet, price a grade nobody agreed to. Nothing is greyed out. Every button is live, and the ones that break the ordering come back with the exception class and message the reference implementation raises.
The number always moves toward whoever spoke second.
Here is the whole problem, without cryptography. A machine comes back off a three-year lease. Someone has to say what it is worth. Whoever states a figure second has strictly more information than whoever went first, and the second figure drifts toward wherever it needs to be. That is not fraud and nobody has to intend it. It is what happens when one party learns something before committing.
The usual fix is a commit-and-reveal scheme: both sides seal a number, both reveal, and neither could have seen the other's. That works, and the protocol page specifies it. But it protects the price and leaves the grade wide open — and the grade is where the money actually is. If I can agree to seal prices and then argue about whether the chassis was Grade B or Grade C, I have simply moved the negotiation one step earlier and taken the commitment scheme's protection off it.
A price window cannot open until a grade_token is published.
The grade is fixed first, in its own commit-and-reveal round, and the price round is
cryptographically bound to it. Not a policy. Not a workflow convention that a busy
afternoon can skip. A precondition that raises an exception.
Two phases, in this order, always. Phase 1 fixes what the object is. Phase 2 fixes what it is worth. The second cannot start until the first has finished, and if the first fails to produce an agreed grade, the second never starts at all — the unit goes back for a re-read instead of being priced through the disagreement.
Seven states and two ways to stop early.
The station is a state machine with seven ordered phases and two terminal states it can fall into instead of finishing. The terminal states are not errors in the software. They are the correct outcome when two people looked at the same machine and read the instruments differently, and the honest thing to do is stop rather than average.
-
grade_openNothing sealed. The unit exists and nothing is claimed about it. -
grade_committedBoth sides have hashed an observation sheet with a random nonce and therubric_hash. Neither can read the other's, and neither can change their own. -
grade_revealedBoth sheets are public and both commitments verified. Any edit made after sealing is caught here. -
grade_publishedAgrade_tokencommits to the rubric, the agreed sheet and the eleven derived fields together. -
price_openOnly reachable fromgrade_published. Opening the window also fixes the policy, so the tolerance band cannot be widened later either. -
price_committedBoth amounts sealed against the agreed grade code. -
settledSpread measured against the band. Midpoint inside it, index outside it.
The two stops
-
voidClass M fields disagree — the ones read off the machine, where two competent people cannot honestly differ. A SMART count is not an opinion. The round voids and the unit is re-read. -
unresolvedA Class H judgement call moved the derived grade. This is a legitimate disagreement between two honest inspectors, and it escalates to a human rather than resolving itself in software.
A greyed-out button teaches you what this page's author thinks. An enabled button that refuses teaches you what the protocol enforces. Every action stays clickable in every phase on purpose — including all eight attacks. If any of them ever succeeds, the panel says so in the log instead of quietly doing nothing.
The station, running here.
This is a browser port of refimpl/station.py — the same phase
machine, the same guards, the same exception classes and messages. It is not a mock-up
with a switch statement behind it. On load it drives all four scenarios to completion,
recomputes every grade_token and bound policy hash with Web
Crypto, replays all eight published settlement vectors through its own arithmetic, and
compares the results to what Python produced. That comparison is the banner:
Pick a pair of observation sheets
Where the round is
Run it in order
spread 250 bp — inside the 1000 bp band
Or try to break the order
Station state
Log
Prices are entered in dollars and held in integer cents. There are no floats anywhere in
this protocol — canon() raises on a non-integer rather than
rounding it, because two implementations that round differently produce two different
hashes and a settlement nobody can reproduce.
Eight guards, and the specific cheat each one closes.
A guard nobody can name the attack for is decoration. Each of these exists because there is a concrete way to make money without it.
| Exception | Attempt | What it would buy you |
|---|---|---|
| GradeNotPublishedError | Open the price window with no grade fixed | Price first, then argue the grade toward your number. The entire reason the two-phase ordering exists. |
| GradeTokenMismatchError | Seal a price against a grade other than the agreed one | Accept the published grade in public and commit an amount that only makes sense at a better one, then dispute later with a sealed price already on record. |
| RevealMismatchError | Reveal a sheet that is not the sheet you sealed | Seal an honest sheet, see theirs, reveal a flattering one. This is the attack the commitment exists for and the only one where the cryptography does the work alone. |
| AlreadySubmittedError | Submit a second sheet or a second price | Keep both options open and pick after the reveal. Equivalent to never having committed. |
| PhaseOrderError | Any step out of sequence | Reveal before they have sealed; settle on one price; skip reconciliation. Each is a different way to act on information you should not have yet. |
| GradeRoundVoidError | Price a unit whose machine reads conflicted | Push a unit through when the instruments disagree, and settle a number against a machine neither party has actually characterised. |
| GradeUnresolvedError | Price a unit with an outstanding material divergence | Let the software split the difference on a judgement call, which converts a disagreement someone should look at into an averaged number nobody chose. |
Where the conservative merge comes in
When two sheets differ but both derive the same grade, the round proceeds — but not on either sheet. It proceeds on a pointwise-worst merge: the higher of the two defect counts, the worse of the two enum values, the union of both missing-parts lists, the pessimistic reading of every boolean. Where the two of you disagree, the unit is assumed to be in the worse condition either of you observed. That direction is deliberate. It means a disagreement can never be worth more to the seller than agreement was, so there is no incentive to manufacture one.
One of the guards has never fired, and cannot.
There is a ninth check in publish_grade. If two sheets both
derive the same grade, but the conservative merge of them derives a
different grade, the round escalates instead of publishing — on the reasoning
that the divergence was material after all and the two sides only agreed by coincidence.
It reads like a sensible belt-and-braces check, and it was written as one.
It is unreachable. We went looking for a single pair of sheets that triggers it: twenty thousand randomised pairs, plus an exhaustive sweep over Class H integer pairs across the full 0–8 range and every Class H boolean and list combination — about 35,400 checks in total. Zero crossings.
Every threshold in the rubric is a conjunction of independent per-field constraints:
this count at or below that number, and that one, and that one. If
a ≤ t and b ≤ t, then
max(a,b) ≤ t. The pointwise-worst merge therefore lands in
the same band both sheets landed in, always. The guard is not weak — it is
logically implied by the shape of SCGR1/1.0.
How we know the search was not simply blind
A negative result from a search that could not have found anything is worthless. So we broke the rubric on purpose: grafted a single cross-field cap onto the cosmetic axis — a limit on total defect load, which is exactly the kind of rule a real calibration round would add — and re-ran the identical search. It found 25 crossings immediately. The search works. The guard really is dormant, and it is dormant because of a property of this rubric rather than a gap in the test.
So it stays, and it gets labelled
The check is not being removed. The moment the rubric grows one cross-field rule — and
calibration against real units is likely to add one — it becomes live, and a guard
deleted for being quiet is a guard nobody restores. What it will never be is
described as enforcement that happens in practice. It has never fired,
it cannot fire today, and any claim that it protects a shipped round would be false.
station-data.json carries it under a
dormant_guards key so the page cannot quietly start implying
otherwise.
The grader on the rubric page was carrying a real defect. Its list of Class M
fields — the machine reads that void a round when they conflict — was maintained by
hand and had fallen three fields behind
grading.py:
ecc_memory, storage_type and
wipe_certificate_id. A disagreement on any of those three was
being classified as an ordinary divergence instead of voiding the round. The list is
now generated from the rubric source, and the page's self-check fails loudly if the two
ever disagree again.
Ordering is enforced. Almost everything else is unbuilt.
- No unit has been graded. All four scenarios are constructed. The thresholds they are graded against are estimates, not calibrated values — the board page states what evidence would move each one.
- No identity. Both roles here are strings. A real round needs signing keys bound to people, which is Ed25519 in the spec and not implemented anywhere yet.
- No clock. Reveal deadlines and the late-reveal penalty are specified and not enforced here; the browser has no trustworthy time source and neither party should be the timekeeper.
- No index. When the spread exceeds the band, the real protocol settles at a published index. There is no such index and no feed for one, so this page reports escalation and refuses to invent a number.
- No transport. Both parties are the same browser tab. Nothing here says anything about how two commitments reach each other, or what happens when one side goes offline mid-round.
- No inter-rater data. The most important unknown in the whole program: how often two trained people filling the same sheet on the same machine produce sheets that derive different grades. Until that number exists, the materiality split between Class M and Class H is an argument, not a measurement.