DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path change-requests/cr-2026-231-w6-writing-the-grant-into-the-record-amendment-v0_1.md

CR-2026-231 — W6 — amendment v0_1 — the refusal writer that failed on refusals

Version. 0.1 Date. 2026-08-21 Amends. change-requests/cr-2026-231-w6-writing-the-grant-into-the-record-v0_3.md. v0_3 is not edited — this sits beside it as CR-2026-231's record entry for a defect found and fixed during Gate 2 execution. Filed here rather than as a standalone finding, per Operator direction: it belongs to this change request's record, not to a document of its own. Engine. Introduced and fixed inside Gate 2 (324c080), merged at 9b103d8.


1. What happened

Gate 2's §5.4 requires mint_session's refusal path to write a refused lifecycle row. A row needs an agent_identity_id, so the writer had to name the identity the refusal was about.

The first implementation asked resolve_agent_identity(role) for it.

That function is W2's grantability resolver. It refuses frozen and retired identities — that is its entire job, and it returns a refusal object rather than an identity when the status is not active.

So the writer got nothing back exactly when a frozen identity was refused. No agent_identity_id, no row.

2. Why that is the worst possible case to miss

A frozen identity is not an edge case in this table. It is the commonest refusal there is.

The refusal path exists to record governance acting. The dominant way it acts is that someone froze an identity and the next mint was refused. The writer worked for the rare refusal — an unseeded role — and failed for the ordinary one.

Caught immediately by test_a_refused_grant_writes_a_row_with_its_verdict, which freezes an identity and then asserts the row exists. It was the first Gate 2 test to run and the only one to fail.

3. The fix

lookup_agent_identity(role) — returns the seeded identity regardless of status. Used only by the recording path.

Its docstring carries the distinction, because the two functions look interchangeable and are not:

> Distinct from resolve_agent_identity, which asks whether the identity may act and refuses a frozen or retired one. This asks only which identity is this… Never use this to decide whether an agent may act.

W2's single-grantability-question invariant is intact. check_grantability still has exactly one caller, and a test pins that. The new function answers a different question and cannot be mistaken for the gate.

4. The shape, which is why this is preserved

It is the tightening-guarantee finding again, reappearing inside the machinery built to record it.

That finding: freeze_agent_identity can be invoked against the rendering identity and returns successfully while stopping nothing — a control that reports success without acting, because nothing downstream consults the identity it acts on.

This defect: a refusal writer that returns successfully while recording nothing — because the function it consulted refuses the very state the refusal is about.

Both are a mechanism that behaves correctly and produces no effect in precisely the case it exists for. Neither raises. Neither logs an error. Both would read, from any surface, as having worked.

And the recurrence is the part worth keeping. The arc identified this failure mode, wrote a standing note about it, built a table to record the acts it concerns — and reproduced it inside that table's own writer, four hours later. Naming a failure mode does not inoculate the next thing you build against it.

5. The discriminating question, added to the two the arc already has

The arc carries two questions earned from repeated errors:

This one adds a third:

> When I call a function to get a fact, does that function also make a judgment — and does its judgment fire in the case I am asking about?

resolve_agent_identity returns an identity and decides whether it may act. Asking it for the first silently inherits the second. A function that both answers and judges will fail its callers exactly where the judgment bites, and that is where the interesting cases live.

6. What is unchanged

v0_3's design, gates, acceptance criteria and rulings all stand. This is an implementation defect found by its own test and fixed inside the gate that introduced it. Gate 2's acceptance criterion 3 — the refusal row with its verdict text — is met, and was met only after this fix.