DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path change-requests/phase-64-cr-pipeline-spend-credit-gate-v0_1.md

Phase 64 — Pipeline-Spend Credit Gate — Change Request

Version. 0.1 Date. 2026-05-30 CR identifier. CR-2026-095 (verify against the registry at Step 0; advance to next available number if 095 is taken) Phase. 64 — Pipeline-Spend Credit Gate (the prepaid hard-gated solvency control over the three model-rooms) Repos affected. DUNIN7/loomworks-engine (substrate only) Tag at completion. phase-64-pipeline-spend-credit-gate on loomworks-engine Drawn from. loomworks-pipeline-spend-credit-gate-scoping-note-v0_2.md (record scoping-notes/, commit c83f98d), which implements the constraints note loomworks-pipeline-spend-credit-gate-scoping-constraints-v0_2.md. All Operator decisions are settled in the scoping note; this CR is execution.


Plain-language summary

What this CR does. It makes running the three model-rooms (Manifestation, Shaping, Rendering) cost prepaid credits, and refuses spend that is not backed. Today the rooms call models and measure nothing — no token capture, no gate, no spender debit. This CR builds that surface: the LLM client returns token usage, the rooms resolve a system key and meter their spend, the consumption is written as tier-identified flows linked to the grant that funded them, and a hard floor at the database makes a balance structurally incapable of going negative. It also builds the funded reservoir, the refill mechanism, and the two-tier (person / agent) authorization that licenses spend.

What changed from the scoping note. Three things were corrected by a read-only code inspection at daee809, and the CR carries the corrections:

  1. The complete() contract change touches nine call sites across eight modules, not just the three rooms — the CR enumerates all nine so none breaks.
  2. The hard floor cannot be a blanket both-sides rule. The institutional dunin7 party is negative by design (its balance is the mirror of all issuance). The floor applies to the spender/person side only; the reservoir side is governed by the funded-reservoir accounting, which has its own floor at provisioned capacity. And because the existing balance trigger is AFTER INSERT (which cannot veto a row), the floor goes on a BEFORE INSERT trigger.
  3. The room consumption writer is three flows (token-in + token-out + credit-debit), adapted from converse's five-flow shape, plus the new lot-linkage.

What the Operator does. Review this CR; on approval it goes to Claude Code for Step 0 pre-flight, then builds across the numbered steps with checkpoint halts. The Operator approves at each checkpoint and at the final tag.

Decision status. All Operator decisions are settled in the scoping note v0.2 §6 (funded reservoir build-with; hard floor; refill auto-fire inside the authorized regime; split person/agent authorization; FIFO lots; Operator-authority provisioning). This CR is execution, not scoping.


1. Purpose

Phase 64 builds the pipeline-spend credit gate — the prepaid, hard-gated solvency control over the three model-calling rooms, per the constraints note §0 governing principle. It is step two of the Memory-completion path (step one, source identification, shipped at CR-2026-094). It exists to make the §0 invariant true: no person spends against the Loomworks reservoir without a pre-spend positive-balance gate; no credit is issued without a funded reservoir backing it; the gate is primary and pre-spend, reconciliation corrective.

The phase makes four things real that do not exist today:

  1. Metering surface for the rooms. The shared LLM client returns token usage; the three rooms resolve a system key and capture per-call token consumption.
  2. Funded reservoir. A provisioning event type, a reservoir balance that is provisioned-minus-allocated (not an unbounded mirror), a refill threshold with auto-fire inside the authorized regime, and an Operator-authority provisioning action.
  3. Hard floor. A BEFORE INSERT trigger that makes a spender's balance structurally incapable of going below zero — the backstop that holds even when a pre-spend check is bypassed.
  4. Authorization + gate. Two-tier authorization (person-spend, agent-spend) that licenses spend against the engagement reservoir, and the gate applied per execution model (inline for synchronous Manifestation; in-job pre-spend for async Shaping and Rendering), with exhaustion surfacing as a state the Operator acts on.

