Skip to content

The per-beamline allocation: budget BC, award-window envelope, campaign seal#560

Merged
xmap merged 3 commits into
mainfrom
worktree-allocation
Jul 13, 2026
Merged

The per-beamline allocation: budget BC, award-window envelope, campaign seal#560
xmap merged 3 commits into
mainfrom
worktree-allocation

Conversation

@xmap

@xmap xmap commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

The R1/R2 component of the facility-LLM-budget reference architecture, and the last big one: LLM spend becomes an allocatable, enforceable, self-sealing envelope. Three commits (aggregate, read side + gates + sealer, five-reviewer panel batch) on the new budget bounded context. Design locked per the memo: beamline-total envelope, AllocationGranted genesis, USD-only ceiling, campaign-scoped debiting deferred to its trigger.

What the allocation is

The 18th BC, one aggregate. An Allocation is the envelope a beamline receives and spends: a USD ceiling, an optional campaign_id reference binding the award window, and a lifecycle that IS the paper's temporal argument, Granted (dormant) -> Active (spending) -> Sealed (books closed with a final-spend snapshot), with Voided from the non-terminal states. Balance is never stored: it folds from the same entries_decision_inferences ledger every enforcement tier already sums. Five slices (grant, activate, amend-ceiling as the cost-overrun tightening lever, seal, void) plus the read side (proj_budget_allocation_summary, the AllocationLookup port, find_total_spend summing every costed row regardless of attribution, the one-balance claim).

The three behaviors that make it the paper's component

  • Enforcement, not visibility (R4): both gate tiers gained an envelope arm above the per-agent caps, refusing when instance-total spend + the next call's projection would breach the Active ceiling. It gates even agents with no declared budget, because the envelope is the instrument's bound.
  • Award window (R1): the allocation's own [activated_at, sealed_at) lifecycle is the window, event-bound, no calendar.
  • Close the books beside the data: a CampaignClosed subscriber seals the bound envelope automatically, freezing the final-spend snapshot as a SYSTEM-authored event (the enclosure-permit-monitor precedent).

Review panel highlights (all applied in eec4404)

Security found the arc's one real bug before merge: the single-Active invariant was claimed in docstrings but enforced nowhere, so activating a second envelope silently stopped enforcing the first and orphaned its seal, and the subscribers' own remediation text steered operators straight into it. activate_allocation is now longhand with a find_active guard (AllocationAlreadyActiveError), and the remediation text says seal-or-void-first. Also applied: a cost_usd write-edge cap (one poisoned entry can no longer halt the instrument or corrupt sealed books), build_kernel fail-loud when a financial factory is missing, the sealer's bounded reload-retry on a benign concurrent amend, four naming renames (incl. AllocationBreach retiring the fourth meaning of "envelope"), a purge of eleven leaked build-stage markers with a fitness-regex arm so they cannot recur, and the mutation-survivor test gaps (gate ordering, factory wiring, wire-binding, Postgres projection arms).

Known debt (recorded)

Contract-tier tests for the five endpoints are allowlisted with removal obligations. The at-most-one-Active guard is best-effort (projection-read race, documented). The flat Trust-policy shape cannot yet express the instrument-admin tier the BC docstring names; pre-existing, not introduced here.

Test plan

Unit 12571 · architecture 29520 · contract 3338 (run because the lifespan gained registrations and a Field bound changed) · integration touched suites green · pyright + tach clean. New coverage spans the aggregate trios, evolver, projection (unit + Postgres apply), both lookups, the sealer (seal/skip/retry/determinism/metadata), the envelope gate arms and ordering in both subscribers and the guard, and the fail-loud raise.

🤖 Generated with Claude Code

