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.
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:
complete() contract change touches nine call sites across eight modules, not just the three rooms — the CR enumerates all nine so none breaks.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.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.
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:
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.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).
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).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.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.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.[CC verifies whether a new table or columns on flows — see §5.4].triggered_by) in the flow.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.[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._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.split authorization was chosen instead).loomworks_credit_opus → claude-opus-4-7, behind opus-4-8) — separate small follow-up per constraints note §6.DUNIN7/loomworks-engine, main at daee809, alembic head 0074 (source identification merged; CR-2026-094). Source: Claude Code re-verification 2026-05-30, corroborated by manifest v0.44 changelog.[CC verifies current pass count at daee809]. Two pre-existing failures on main are accepted and pinned (test_api_assertion_retract, test_phase_44_evaluator dedup) — unrelated to this work; do not treat as regressions.main.DUNIN7/loomworks) and Workshop (DUNIN7/loomworks-ui) are unchanged by Phase 64.
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.
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:
main at daee809, alembic heads reports 0074, working tree clean, test baseline matches §3.1 (two pinned failures only).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.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.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.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._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.[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.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]).[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.[CC verifies the POST /admin/grants handler and its Operator-only auth dependency.] The provisioning action (piece 10) follows the same auth pattern.0074. This CR expects 0075 (and possibly 0075a/0075b if split — see §20 halt conditions).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.
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.
These are settled (scoping note v0.2 §6). Stated here so the build does not re-litigate them.
BEFORE INSERT trigger that aborts the transaction. Not the institutional side (negative by design).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.
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:
from_party is a person/spender [CC verifies the predicate that distinguishes a person party from the institutional/anthropic parties — likely from_party NOT IN ('dunin7','anthropic') or a UUID-shape check]: compute the resulting balance (current balance − quantity on the from-side) and RAISE EXCEPTION if it would be below zero, aborting the transaction.from_party is the institutional dunin7 party: no zero-floor (negative by design). The reservoir floor at provisioned capacity is enforced by the provisioning/grant accounting (§5.3), not this trigger.
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.]
event_kind = 'provisioning'. A provisioning flow credits the reservoir's provisioned balance for a tier [CC verifies how provisioned balance is represented — a row in asset_balances for the institutional party at a loomworks_credit_* asset, or a dedicated reservoir table].RAISE / refuse otherwise. [CC verifies whether this is a BEFORE-INSERT check on issuance flows or a guard in issue_grant.][CC verifies where the threshold check sits — likely in the issuance/draw path after a grant or consumption reduces the provisioned balance.]
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]:
triggered_by.kind in {contributor, person}) room spend against the engagement reservoir.triggered_by.kind == agent) room spend. Default-absent.A spend whose authorization is absent hard-stops (the gate, §5.6), with the exit "authorize {person|agent} spend for this engagement."
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:
event_kind='consumption_token', asset_id=<model>_input, quantity=prompt_tokens, from_party=spender_str, to_party=ANTHROPIC_PARTY, turn_event_id, extra_metadata={engagement_id, pipeline_stage:'<room>'}.asset_id=<model>_output, quantity=completion_tokens.event_kind='consumption_credit', asset_id=<the loomworks_credit_* tier>, quantity=<credit debit via the oracle>, from_party=spender_str, to_party=DUNIN7_PARTY, turn_event_id, plus the lot draw-down linkage (§5.6 lot structure).
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).
[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].
manifestation_organization.py:409): inline pre-spend gate before the complete() call — check the spender's positive balance for the tier and the authorization (§5.4); refuse with the tier-cascade if non-positive. Person is in the request; exhaustion is an immediate response carrying the exits.shaping.py:512) and Rendering (render_specialist.py:887): the pre-spend gate sits inside the running job, immediately before the complete() call. Check balance + authorization at the moment of spend; on refusal, transition the job to an exhausted/refused state the Operator sees, carrying the exits. A dispatch-time check MAY be added as fail-fast courtesy but is explicitly not the gate.
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.]
Single migration 0075 [CC verifies next number] phase_64_pipeline_spend_credit_gate:
provisioning to the event_kind taxonomy (and any check constraint listing it).BEFORE INSERT floor trigger + function (§5.2).
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).
Auto-mode posture: Steps 0–N proceed in auto-mode; checkpoints halt for Operator confirmation. Final tag executes after Operator approval.
[CC verifies] markers; confirm inspection findings hold at the build SHA. Archive CR (§3.3). Halt if architectural divergence (not naming).complete() contract + all nine call sites (§5.1). The contract change and all nine caller updates land together. Tests for the new return type.phase-64-pipeline-spend-credit-gate (post-checkpoint, after Operator approval).complete() contract change touches more tests than expected [CC reports count] → propose splitting Step 1.LLMClient.complete() returns text + token usage; all nine call sites updated; non-room callers read .text unchanged in behavior.daee809/0074; downgrade -1 / upgrade head round-trips.provisioning event_kind exists; a provisioning flow raises the reservoir's provisioned balance.BEFORE INSERT floor trigger aborts a transaction that would drive a person balance below zero; does not block institutional dunin7 issuance (negative by design).turn_event_id), lot-linked (FIFO draw-down).DUNIN7/loomworks-engine: alembic head 0075; credit.flows taxonomy gains provisioning; new lot/draw-down and authorization structures; BEFORE INSERT floor trigger; LLM client returns usage; three rooms metered and gated; reservoir funded and floored; reserved-emit payload widened. Tag phase-64-pipeline-spend-credit-gate. The §0 invariant is true for the rooms (person-side gate + floor) and on the issuing side (funded reservoir). The hard precondition — pipeline not opened to other Operators until the gate is real — is satisfiable after this phase.DUNIN7/loomworks (Operator Layer): unchanged.DUNIN7/loomworks-ui (Workshop): unchanged.(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.
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