Phase 64 is substrate only. No Operator Layer frontend, no marketing surface, no credit purchase/sale mechanism (the ledger is shaped for it; the purchase flow is Phase 65+), no FORAY anchoring (the reserved seam is widened but stays no-op), no provider-anchored provisioning (Operator-authority provisioning this phase).


2. Scope

2.1 In scope

  1. Usage-returning LLM client. Change LLMClient.complete() (and both implementations) from -> str to return a structured result carrying completion text plus token usage (prompt tokens, completion tokens, model identity). Update all nine call sites (§5.1).
  1. System-key path for the three rooms. Give Manifestation, Shaping, and Rendering the three-tier key resolution by converging onto the canonical resolve_engagement_api_key resolver [CC verifies resolver name/path — scoping note names it; expected api_keys/resolve.py], replacing their current bare engagement-tier resolution. This is also the mechanism behind exhaustion exit (b) — the Operator's own supplied key resolves here.
  1. Provisioning event type + funded reservoir. Add a provisioning value to the credit.flows event_kind taxonomy. A provisioning flow credits the institutional reservoir's provisioned balance. The reservoir gains a real, floored balance (provisioned-minus-allocated), replacing the unbounded-mirror behavior on the issuing side.
  1. Hard floor (person/spender side) via BEFORE INSERT trigger. A new BEFORE INSERT trigger on credit.flows that raises and aborts the transaction if the flow would drive a person/spender balance below zero. Does not apply the zero-floor to the institutional dunin7 party (negative by design); the reservoir side is floored at provisioned capacity by piece 3's accounting, not at zero.
  1. Refill threshold + auto-fire. A refill level on the reservoir; when the provisioned balance crosses below it, refill auto-fires — drawing on provisioned backing — if and only if the spend is under an authorized Loomworks-credits regime (piece 7).
  1. Lot-based FIFO grant→consumption lineage. A lot/draw-down structure linking provisioning → grant → consumption. A grant creates a person's lot(s); a consumption draws down lot(s) in FIFO order, recording one or more draw-down rows linking the spend to the lot(s) and amount drawn from each. Additive columns/table [CC verifies whether a new table or columns on flows — see §5.4].
  1. Two-tier authorization. A person-spend authorization and a separate agent-spend authorization, both engagement-scoped, both Operator-granted. Person-triggered room spend requires person-spend authorization; agent-triggered room spend requires the agent-spend authorization (default-absent — the amok guard). Both debit the same engagement reservoir, recording the actor (triggered_by) in the flow.
  1. Per-room consumption-flow writer. A room writer adapted from converse's write_consumption_flows: three flows per room call (token-in leg + token-out leg + credit-debit), tier-identified (debits the specific loomworks_credit_haiku/sonnet/opus asset), work-linked (turn_event_id), lot-linked (piece 6), FORAY-seam-emitted.
  1. Gate + exhaustion, per execution model. Inline pre-spend gate for synchronous Manifestation; in-job pre-spend gate at the debit point for async Shaping and Rendering. Tier-cascade (opus → sonnet → haiku → exhausted) within the authorized regime. Exhaustion surfaces as a job state (async) or immediate response (sync), carrying the available exits.
  1. Operator-authority provisioning action. A callable (and the endpoint it sits behind [CC verifies admin endpoint pattern]) by which the Operator records a provisioning event ("DUNIN7 has provisioned N credits at tier T"). Operator-only, like POST /admin/grants.
  1. Widened FORAY reserved-emit payload. The credit-flow reserved-emit sites (_foray_reserved_emit, 12 sites per manifest v0.44 / Phase 63) widen their payload from {flow_id, transaction_id, asset_id, quantity} to the full row shape (adding from_party, to_party, turn_event_id, and the lot linkage). Stays no-op; anchoring is not wired.

2.2 Out of scope (Phase 65+)

2.3 Out of scope (future)


3. Prerequisites

3.1 Baseline

If the live baseline diverges (alembic head not 0074, working tree dirty, or test count off beyond the two pinned failures plus routine ±2 noise), CC stops at Step 0 and reports before proceeding.

