DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path standing-notes/loomworks-standing-note-spend-context-built-at-the-gate-v0_1.md

Loomworks — standing note: build the spend context at the gate, not at the route — v0.1

Version. 0.1 Date. 2026-08-07 Author. Claude Code (CR-2026-177 execution session). Operator: Marvin Percival. Charter. standing-notes/dunin7-standing-authorization-charter-v0_1. Origin. The sibling audit required by CR-2026-177 Step 3, run against engine 20f71d7. The audit came back a clean negative — and the reason it came back clean is the principle recorded here. Reads with. inspection-briefs/loomworks-b32-findings-v0_1 (where the defect was found); change-requests/cr-2026-177-loomworks-manifestation-preview-metering-v0_1.


The principle

A room's spend context is constructed at the point of the gated call, not upstream and passed down.

Where the context is built decides whether it can be silently lost. A context constructed inline, in the same expression as the gated_room_complete call, cannot be dropped — there is no intervening function that could fail to forward it. A context constructed at the route and threaded through intermediate layers can be dropped by any one of them, and dropping it fails open: the call still succeeds, it simply stops being metered.


The evidence this came from

Three rooms call gated_room_complete. Two are safe by construction; one was not, and was the one that broke.

| Room | Where the context is built | Layers it crosses | Outcome | |---|---|---|---| | Shaping | inline at agents/shaping.py:580-581 | none | structurally safe | | Rendering | inline at agents/render_specialist.py:971-972 | none | structurally safe | | Manifestation | at the route, api/routers/manifestations.py:312 | two (preview_manifestationorganize_assertions) | dropped in the middle |

preview_manifestation accepted spend_context and never forwarded it. organize_assertions branches on spend_context is not None and db is not None, so the omission put every Manifestation LLM call on the unmetered path and made the route's own 402 handler unreachable.

The audit found no siblings. That is not luck. Shaping and Rendering are safe for a structural reason, not a diligence reason — nobody remembered to forward anything, because there was nothing to forward.


Why this failure mode is worse than it looks

It fails open, quietly. A dropped authorization check usually fails closed — something is refused and someone notices. A dropped spend context refuses nothing. The feature works. The user is served. Only the charge is missing, and nothing in the system is shaped to notice an absent debit.

Unit coverage does not see it. tests/test_phase_64_room_gate.py calls gated_room_complete directly with room="manifestation" and passes — it passed throughout the defect's entire life. The gate was correct; the wiring to it was not. A green gate test says the gate works, never that anything reaches it.

It disarms the error handler silently. The route's 402 branch was live code that could not execute. Reverting the fix and re-running the route test returns 502, not 402 — the ungated call proceeds to the model and fails later, somewhere unrelated. A handler that cannot fire looks identical to a condition that never arises.


How to apply

When adding a metered room, or moving an existing one:

  1. Build the RoomSpendContext in the same function as the gated_room_complete call. If that function does not have what it needs (person_id, engagement_id, key_source, secret_key), pass those ingredients down rather than the assembled context — a missing ingredient is a TypeError at the call, where a missing context is silence.
  2. Never give a spend-context parameter a None default on an intermediate function. spend_context: object | None = None is what let the omission type-check. If a layer must carry one, make it required so forgetting it is a hard error.
  3. Never branch on context is not None to choose metered vs unmetered without a test that proves the metered branch is reachable from the real entry point. That single condition is the whole failure: the drop and the legitimate own-key bypass are indistinguishable at the branch.
  4. Test metering through the entry point, not against the gate. At minimum: a sufficient-credit case asserting a spend was recorded, and an insufficient-credit case asserting the refusal status actually returns. The second is the load-bearing one — it proves the refusal path is reachable, which is exactly what unit coverage cannot tell you.
  5. Keep an own-key control test. It pins that a metering fix has not over-reached into engagements that bring their own key.

The general form

> Prefer designs where the unsafe state is unconstructible over designs where it is merely unwritten.

Shaping and Rendering are not correct because someone checked them. They are correct because their shape gives no opportunity to be wrong. Where a value must cross layers to do its job, crossing layers is the risk — and the fix is usually to move the construction, not to add a reminder.


DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — standing note: build the spend context at the gate — v0.1 — 2026-08-07