Threads · 06 / 18 · Sep 7, 2026 · Apache-2.0

Human approval is not a defence; freezing the arguments is

Around 93% of approval requests are approved without being read. If your defence is that somebody said yes, you do not have a defence. What protects you is that the thing executed is identical to the thing shown, checked again at the moment of execution.

The test

The version that ships passes every test anyone would think to write:

const p = propose("seat", /* freeze */ false);
p.signedBy = "owner@example.com";
expect(executeByReference(p)).toEqual({ ok: true, charged: "40.00" });   // green

Then an ordinary Tuesday happens between the approval and the execution:

priceChanges("seat", "400.00");
expect(executeByReference(p)).toEqual({ ok: true, charged: "400.00" });  // also green

It charged ten times what the approver saw, and reported success. Nothing anywhere is wrong: the record says approved, the amount matches the catalogue, and the only person who knows something happened is the customer.

With the arguments frozen and the world re-checked:

expect(executeFrozen(p)).toEqual({ ok: false, reason: "price_changed" });

Why the broken version is so convincing

It stores a reference and reads the value when it executes. That is the obvious design, it is simpler, and it is green in every test you would write against it: propose, approve, execute, correct amount.

What it actually promises the approver is "you approved this item, at whatever it costs when we get to it" — which is not what the screen said and not what they meant. The bug is not in the code path. It is in the sentence the code path implies.

Freezing and re-checking are two different things, and you need both

Freeze says what was approved. Bytes, not a pointer: if the approver signs a reference, they signed nothing in particular.

Re-check asks whether it is still true — and it goes at execution, never at approval. Checking at approval time is checking before anything could have gone wrong.

The test that separates them: the item is withdrawn after approval.

withdraw("seat");
expect(executeFrozen(p)).toEqual({ ok: false, reason: "withdrawn" });

Freezing alone would have charged happily for something that no longer exists.

Refuse with a name

price_changed is something the caller can act on — ask for the signature again, on the new number. A bare failure teaches them to retry, and retrying will never work. Three refusals, three different situations, three different things to do about them.

The third property, which is not in this repository

Check the approver's role when they sign, not when the proposal is made. Hours pass; people change teams and lose access, and a permission checked at the wrong moment is one that expired without anyone noticing.

It is not here because it needs an identity system to demonstrate honestly, and a fake one would teach the mechanics while hiding the difficulty. It is the same shape as everything above: the gap between deciding and doing is where all of this lives.

Where this comes from

Extracted from niiko, where every action that cannot run unattended becomes a frozen proposal, and the re-validation at execution is a step of the gate that no action can skip. The policy engine that decides which actions need this is published as @vorluno/ratchet.

License

Apache-2.0 — see LICENSE. This is a demonstration, not a package. Copy what you need.


Built by Vorluno — a software studio from Panamá.

// next threadLedger invariants the database enforces, not your prose