3.2 Step 0 — pre-flight against the live codebase

CC reads the actual codebase before writing any code. This CR specifies architectural decisions firmly; substrate names not yet verified against ground truth are marked [CC verifies]. The inspection of 2026-05-30 grounded the major shapes (cited inline below); pre-flight confirms them still hold at the build SHA and resolves the remaining [CC verifies] markers. If pre-flight finds divergence that changes the architecture (not just naming), CC halts and produces an amendment proposal.

Pre-flight items:

  1. Substrate baseline confirmed. main at daee809, alembic heads reports 0074, working tree clean, test baseline matches §3.1 (two pinned failures only).
  1. complete() contract and call sites. Confirm LLMClient.complete() Protocol and both implementations return -> str (inspection: llm_client.py:26-35 Protocol; :79-115 AnthropicLLMClient; :131-145 DeterministicLLMStub). Confirm response.usage is available on the Anthropic response and currently discarded at [CC verifies — inspection saw it at the messages.create call, llm_client.py:104-105]. Confirm the nine call sites in §5.1 still exist at the cited file:lines.
  1. The balance trigger. Confirm credit.update_balance_on_flow() and trigger trg_balance_update are defined in migration 0062_phase_47_credit_substrate.py:430-467, never redefined since (0073 renamed the table only; trigger followed the OID). Confirm the trigger is AFTER INSERT and UPSERTs both sides unconditionally with no floor. Confirm the institutional from_party is "dunin7" [CC verifies — inspection saw flows.py:66] and goes negative on issuance by design.
  1. The consumption-flow writer. Confirm write_consumption_flows(...) at credit/flows.py:134-299 writes one transaction_id + five flows (4 token legs + 1 credit debit), with the column-set the inspection recorded (§5.5). Confirm it sets no lot/grant linkage today.
  1. credit.flows columns + event_kind taxonomy. Confirm the column set (ORM CreditFlowRow, credit/models.py:84-115) and the 9-value event_kind taxonomy (models.py:70-75) per §5.4. Confirm no content_hash and no grant/lot-linkage column exist.
  1. The reserved-emit sites. Confirm _foray_reserved_emit exists in src/loomworks/foray/reserved_emit.py (no-op), and that the 12 credit call sites carry it (# FORAY_RESERVED_LOCATION: credit.<event_kind> markers), with the thin payload {flow_id, transaction_id, asset_id, quantity} the inspection saw at flows.py:233-242,290-298.
  1. The system-key / three-tier resolver. [CC verifies the canonical resolver name and module — scoping note names resolve_engagement_api_key; expected api_keys/resolve.py, used today by uploads.] Confirm the three rooms currently resolve only the bare engagement-tier key (get_api_key_plaintext or equivalent) and do not touch the canonical resolver.
  1. The async job dispatch + triggered_by threading. Confirm (per the spender-threading verification, 2026-05-30) that triggered_by: ActorRef threads into produce_shape (shaping.py:264) and produce_render (render_specialist.py:237) via the dispatch path, and that ActorRef.kind distinguishes contributor/person from agent (considerations.py / agents/base [CC verifies ActorRef definition module]).
  1. The grant claim/issue functions. [CC verifies issue_grant/claim_grant location — inspection/Phase 47 put them in credit/grants.py.] These create grants today; the lot structure (piece 6) attaches at grant creation. Confirm where a grant row is written so the lot-writer hooks there.
  1. Admin endpoint + auth pattern. [CC verifies the POST /admin/grants handler and its Operator-only auth dependency.] The provisioning action (piece 10) follows the same auth pattern.
  1. Alembic numbering. Next migration number after 0074. This CR expects 0075 (and possibly 0075a/0075b if split — see §20 halt conditions).
  1. No pre-existing provisioning/lot structures. Confirm no provisioning event_kind, no lot table, no authorization table exist already.

If items 7, 8, or 9 diverge from the inspection findings, halt and amend — they are load-bearing for the gate.

3.3 CR archival

At Step 0, before Step 1, CC archives this CR to docs/phase-crs/phase-64-cr-pipeline-spend-credit-gate-v0_1.md in the engine repo.


4. Architecture decisions carried from the scoping note

These are settled (scoping note v0.2 §6). Stated here so the build does not re-litigate them.


5. Substrate detail (grounded against the inspection)

5.1 The complete() contract change and all nine call sites

Current (src/loomworks/agents/llm_client.py:26-35): async def complete(..., max_tokens=4096, temperature=None) -> str. AnthropicLLMClient.complete (:79-115) calls messages.create, extracts response.content[0].text, and discards response.usage.

Change: return a structured result — [CC names the type; e.g. CompletionResult(text: str, usage: TokenUsage) where TokenUsage carries prompt_tokens, completion_tokens, model identity]. A structured object, not a tuple, so usage carries model-tier identity for the flow writer. DeterministicLLMStub (:131-145) returns the same type with deterministic/zero usage. Preserve LLMResponseTruncatedError behavior (:107-114).

All nine call sites to update (inspection-confirmed):

| Module | file:line | Metered this phase? | |---|---|---| | engagement/manifestation_organization.py | :409 | Yes — Manifestation room (sync) | | agents/shaping.py | :512 | Yes — Shaping room (async) | | agents/render_specialist.py | :887 | Yes — Rendering room (async) | | agents/drift_detection.py | :321, :325 | No — update contract only | | agents/seed_cadence_synthesis.py | :222 | No — update contract only | | agents/summarization.py | :206 | No — update contract only | | credit/grant_decision_dispatcher.py | :506 | No — update contract only | | engagement/seed_induction.py | :112 | No — update contract only |

The five non-room callers are updated to consume the new return type (read .text) but are not metered in this phase. A missed caller is a runtime break, since the return shape changes — all nine must be updated in the same step as the contract.

5.2 The hard floor — BEFORE INSERT, person side only

The existing AFTER INSERT trigger (update_balance_on_flow, migration 0062:430-467) cannot veto a row. The floor is a separate BEFORE INSERT trigger on credit.flows:

This keeps the floor as a ledger invariant independent of any application-level check. The pre-spend gate (§5.6) stays as fail-fast UX; the floor is the backstop. [CC verifies BEFORE-INSERT trigger interaction with the existing AFTER-INSERT balance trigger — both fire per row; ordering is BEFORE then AFTER, so the floor vetoes before the balance UPSERT runs.]

5.3 The funded reservoir + provisioning + refill

5.4 Authorization model (split)

Two engagement-scoped, Operator-granted authorizations [CC verifies storage — a new credit.engagement_authorization table or columns; expected a small table keyed by engagement_id with person_spend_authorized and agent_spend_authorized booleans plus grant provenance]:

A spend whose authorization is absent hard-stops (the gate, §5.6), with the exit "authorize {person|agent} spend for this engagement."

5.5 The per-room consumption-flow writer (three flows)

Adapt from write_consumption_flows (credit/flows.py:134-299, the five-flow converse shape). A room is one complete() call, so the room writer writes one transaction_id + three flows:

Where spender_str is str(triggered_by.id) for person spend (asserting contributor.id == person.id at the seam), or the reservoir-debiting identity for agent spend with the agent recorded as actor in extra_metadata/triggered_by [CC verifies how actor-vs-payer is recorded when payer is the reservoir and actor is an agent].

Each flow fires _foray_reserved_emit("credit.<event_kind>", <full row payload>) (§5.7 widened payload).

5.6 Lot structure + draw-down

[CC verifies new table vs columns.] Expected: a lot is created when a grant issues (a person's allocation at a tier, with remaining balance). A draw-down links a consumption credit-debit flow to the lot(s) it consumed, in FIFO order (oldest lot first), with the amount drawn from each. A consumption crossing a lot boundary produces ≥2 draw-down rows. Recommended shape: a credit.lot table (id, party, asset/tier, granted_quantity, remaining_quantity, source_grant_id, source_provisioning_id, created_at) and a credit.draw_down table (id, consumption_flow_id, lot_id, quantity) — [CC confirms or proposes alternative at pre-flight].

5.7 The gate, per execution model

5.8 Widened reserved-emit payload

The 12 credit reserved-emit sites widen their payload from {flow_id, transaction_id, asset_id, quantity} to the full row shape: add from_party, to_party, turn_event_id, and the lot linkage. Stays no-op (anchoring not wired). [CC verifies the emit signature can carry the wider payload without breaking the no-op contract.]


6. Migration

Single migration 0075 [CC verifies next number] phase_64_pipeline_spend_credit_gate:

If the migration is too large to land safely, split into 0075a (schema: tables, columns, taxonomy) + 0075b (triggers/functions) — halt and propose at Step 0 (§20).


7–19. (Reserved — implementation-detail sections to expand at draft-finalization if needed; the architecture above is the binding specification. Following the Phase 47 CR pattern, detailed per-function pseudocode would live here. For this draft, §5 carries the binding substrate detail and §20 carries the step plan.)


20. Build steps and checkpoints

Auto-mode posture: Steps 0–N proceed in auto-mode; checkpoints halt for Operator confirmation. Final tag executes after Operator approval.

  1. Step 0 — pre-flight (§3.2). Resolve all [CC verifies] markers; confirm inspection findings hold at the build SHA. Archive CR (§3.3). Halt if architectural divergence (not naming).
  2. Step 1 — complete() contract + all nine call sites (§5.1). The contract change and all nine caller updates land together. Tests for the new return type.
  3. Step 2 — system-key path for the three rooms (§2 piece 2). Converge onto the canonical resolver.
  4. Step 3 — migration 0075 (§6): taxonomy, lot/draw-down, authorization, floor trigger, reservoir balance.
  5. Step 4 — provisioning action + funded reservoir accounting (§5.3, piece 10).
  6. Step 5 — authorization model (§5.4).
  7. Step 6 — per-room consumption-flow writer + lot draw-down (§5.5, §5.6).
  8. Step 7 — the gate per execution model (§5.7): inline for Manifestation, in-job for Shaping/Rendering; tier-cascade.
  9. Step 8 — widened reserved-emit payload (§5.8).
  10. CHECKPOINT A — halt. CC reports: migration applied + round-trips, floor trigger vetoes a below-zero person spend, reservoir refuses an over-provisioned grant, the three rooms meter and debit correctly, agent spend refused without agent-authorization, all nine callers updated, test suite green (baseline + new, two pinned failures unchanged). Operator confirms.
  11. Step 9 — tag phase-64-pipeline-spend-credit-gate (post-checkpoint, after Operator approval).

20.1 Halt conditions


21. Acceptance criteria

  1. LLMClient.complete() returns text + token usage; all nine call sites updated; non-room callers read .text unchanged in behavior.
  2. The three rooms resolve the system key via the canonical resolver and capture token usage per call.
  3. Migration 0075 applies cleanly from daee809/0074; downgrade -1 / upgrade head round-trips.
  4. provisioning event_kind exists; a provisioning flow raises the reservoir's provisioned balance.
  5. A grant exceeding provisioned reservoir capacity at a tier is refused.
  6. The BEFORE INSERT floor trigger aborts a transaction that would drive a person balance below zero; does not block institutional dunin7 issuance (negative by design).
  7. Refill auto-fires when provisioned balance crosses the threshold under an authorized regime; does not fire absent authorization.
  8. Person-spend authorization licenses person spend; agent-spend authorization is separate and default-absent; agent spend without it hard-stops.
  9. Agent spend (when authorized) debits the engagement reservoir with the agent recorded as actor.
  10. The per-room writer writes three flows (token-in, token-out, credit-debit), tier-identified, work-linked (turn_event_id), lot-linked (FIFO draw-down).
  11. A consumption crossing a lot boundary produces ≥2 draw-down rows summing to the debit.
  12. Synchronous Manifestation gates inline pre-spend; async Shaping/Rendering gate in-job pre-spend at the debit point; both honour the tier-cascade.
  13. Exhaustion surfaces as a job state (async) / immediate response (sync) carrying the available exits.
  14. Reserved-emit payload widened to the full row shape at all 12 credit sites; stays no-op.
  15. Test suite: new tests for floor, reservoir, authorization, lot draw-down, the three-flow writer, the gate per model; total green; two pinned pre-existing failures unchanged; no new skips; alembic head 0075.

22. Post-CR state


23. What this CR does not build

(Restated for reading-at-Checkpoint-A clarity.) Credit purchase/sale mechanism and pricing; FORAY anchoring (seam widened, stays no-op); provider-anchored provisioning; agent→responsible-person resolution; any Operator Layer frontend; reconciliation evaluator changes beyond new event kinds; the stale model-tier asset map fix.


24. Kickoff prompt for the Claude Code session


Read the Change Request at the path below. This is CR-2026-095 v0.1,
the Phase 64 Change Request — the pipeline-spend credit gate. You are
the executing agent named in the CR.

CR path: ~/Downloads/phase-64-cr-pipeline-spend-credit-gate-v0_1.md

Phase 64 builds the prepaid hard-gated solvency control over the three
model-rooms (Manifestation, Shaping, Rendering). It is a SOLVENCY
CONTROL, not a metering feature: the gate is primary and pre-spend;
reconciliation is corrective only. Read CR §1 and the scoping note §0
framing before building.

Substrate-only phase, loomworks-engine. Single migration 0075 (split to
0075a/0075b only if too large to land safely). Builds: usage-returning
LLM client (nine call sites), system-key path for the three rooms,
funded reservoir with provisioning + refill, a BEFORE INSERT hard-floor
trigger (person side only — dunin7 is negative by design), split
person/agent authorization, lot-based FIFO grant->consumption lineage,
the per-room three-flow consumption writer, and the gate per execution
model (inline for sync Manifestation; in-job pre-spend for async
Shaping/Rendering).

Code baseline: main at daee809, alembic head 0074 (source
identification merged, CR-2026-094). Working tree clean. Two
pre-existing test failures are pinned and accepted
(test_api_assertion_retract, test_phase_44_evaluator dedup) — NOT
regressions.

Three repos:
  - DUNIN7/loomworks-engine — substrate work, this CR.
  - DUNIN7/loomworks — UNCHANGED.
  - DUNIN7/loomworks-ui — UNCHANGED.

Run Step 0 pre-flight per CR §3.2. The 2026-05-30 inspection grounded
the major shapes (complete() contract + nine call sites; the AFTER
INSERT balance trigger from 0062; the five-flow converse writer; the
9-value event_kind taxonomy; triggered_by threading present in both
async rooms). Pre-flight confirms these hold at the build SHA and
resolves the [CC verifies] markers. HALT and amend if pre-flight items
7, 8, or 9 diverge architecturally (resolver, triggered_by threading,
grant write site) — they are load-bearing.

Per CR §3.3: archive this CR to
docs/phase-crs/phase-64-cr-pipeline-spend-credit-gate-v0_1.md at Step 0
before Step 1.

Per CR §20: build steps 0-8, then CHECKPOINT A halts for Operator
confirmation, then Step 9 (tag) after approval. Auto-mode-proceed
Steps 0-8; halt at Checkpoint A.

Halt conditions per CR §20.1: architectural divergence at pre-flight;
migration too large (propose 0075a/0075b); floor predicate not cleanly
expressible at trigger level; agent actor-vs-payer schema gap;
complete()-contract test refactor larger than expected.

Implementation notes after Checkpoint A:
docs/phase-impl-notes/phase-64-implementation-notes-v0_1.md

Tag at Step 9 (post-checkpoint):
phase-64-pipeline-spend-credit-gate on loomworks-engine.

DUNIN7 — Done In Seven LLC — Miami, Florida Phase 64: Pipeline-Spend Credit Gate — CR v0.1 — 2026-05-30