xmap and others added 3 commits July 13, 2026 06:20
The eighteenth bounded context and the paper's last big component made
domain code. An Allocation is the envelope a beamline receives and
spends: a USD ceiling, an optional campaign reference that will bind
the award window, and a lifecycle whose shape IS the paper's temporal
argument, Granted (the award's dormant phase) -> Active (spending) ->
Sealed (books closed with a final-spend snapshot), with Voided from
the non-terminal states for a mistaken grant. Balance is never stored:
it will fold from the same inference ledger every enforcement tier
already sums.

Five slices ship with routes and MCP tools: grant (genesis,
idempotency-wrapped), activate, amend-ceiling (PUT semantics, the
cost-overrun tightening lever), seal, and void (reason required). The
seal handler takes a total-spend reader at bind time and threads the
allocation's own [activated_at, now) window into it; this stage binds
a documented zero reader, and the gate stage swaps in the real
instance-total query, so the slice's shape never changes. Attribution
halves (granted_by / activated_by / sealed_by) ride every fact event
per the fold-symmetry convention.

AllocationGranted takes the genesis-deviation allowlist's fifth slot:
granted is the paper's and HPC accounting's exact verb for an award,
and it names the FSM's initial state. Count pins and BC enumerations
updated honestly (18 BCs, 43 aggregates; budget graduates from the
planned rows to the resource group in the architecture pages).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stages B and C of the allocation arc: the Allocation stops being a
declaration and becomes the instrument-wide control the paper claims.

Read side: proj_budget_allocation_summary + the AllocationLookup port
(no-envelope-declared means no-envelope-constraint, the family's
opt-in posture) with the budget BC's Postgres adapter picking the
single Active envelope, newest-activated wins. SpendLookup gains
find_total_spend, summing every costed row regardless of attribution:
the envelope bounds the whole instrument's LLM spend, agent and
operator paths alike, which is the paper's one-balance claim.

Gates: find_envelope_breach carries both refusal arms, the
pre-estimate projection (spent plus pending exceeds ceiling) and the
post-hoc exhaustion (spent has reached ceiling), documented side by
side. Both subscriber agents defer with AllocationExhausted after
their per-agent cap check; the pre-estimate guard gains the envelope
arm and now gates even agents with no stream or declared budget,
because the envelope is the instrument's bound, not the agent's. The
seal slice's reader seam swaps from the stage-A zero reader to the
ledger fold, exactly the one-line change the seam promised.

The books close themselves: a CampaignClosed subscriber seals the
Active allocation bound to that campaign, stamping the final spend
from the same ledger fold, authored as SYSTEM via the same-BC decider
(the enclosure permit monitor precedent; the wired handler's authorize
gate would rightly deny a system principal under real Trust).
Deterministic event id and expected-version guard make replay a
no-op; mismatched, unbound, or non-Active allocations skip with a log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nd hardening

Security. The one real bug: activating a second envelope silently
stopped enforcing the first's ceiling and orphaned its automatic seal,
and the docstrings claimed a guard that did not exist. activate_allocation
is now longhand (the seal precedent) and consults find_active before the
transition, refusing with AllocationAlreadyActiveError when a DIFFERENT
envelope is Active; the subscribers' remediation text now steers
operators to seal or void before activating a replacement rather than
orphaning the exhausted one. cost_usd gained a 100k upper bound at the
inference write edge, so one poisoned entry can no longer exhaust the
whole instrument's envelope or corrupt a sealed books figure. build_kernel
raises in its Postgres branch when a financial factory is missing, rather
than silently metering zero and disarming the envelope. The BC docstring
names the instrument-admin authz tier (the five commands plus CloseCampaign,
which transitively disarms via the auto-seal).

Correctness. The sealer no longer forfeits the automatic seal on a benign
concurrent amend: it re-loads and retries at the fresh version (bounded,
deterministic event id keeps it idempotent), and it documents the
stale-negative projection-lag window whose recovery is the manual seal
slice. The fold-divergence skip logs at WARNING.

Naming (per the panel): AllocationCannotAmendCeilingError (full verb
phrase), AllocationBreach / find_allocation_breach (the fourth meaning of
"envelope" was one too many; the paper word stays in prose),
AllocationLookupResult (rejoins the LookupResult family), and note /
AllocationNote (a name that needed a disclaimer in its own docstring).

Conventions. Purged eleven build-stage markers that had leaked into
source and test docstrings, rewording each to name the mechanism; the
no-phase-markers fitness regex gains a lowercase-stage arm so letter-only
markers cannot slip through again (Title-Case "Stage A" spared, it is the
shape of real device names).

Tests close the panel's mutation survivors: the single-Active guard both
directions, the sealer retry and event-id determinism, gate ordering
(cap before envelope, envelope not consulted) in both subscribers, the
allocation_lookup factory wiring, the wire_budget ledger-reader binding,
the Postgres projection apply arms, the daily-token equality edge, and the
build_kernel fail-loud raise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  apps/api/src/cora/agent
  _budget_gate.py
  apps/api/src/cora/agent/adapters
  budget_spend_guard.py
  apps/api/src/cora/agent/subscribers
  caution_drafter.py
  run_debriefer.py
  apps/api/src/cora/api
  main.py 601-602
  apps/api/src/cora/budget
  routes.py 57-58, 65-67, 75-76, 90-91, 104-105
  apps/api/src/cora/budget/aggregates/allocation
  events.py 262, 290-292
  evolver.py
  state.py
  apps/api/src/cora/budget/features/activate_allocation
  handler.py
  route.py 23-24, 62
  tool.py 42-49
  apps/api/src/cora/budget/features/amend_allocation_ceiling
  route.py 38-39, 85
  tool.py 46-53
  apps/api/src/cora/budget/features/grant_allocation
  handler.py
  route.py 72-73, 120-132
  tool.py 79-91
  apps/api/src/cora/budget/features/seal_allocation
  handler.py
  route.py 42-43, 89
  tool.py 50-57
  apps/api/src/cora/budget/features/void_allocation
  route.py 39-40, 86
  tool.py 49-56
  apps/api/src/cora/budget/projections
  allocation.py
  apps/api/src/cora/budget/subscribers
  allocation_sealer.py 191-198, 255-259
  apps/api/src/cora/infrastructure/ports
  spend_lookup.py 167
Project Total  

The report is truncated to 25 files out of 62. To see the full report, please visit the workflow summary page.

This report was generated by python-coverage-comment-action

@xmap
xmap merged commit 8012d18 into main Jul 13, 2026
16 checks passed
@xmap
xmap deleted the worktree-allocation branch July 13, 2026 08:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant