Capacity Constraint Enforcement

Table of contents
  1. Capacity Constraint Enforcement
    1. Intent
    2. Summary
    3. Structure
      1. Identity model
      2. Inputs and Outputs
      3. State
      4. Flow
      5. Decision points
      6. Behavior
      7. Feedback
      8. Invariants
    4. Examples
      1. Airline — non-overbooking seat pool
      2. Banking — credit-limit headroom
      3. Healthcare — ward bed pool
      4. Database operations — connection pool
      5. Warehouse — inventory pool
      6. Rejection paths
      7. Regulated adversarial scenarios
    5. Generation acceptance
    6. Edge cases and explicit non-goals
    7. Composition notes
    8. Standards references
    9. Status
    10. Lineage notes

A resource-lifecycle primitive: a named, bounded pool of a finite resource with arithmetic that enforces total allocated never exceeds declared capacity. The pool’s identity is an opaque immutable id; capacity is mutable via attributed, audited adjustments. Allocations consume units, releases return units; units are fungible — the atom does not retain per-allocation identity. States: Open, Suspended, Closed. The load-bearing contribution: a single durable arithmetic invariant (allocated ≤ capacity) that composing patterns may rely on without re-implementing the constraint at every call site — subject to four host obligations named explicitly in this spec (serializable concurrent execution against the same pool_id, overflow-safe integer arithmetic, crash-atomic multi-record writes, and system-wide id generation producing non-colliding pool_id and event-id values across the lifetime of the system). The invariant is the atom’s guarantee; the four obligations are the deployment conditions under which the guarantee — together with Invariants 12 and 13 — holds.


Intent

Many regulated and operational systems must enforce a hard arithmetic bound on a shared, finite resource: an airline cannot ticket more passengers than the aircraft’s seats; a bank cannot extend credit beyond a customer’s declared limit; a hospital ward cannot admit more patients than its bed count; a connection pool cannot allocate more concurrent connections than the database supports; a warehouse cannot pick more units than are on the shelf. The shape is constant — declared capacity, accumulating allocations, hard rejection on over-capacity, releases returning units to availability — even though the resource semantics vary across domains.

Capacity Constraint Enforcement isolates that arithmetic into a single primitive. It owns one rule and one rule only: at every instant, the sum of currently-allocated units against any pool is less than or equal to that pool’s declared capacity. Allocations that would violate the rule are rejected at the boundary; releases decrement the running count; capacity may be adjusted upward freely and downward only when the new capacity still admits the current allocation count.

The pattern is distinct from Provisional Commitment. Provisional Commitment owns the per-allocation lifecycle — a specific resource is held for a specific requester for a bounded window, with an opaque commitment id and the absorbing terminal states Confirmed / Released / Expired. Capacity Constraint Enforcement owns the pool-aggregate arithmetic — the total allocated count against a declared bound, with no per-allocation identity at this layer. The two atoms compose in the obvious way: Provisional Commitment supplies the per-commitment record; Capacity Constraint Enforcement supplies the gate that prevents the pool’s running total from exceeding capacity when the commitment is placed. Reservation Lifecycle is the composition that wires them together. Each atom remains freestanding.

This is a freestanding (can be specified without naming any other pattern) atom in the EOS (Essence of Software — Daniel Jackson’s framework for specifying software concepts as freestanding, composable units) sense. It has its own state machine (a model that tracks which named states a record moves through: Open ⇄ Suspended; either non-Closed state → Closed via close_pool), its own actions (declare_pool, allocate, release, adjust_capacity, suspend_pool, resume_pool, close_pool, query), and its own invariants (the arithmetic bound, the audit-log immutability, the state-change auditability). It does not implement per-allocation identity, fairness or eviction policy under contention, preemption, capacity bursting or overcommit, allocation expiry, or the resource semantics that determine what a “unit” means. Each is a composing concern. See Composition notes.


Summary

Capacity Constraint Enforcement is a resource-lifecycle atom (a freestanding pattern spec that does not name any other pattern) that owns a single rule: at every instant, the sum of currently-allocated units against any named pool must not exceed that pool’s declared capacity. The atom enforces this bound by precondition — any allocation that would push the running total over the limit is rejected at the boundary before it is recorded — rather than by clamping or silently adjusting. That single arithmetic invariant (a condition that must always hold) is the atom’s load-bearing contribution: composing patterns may rely on it without re-implementing the constraint at every call site.

A pool is declared with a capacity — the maximum number of units that may be simultaneously allocated — and begins in Open state with zero units allocated. Allocation calls increment the running total; release calls decrement it. The pool’s capacity may be adjusted upward at any time and downward only when the new capacity is still at least as large as the current allocation count. The pool moves through three states: Open (accepting allocations), Suspended (blocking new allocations while allowing releases and capacity adjustments — used for maintenance windows or regulatory holds), and Closed (terminal — no new allocations or capacity adjustments, but releases are still accepted to allow in-flight work to drain cleanly).

Units are fungible at this atom’s grain — the atom does not track which specific resource or requester a given allocation corresponds to. An allocate call for five units produces one audit event recording that five units were consumed; a release call for five units produces one audit event recording that five units were returned. Per-allocation identity (this seat, this bed, this connection handle) lives in a composing atom, typically Provisional Commitment. The distinction matters: Provisional Commitment owns the per-commitment lifecycle with named states and terminal transitions; Capacity Constraint Enforcement owns only the pool’s running arithmetic.

Every allocation, release, capacity adjustment, and state change produces a durable, attributed, append-only (records can be added but never changed or deleted) audit-log entry with a before-and-after snapshot of the quantity it mutates. That snapshot design means any individual event can be verified for arithmetic correctness without replaying the entire log — an auditor can confirm that a given allocation did not exceed the declared capacity at the moment it was recorded by inspecting the event alone. The atom’s records cover successful operations; rejected calls produce no event at this layer, which is a deliberate design decision documented explicitly in the Edge cases.

The atom is relevant across any operational or regulated domain that enforces a hard bound on a finite resource: airline seat pools, per-customer credit lines at banks, ward bed counts at hospitals, database connection pools, and per-SKU inventory pools at warehouses all share the identical mechanic. What varies across domains is what a “unit” means, the rate of allocate and release calls, and the composing atoms that handle per-allocation lifecycle, idempotency (submitting the same operation twice produces the same result as once) under retry, verifiable attribution, and regulatory retention.


Structure

Identity model

Every pool known to the system has a pool_id — an opaque, immutable, system-generated identifier produced by declare_pool. The id is the pool’s identity; the declaring actor reference, declaration timestamp, and declaration reason are immutable properties of the pool record, set at creation.

The opaque-id model is load-bearing for two reasons. First, the name a deployment might use for a pool (e.g., "flight-NK1234-2026-05-14-seats" or "connection-pool-primary") is a host-system concern: pools may be re-named, re-categorized, or re-tagged without the pool’s identity changing. Second, two pools with the same human-readable label — declared in different deployment regions or against different resource registries — must have distinct ids so their arithmetic does not merge. Using a content field as identity would silently conflate logical-rename and distinct-pool cases.

Each allocate call produces an allocation_event_id — opaque, immutable, system-generated. Each release call produces a release_event_id. Each adjust_capacity call produces an adjustment_event_id. Each suspend_pool, resume_pool, or close_pool call produces a state_change_id. All four event-id classes are sub-records of the pool, accumulating on the pool’s audit log in insertion order, each individually addressable so that composing patterns (Actor Identity attestation, Audit Trail recording, Reservation Lifecycle’s per-commitment cross-reference) can reference a specific event by id without depending on timestamp or position.

Units are fungible at this atom’s grain — the atom does not assign or track per-allocation identities. An allocate(pool_id, count=5, ...) call increments the running total by five and emits one allocation event with one id; it does not produce five sub-records or five allocation ids. A subsequent release(pool_id, count=5, ...) decrements the running total by five and emits one release event with one id. The composing pattern (Provisional Commitment, or whatever owns the per-allocation lifecycle in the host system) supplies the per-allocation identity; this atom owns only the pool’s arithmetic.

Inputs and Outputs

  • A capacity value — a non-negative integer naming the maximum total allocation the pool admits. Zero is allowed (a pool that admits no allocations until its capacity is adjusted upward).
  • A unit count — a positive integer naming how many units an allocate or release call operates on.
  • A new-capacity value — a non-negative integer supplied to adjust_capacity.
  • A declaring / allocating / releasing / adjusting / suspending / resuming / closing actor reference — an opaque pointer to the internal actor performing the action. Non-empty, non-whitespace-only. Attribution only; non-repudiable proof composes with Actor Identity.
  • A reason — a non-empty, non-whitespace-only string of at most 2000 characters, required on declare_pool, adjust_capacity, suspend_pool, resume_pool, close_pool. Not required on allocate or release (those are routine arithmetic operations; the audit value of a per-allocation reason is low and would clutter the event log).
  • Actions:
    • declare_pool(capacity, declaring_actor_ref, reason) → pool_id | rejected(invalid-request | storage-failure)
    • allocate(pool_id, count, allocating_actor_ref) → allocation_event_id | rejected(not-known | over-capacity | suspended | closed | invalid-request | storage-failure)
    • release(pool_id, count, releasing_actor_ref) → release_event_id | rejected(not-known | over-release | invalid-request | storage-failure)
    • adjust_capacity(pool_id, new_capacity, adjusting_actor_ref, reason) → adjustment_event_id | rejected(not-known | closed | over-allocated | invalid-request | storage-failure)
    • suspend_pool(pool_id, suspending_actor_ref, reason) → state_change_id | rejected(not-known | not-open | already-closed | invalid-request | storage-failure)
    • resume_pool(pool_id, resuming_actor_ref, reason) → state_change_id | rejected(not-known | not-suspended | already-closed | invalid-request | storage-failure)
    • close_pool(pool_id, closing_actor_ref, reason) → state_change_id | rejected(not-known | already-closed | invalid-request | storage-failure)
    • query(pool_id) → {capacity, allocated, available, state} | rejected(not-known)
  • An implicit clock providing wall-time (clock time as a human would read it) timestamps for event-log entries.

On declare_pool: capacity must be a non-negative integer; otherwise invalid-request. declaring_actor_ref and reason must satisfy the uniform validation rule below.

On allocate: count must be a positive integer (at least 1); otherwise invalid-request. allocating_actor_ref must satisfy the uniform validation rule. The atom does not permit zero-unit allocations — a no-op allocate is not a legitimate use of the action.

On release: count must be a positive integer; otherwise invalid-request. releasing_actor_ref must satisfy the uniform validation rule.

On adjust_capacity: new_capacity must be a non-negative integer; otherwise invalid-request. new_capacity must additionally differ from the pool’s current capacity; an adjust call with new_capacity == current_capacity is rejected with invalid-request — a no-op adjustment is not a legitimate use of the action and admitting it would emit a capacity-adjustment event with prior_capacity == new_capacity, cluttering the audit log with events that record no change. This mirrors the discipline allocate and release apply to count (must be positive; zero-unit operations are not legitimate uses of those actions). adjusting_actor_ref and reason must satisfy the uniform validation rule.

On suspend_pool, resume_pool, close_pool: *_actor_ref and reason must satisfy the uniform validation rule.

Outputs — the atom’s persisted state takes two forms with different read surfaces.

The persisted pool record — what the atom durably keeps for each declared pool — carries: pool_id, capacity (current declared maximum), allocated (current running total), available (= capacity - allocated), current state, declared_at, declaring_actor_ref, declaration_reason, and the full audit log (allocation events, release events, capacity-adjustment events, state-change events, in insertion order). This is the shape audit pipelines, regulator queries, and composing patterns read against; it is the surface the Generation acceptance checks operate on.

The runtime read surface exposed to allocation-hot-path callers is query(pool_id) → {capacity, allocated, available, state} — a deliberately narrow projection of four fields. The projection is deliberate: callers in the allocation hot path need the current bound and headroom for routing or admission decisions, not the declaration metadata or the audit log on every call. Declaration fields and the audit log are read through composing surfaces — Audit Trail’s tamper-evident composite, Event Log’s deployment-grain journal, or direct inspection of the persisted record by audit pipelines — not through query. A reader who needs the full record reads the persisted record directly via the audit surface; a reader who needs the live arithmetic state reads query.

All four event classes in the persisted pool record carry before/after snapshots for the quantity they mutate, symmetric across the audit-log surface:

  • Allocation events: allocation_event_id, pool_id, count, allocated_before, allocated_after (= allocated_before + count), allocating_actor_ref, recorded_at.
  • Release events: release_event_id, pool_id, count, allocated_before, allocated_after (= allocated_before - count), releasing_actor_ref, recorded_at.
  • Capacity-adjustment events: adjustment_event_id, pool_id, prior_capacity, new_capacity, adjusting_actor_ref, reason, recorded_at.
  • State-change events: state_change_id, pool_id, prior_state, new_state, acting_actor_ref, reason, recorded_at.

The before/after symmetry is load-bearing for Generation acceptance: an auditor inspecting a single event can verify Invariant 4 (allocate: allocated_after ≤ capacity_in_effect) or Invariant 5 (release: allocated_after ≥ 0) without replaying the entire log to that point. Replay remains authoritative under Invariant 9 — the snapshots are a witness to the arithmetic, not a substitute for it.

Action returns: the event id created (per the action signatures above) so the caller has the id in hand without a follow-up query — required for passing to Actor Identity for attestation and to Audit Trail for tamper-evident recording.

State

A pool, once declared, occupies exactly one of three states:

  • Open — the pool accepts allocate calls subject to the capacity constraint (allocations that would push allocated + count > capacity are rejected with over-capacity; the pool remains Open). Entry state for every newly declared pool.
  • Suspended — the pool rejects all new allocate calls regardless of capacity headroom. release calls are still accepted (in-flight allocations can be cleanly unwound). adjust_capacity is still accepted (capacity can be revised before resumption). Reached via suspend_pool; left via resume_pool (back to Open) or close_pool (terminal).
  • Closed — terminal. The pool rejects new allocate calls and new adjust_capacity calls. release calls are still accepted so callers can unwind in-flight allocations; this is the only post-Closed mutation permitted. The pool record persists indefinitely from the atom’s perspective.

Drained is not a state. The arithmetic condition allocated == capacity is observable via query (returns available = 0) and is the precondition that causes allocate to reject with over-capacity. Treating it as a state would conflate a policy decision (an actor deciding to stop new allocations) with an arithmetic property (the running total has reached the bound). The state machine names policy-driven transitions only; arithmetic conditions are derived.

Ordering. The pool’s audit log is ordered by insertion sequence. References elsewhere in this spec to “after the most recent X,” “between X and Y,” or “most recent X” mean by insertion order, not by timestamp order. Timestamps on log entries are best-effort wall-time metadata sourced from the implicit clock; under skew or clock adjustment, timestamps may not be monotonic. Composing with Trusted Timestamping binds insertion order to externally-verifiable wall-time; without that composition, timestamps are advisory and insertion order is authoritative.

Each pool record carries:

  • pool_id — opaque, immutable, system-generated. Set on declare_pool. Never changes.
  • declared_at — wall-time of declaration. Set on declare_pool. Never changes.
  • declaring_actor_ref — set on declare_pool. Never changes.
  • declaration_reason — set on declare_pool. Never changes.
  • capacity — current declared maximum. Set on declare_pool; modified only by adjust_capacity.
  • allocated — current running total. Modified only by allocate (incremented) and release (decremented).
  • current state — one of {Open, Suspended, Closed}. Modified only by suspend_pool, resume_pool, close_pool.
  • audit log — ordered, append-only list of allocation events, release events, capacity-adjustment events, and state-change events. Each entry is individually addressable by its respective event id.

Transitions:

  • declare_pool(capacity, ...) → pool created in Open with fresh pool_id, declared_at = now, allocated = 0, current capacity = supplied capacity.
  • allocate(pool_id, count, ...) when Open and allocated + count ≤ capacityallocated increments by count; allocation event appended; state unchanged.
  • allocate(pool_id, count, ...) when Open and allocated + count > capacityrejected(over-capacity); no state change, no allocation event recorded.
  • allocate(pool_id, count, ...) when Suspended → rejected(suspended).
  • allocate(pool_id, count, ...) when Closed → rejected(closed).
  • release(pool_id, count, ...) when count ≤ allocated (in any state including Closed) → allocated decrements by count; release event appended; state unchanged.
  • release(pool_id, count, ...) when count > allocatedrejected(over-release); no state change, no release event recorded.
  • adjust_capacity(pool_id, new_capacity, ...) when Open or Suspended and new_capacity ≠ current_capacity and new_capacity ≥ allocatedcapacity updated to new_capacity; adjustment event appended; state unchanged.
  • adjust_capacity(pool_id, new_capacity, ...) when Open or Suspended and new_capacity == current_capacityrejected(invalid-request); no change, no event (no-op rejection — see Decision points).
  • adjust_capacity(pool_id, new_capacity, ...) when Open or Suspended and new_capacity < allocatedrejected(over-allocated); no change, no event.
  • adjust_capacity(pool_id, ..., ...) when Closed → rejected(closed).
  • suspend_pool(pool_id, ...) when Open → Suspended; state-change event appended.
  • suspend_pool(pool_id, ...) when Suspended → rejected(not-open).
  • suspend_pool(pool_id, ...) when Closed → rejected(already-closed).
  • resume_pool(pool_id, ...) when Suspended → Open; state-change event appended.
  • resume_pool(pool_id, ...) when Open → rejected(not-suspended).
  • resume_pool(pool_id, ...) when Closed → rejected(already-closed).
  • close_pool(pool_id, ...) when Open or Suspended → Closed; state-change event appended.
  • close_pool(pool_id, ...) when Closed → rejected(already-closed).
  • query(pool_id) (any state) → returns the pool’s current capacity, allocated, available, and state. Does not modify state; does not produce an audit event (queries are not logged at this layer; the composing Event Log handles per-query telemetry if needed).

Flow

Standard allocation cycle — happy path:

  1. An operator calls declare_pool(capacity=100, declaring_actor_ref="ops_admin_3", reason="hotel-room-pool-floor-2-2026-may") → pool_id = pool_h2. The pool is in Open with capacity = 100 and allocated = 0.
  2. A reservation system calls allocate(pool_h2, count=1, allocating_actor_ref="reservation_svc") for each new booking. Each call increments allocated by 1 and returns an allocation_event_id. When allocated = 100, subsequent allocate calls receive rejected(over-capacity) until a release occurs.
  3. As bookings are cancelled or stays complete, the reservation system calls release(pool_h2, count=1, releasing_actor_ref="reservation_svc"). Each call decrements allocated by 1 and returns a release_event_id.
  4. The capacity for next season expands to 120. An operator calls adjust_capacity(pool_h2, new_capacity=120, adjusting_actor_ref="ops_admin_3", reason="floor-renovation-added-20-rooms") → adjustment_event_id. The pool’s capacity is now 120; allocated is unchanged.
  5. End-of-season: close_pool(pool_h2, closing_actor_ref="ops_admin_3", reason="floor-decommissioned-renovation-permanent") → state_change_id. The pool enters Closed; no further allocations admitted; releases of remaining bookings still proceed until allocated = 0.

Suspension and resumption — operational hold:

  1. A connection pool is declared with capacity = 50 for a database-backed service.
  2. A maintenance window begins; the DBA calls suspend_pool(conn_pool_1, suspending_actor_ref="dba_07", reason="db-failover-2026-05-14-0200-utc") → state_change_id. New allocate calls are rejected with suspended; existing in-flight connections may still call release to unwind cleanly.
  3. The failover completes; the DBA calls resume_pool(conn_pool_1, resuming_actor_ref="dba_07", reason="failover-complete-2026-05-14-0235-utc") → state_change_id. The pool returns to Open; new allocations proceed.

Capacity downgrade rejected — preserving the invariant:

  1. A pool has capacity = 100 and currently allocated = 80.
  2. An operator attempts to lower the capacity to 60: adjust_capacity(pool_id, new_capacity=60, ...) → rejected(over-allocated). The atom rejects because 60 < 80 would violate the capacity constraint for the currently-allocated units. The operator must first release units (or wait for releases to occur) until allocated ≤ 60, then re-attempt the adjustment.

This is the preserve-by-precondition discipline: the atom enforces the constraint by rejecting actions that would violate it, never by silently clamping a value to fit. The caller owns the resolution policy — release first, then adjust; or accept that the desired downgrade is currently infeasible.

Decision points

Uniform validation rule. Across all actions, every required string field (actor references, reasons) must be non-null, non-empty, and non-whitespace-only; otherwise rejected(invalid-request). String validation operates on the Unicode codepoint sequence: non-empty means at least one codepoint; non-whitespace-only means at least one codepoint outside the Unicode whitespace category (\p{White_Space}); the 2000-character cap for reasons is a codepoint count, not a byte length, so multi-byte scripts are not penalized against single-byte ASCII. The atom additionally rejects with invalid-request any string field containing control characters (Unicode general category Cc: U+0000U+001F, U+007F, U+0080U+009F), zero-width characters (U+200BU+200D, U+FEFF), or bidi-override characters (U+202AU+202E, U+2066U+2069) — the rationale is regulator-readability: a reason field whose contents are control bytes, zero-width-only, or bidi-spoofed is invisibly empty or deceptively rendered to a human auditor reading the records, and admitting such values would pass the atom’s syntactic check while failing the audit-surface intent the field exists to serve. Unicode normalization (NFC, NFKC, others) is not applied by the atom — it stores the codepoint sequence as supplied; deployments under regulators that require comparison or deduplication on string fields apply normalization at the deployment boundary before passing to the atom, and the atom records the normalized form. The atom does not perform case-folding on any string field — acting_actor_ref values that differ in case are distinct attribution surfaces to the atom; deployments requiring case-insensitive actor identity normalize at the boundary. Every required integer field (capacity, count, new_capacity) must be of the correct sign (non-negative for capacity and new_capacity; positive for count); otherwise rejected(invalid-request).

At declare_pool(capacity, declaring_actor_ref, reason): All three fields must satisfy the uniform validation rule (with capacity non-negative integer); otherwise rejected(invalid-request). If the pool store write fails, rejected(storage-failure) — no pool record is created.

At allocate(pool_id, count, allocating_actor_ref): pool_id must reference a known pool; otherwise rejected(not-known). The pool must be in Open state. If Suspended, rejected(suspended). If Closed, rejected(closed). count and allocating_actor_ref must satisfy the uniform validation rule; otherwise rejected(invalid-request). The arithmetic precondition is: allocated + count ≤ capacity. If allocated + count > capacity, rejected(over-capacity). If the write fails, rejected(storage-failure)allocated and the audit log are unchanged.

At release(pool_id, count, releasing_actor_ref): pool_id must reference a known pool; otherwise rejected(not-known). release is permitted in all three states (Open, Suspended, Closed) — releases of in-flight allocations must succeed regardless of pool state so the running count can be cleanly unwound. count and releasing_actor_ref must satisfy the uniform validation rule; otherwise rejected(invalid-request). The arithmetic precondition is: count ≤ allocated. If count > allocated, rejected(over-release) — releasing more than is allocated would violate the non-negativity invariant and almost certainly indicates a coordination bug at the caller. If the write fails, rejected(storage-failure).

At adjust_capacity(pool_id, new_capacity, adjusting_actor_ref, reason): pool_id must reference a known pool; otherwise rejected(not-known). The pool must not be Closed; otherwise rejected(closed). All four fields must satisfy the uniform validation rule; in addition, new_capacity must differ from the pool’s current capacity — an adjust whose new value equals the current value is a no-op and is rejected with invalid-request rather than admitted (audit-log hygiene, asymmetry-resolution with allocate/release positive-count rule). The arithmetic precondition is: new_capacity ≥ allocated. If new_capacity < allocated, rejected(over-allocated) — the requested capacity would put the pool’s already-allocated units over the bound, violating the capacity constraint. The atom enforces by precondition, never by clamping. If the write fails, rejected(storage-failure).

At suspend_pool(pool_id, suspending_actor_ref, reason): pool_id must reference a known pool; otherwise rejected(not-known). The pool must be in Open state. If Suspended, rejected(not-open). If Closed, rejected(already-closed). Field validation as above. If the write fails, rejected(storage-failure).

At resume_pool(pool_id, resuming_actor_ref, reason): pool_id must reference a known pool; otherwise rejected(not-known). The pool must be in Suspended state. If Open, rejected(not-suspended). If Closed, rejected(already-closed). Field validation as above. If the write fails, rejected(storage-failure).

At close_pool(pool_id, closing_actor_ref, reason): pool_id must reference a known pool; otherwise rejected(not-known). The pool must not already be Closed; otherwise rejected(already-closed). Field validation as above. If the write fails, rejected(storage-failure).

At query(pool_id): pool_id must reference a known pool; otherwise rejected(not-known). Query does not modify state and does not produce an audit-log entry at this layer.

Priority ordering among rejection reasons: For any action, not-known is checked before state-validity checks; state-validity checks are checked before field-format checks; field-format checks are checked before arithmetic preconditions (over-capacity, over-release, over-allocated); all checks precede the store write. Each ordering decision is defended in-line.

not-known first because every other check presupposes a real pool record to inspect. A call against an unknown pool_id has no state, no current allocated, no capacity to compare against — none of the subsequent checks are meaningful.

State-validity before field-format because the pool’s state is a structural property of the call’s target, whereas field-format is local to the specific call. A Closed pool is a target that does not accept allocate or adjust_capacity calls at all; reporting closed to the caller communicates “this target is unusable for this action” before per-call fields are validated against the same target. The convention mirrors how databases reject “table does not exist” before “your column type is wrong” — target-level structural rejections precede call-level structural rejections. The cost of this ordering is that a caller passing a malformed count against a Closed pool sees closed and not invalid-request; the caller’s malformation is masked until they retry against a non-Closed pool. The cost is accepted because the alternative — field-format first — forces every call to be validated against the pool’s per-call surface before the caller learns the target is fundamentally unusable, which is the noisier path for the common case (operators draining a pool, callers trying to use a decommissioned pool).

Field-format before arithmetic because the arithmetic preconditions (allocated + count ≤ capacity, count ≤ allocated, new_capacity ≥ allocated) require the integer fields to be well-formed before they can be meaningfully evaluated — an allocate(pool_id, count=-5, ...) against an Open pool returns invalid-request (count is not a positive integer) rather than passing the arithmetic check by happenstance (because allocated + (-5) ≤ capacity holds for any non-negative allocated) and then rejecting on field-format afterwards.

Store write last because all in-memory checks precede any durable side effect; a storage-failure rejection carries the same all-or-none guarantee as the precondition rejections (no partial commit, no audit-log entry written, no running-total change).

Behavior

Observed behavior, derived from how operational and regulated systems use bounded resource pools:

allocate increments the running total by the requested count, atomically with respect to other concurrent calls under the host environment’s serialization guarantees. Two concurrent allocates against a pool with one unit of headroom resolve serially — whichever wins the race produces an allocation event; the loser receives over-capacity. The atom does not implement fairness policy (FIFO, priority, lottery); the host environment’s serialization order is what determines the outcome.

release decrements the running total by the requested count. The atom accepts releases of any positive count up to the current allocated. A release of exactly the currently-allocated amount drives allocated to zero; subsequent releases are rejected with over-release until new allocations occur. The atom does not validate that the released count matches any prior allocation count — units are fungible. A caller that allocates 5 in one call and 3 in another may release in any combination summing to no more than 8.

adjust_capacity modifies the bound without modifying the running total. An upward adjustment (new_capacity > current_capacity) is always accepted (subject to field-validation). A downward adjustment (new_capacity < current_capacity) is accepted only if new_capacity ≥ allocated; otherwise rejected with over-allocated. A same-value adjustment (new_capacity == current_capacity) is rejected with invalid-request — a no-op adjustment is not a legitimate use of the action, mirroring the positive-count rule on allocate/release. The atom does not permit silent clamping (set capacity to allocated count) or forced eviction (release units to fit the lower capacity) — those are policy decisions the caller must make explicitly via additional release calls before re-attempting the adjustment.

suspend_pool halts new allocations without unwinding existing ones. Use cases: maintenance windows, regulatory holds (a credit pool suspended pending compliance review), operational pauses (a connection pool suspended during failover). Releases remain admissible; capacity adjustments remain admissible (operators can re-tune capacity while the pool is paused). The pool’s running total is unchanged by the suspend itself.

close_pool is terminal. Once closed, no new allocations and no capacity adjustments are admitted. Releases remain admissible so the pool’s running total stays consistent with the composing patterns whose per-allocation records may still be unwinding when close occurs (see Invariant 3’s defended-in-line rationale). The pool record persists in Closed indefinitely; retention and archival are a composing concern.

query is read-only. It returns the pool’s current capacity, allocated count, available (= capacity − allocated), and state. The query is not logged at this layer; the composing Event Log handles per-query telemetry if a deployment needs it. Queries are not subject to the pool’s state — a Closed pool’s data remains queryable for as long as the record persists.

No action modifies declaration fields (pool_id, declared_at, declaring_actor_ref, declaration_reason) after declare_pool. The declaration captures the pool’s origin; subsequent changes (capacity adjustment, state transition) layer on top via the audit log without overwriting the declaration record.

Feedback

Each successful action produces an observable, measurable change:

  • After declare_pool — a new pool appears in Open with the supplied capacity, allocated = 0, and fresh pool_id. Total pool count increases by one.
  • After allocateallocated increments by the supplied count. An allocation event appears in the audit log with a fresh allocation_event_id (returned to the caller), the count, the actor, a wall-time recorded_at, and the allocated_before / allocated_after snapshot. available (queryable via query) decreases by the same count.
  • After releaseallocated decrements by the supplied count. A release event appears in the audit log with a fresh release_event_id (returned to the caller), the count, the actor, a wall-time recorded_at, and the allocated_before / allocated_after snapshot. available increases by the same count.
  • After adjust_capacitycapacity is replaced by the supplied new_capacity. An adjustment event appears in the audit log with a fresh adjustment_event_id (returned to the caller), naming the prior and new capacities, the actor, the reason. available may change as a side effect of the new capacity (it is recomputed as capacity − allocated).
  • After suspend_pool, resume_pool, close_pool — the pool’s state is the new state. A state-change event appears in the audit log with a fresh state_change_id (returned to the caller), naming prior state, new state, actor, reason. Pool counts segmented by state shift accordingly.

Each rejected action produces an observable refusal with a named reason. The pool-count segmentation (Open, Suspended, Closed) is computable from the pool record set at any time; the running-total state of each pool is exposed via query.

Invariants

The following hold across all valid sequences of actions and constitute the verification surface of the pattern:

Invariant 1 — Pool record permanence under this atom’s actions; the composed-system view is bounded by the deployment’s retention policy. The atom defines no action that removes a pool record. Once declared by a successful declare_pool call, the pool_id is durably persisted and remains in the system through every subsequent state transition including close_pool; no atom-defined action deletes the record at any point in its lifecycle. A storage-failure rejection on declare_pool guarantees no partial record was written. Under composition with Retention Window — which applies to the pool record itself as well as to the audit log — the composed-system view may differ: pool records whose state has been Closed for longer than the deployment’s retention schedule for closed-pool records may be archived to cold storage or purged entirely, mirroring Invariant 9’s treatment of audit-log entries. The atom’s contribution is the durability discipline (no atom-defined action removes a pool record); the deployment’s Retention Window policy is the other half of what determines whether a regulator querying a stale pool_id receives not-known because the pool was never declared or because its record was purged — the rejection surface is identical to the caller (named explicitly in Examples → Rejection paths → not-known), and the regulator distinguishes the two by reading the deployment’s retention manifest, not the atom’s records.

Invariant 2 — State membership exclusivity. Every pool known to the system is in exactly one of {Open, Suspended, Closed} at all times.

Invariant 3 — Closed is absorbing for state transitions and for new allocations. Once a pool enters Closed, no action transitions it elsewhere. allocate, adjust_capacity, suspend_pool, and resume_pool against a Closed pool are rejected. release is the single mutating action admitted in Closed; the running total can be decremented by composing patterns unwinding in-flight allocations. The rationale is cross-pattern data consistency: composing patterns such as Provisional Commitment maintain per-allocation records that may still be in non-terminal states (e.g., Held) when close_pool is called. When those per-allocation records reach their own terminal states (Released, Expired), the composing system calls release on the pool to keep the pool’s running total aligned with the truth on the composing side. Rejecting release in Closed would not block the composing pattern from reaching its terminal states — Provisional Commitment’s terminal transitions are internal to it — but it would permanently strand the pool’s running total at its close-time value, producing an audit log whose final allocated figure does not match any observable reality. Permitting release in Closed preserves the data-consistency property without weakening the state-machine’s terminal semantics for the operationally-meaningful transitions (suspend/resume/close, allocate, adjust_capacity).

Invariant 4 — Capacity constraint. For every pool at every instant, allocated ≤ capacity, conditional on the host obligations the atom names as deployment-shaped concerns (see Edge cases → Concurrency and atomicity (concurrent-call atomicity), Edge cases → Crash atomicity (mid-action process failure), and Edge cases → Integer arithmetic precision): serializable concurrent execution against the same pool_id, crash-atomic multi-record writes across the action’s audit-log entry and running-total update, and overflow-safe integer arithmetic for allocated + count. Under those conditions, the atom enforces the invariant by precondition on allocate (rejects if allocated + count > capacity) and on adjust_capacity (rejects if new_capacity < allocated); there is no action sequence the atom accepts that produces a state with allocated > capacity. This is the load-bearing arithmetic invariant; it is what composing patterns may rely on without re-implementing the constraint, contingent on the named host obligations being met. A deployment that violates any of the three obligations produces an environment in which the invariant can be observed to fail despite the atom’s preconditions; such a failure is a deployment-side gap, not an atom-side guarantee failure, and the deployment’s audit posture must acknowledge it as such.

Invariant 5 — Non-negativity. For every pool at every instant, allocated ≥ 0. The atom enforces this by precondition on release (rejects if count > allocated). There is no action sequence the atom accepts that produces a negative running total.

Invariant 6 — Capacity non-negativity. For every pool at every instant, capacity ≥ 0. The atom enforces this by precondition on declare_pool and adjust_capacity (both reject negative values).

Invariant 7 — Declaration fields immutable. pool_id, declared_at, declaring_actor_ref, and declaration_reason are set on declare_pool and never change.

Invariant 8 — Audit-log events have two surfaces with distinct lifecycles. Every recorded event has an audit-identifier surface — event id, pool id, event class, arithmetic fields (count, allocated_before, allocated_after, prior_capacity, new_capacity), state fields where applicable (prior_state, new_state), and recorded_at timestamp — that the atom defines no action to modify; and an attribution surface*_actor_ref and reason — that the atom likewise defines no action to modify but the composing Retention Window pattern may erase under GDPR Article 17 obligations when the deployment encodes personally-identifying information into those fields. Composing systems read the two surfaces with different lifecycles: the audit-identifier surface persists for as long as the event persists; the attribution surface persists until Retention Window scrubs it, after which the records still verify the arithmetic chain but no longer identify the actor. The split is structural, not stylistic — the audit-identifier surface is what makes the structural audit queries (Invariant 4 verification, Invariant 5 verification, capacity-adjustment replay, state-change replay) verifiable from records alone; the attribution surface is what makes the records personally-identifying and therefore subject to erasure obligations the audit chain must not depend on. A regulator reading the invariant must understand the operational reality: the atom’s records are what the atom’s actions write and never re-write, but under the Retention Window composition the atom recommends for regulated deployments, the attribution surface is by-design mutable on the composing pattern’s schedule.

Invariant 9 — Audit-log events are append-only under this atom’s actions; the composed-system view is bounded by the deployment’s retention policy. The atom defines no action that removes an event from a pool’s audit log, no action that re-orders events, and no action that inserts an event before any prior event. Under the atom’s actions alone, the log grows monotonically in length and in insertion order. Under composition with Retention Window — the composition the atom recommends for every regulated deployment — events that fall outside the deployment’s retention window may be archived to cold storage or purged entirely, and the composed-system view of the audit log is bounded by the retention schedule, not by the atom’s append-only discipline. The arithmetic chain (Invariant 4 verification) is reconstructable from records within the active retention window; reconstruction of pre-retention history requires the archive when one exists, and is bounded out when purge has occurred without archive. The Generation acceptance section names the verification scope explicitly. The “under this atom’s actions” qualifier is load-bearing: a regulator querying the records is reading the composed-system view, which the atom does not single-handedly govern; the atom’s append-only discipline is necessary for the composed-system audit chain but not sufficient — the deployment’s retention policy is the other half of what determines what the regulator sees.

Invariant 10 — State-change events are auditable. Every transition (Open → Suspended, Suspended → Open, any non-Closed → Closed) produces a durable state-change entry in the pool’s audit log with a fresh state_change_id, naming the prior state, new state, acting actor reference, reason, and timestamp. No state transition is silent.

Invariant 11 — Capacity-adjustment events are auditable. Every capacity change produces a durable adjustment entry in the audit log with a fresh adjustment_event_id, naming the prior capacity, the new capacity, the acting actor reference, reason, and timestamp. No capacity change is silent.

Invariant 12 — Id stability. A pool’s pool_id is set on declare_pool and never changes. An allocation, release, adjustment, or state-change event’s id is set when the event is written and never changes.

Invariant 13 — No id reuse. No two pools share a pool_id; no two events of the same class share an event id; no event id is reused across classes — across the lifetime of the system.

Invariant 14 — Action atomicity. Each action either commits all of its intended records — pool record (for declare_pool), audit-log event (for allocate, release, adjust_capacity, suspend_pool, resume_pool, close_pool), running-total or capacity or state update — or none, conditional on the deployment providing crash-atomic multi-record writes (see Edge cases → Crash atomicity (mid-action process failure)). A storage-failure rejection on any action guarantees no partial record, across any record type written by that action, has been persisted under the return-value path — the host’s write subsystem surfaced failure and the action did not commit; the crash-atomicity obligation extends the same all-or-none property to the no-return path in which the host fails between the audit-log append and the running-total update with no rejection delivered to the caller. Under both paths the total count of pool records is monotonically non-decreasing. A deployment that does not meet the crash-atomicity obligation can produce recovered states in which some of an action’s records committed and others did not; this is a deployment-side gap, not an atom-side guarantee failure.

Invariants 4 and 5 together give the bounded-arithmetic property — at every reachable state, 0 ≤ allocated ≤ capacity, conditional on the host obligations Invariant 4 names. This is the property composing patterns may treat as a precondition under those obligations; without it, every caller would have to defend the bound at every call site. Invariants 8, 9, 10, and 11 together give the successful-change-audit property — every successful change to the pool’s capacity, allocation count, or state is recorded as an event whose audit-identifier surface no atom-defined action modifies, in append-only insertion order under the atom’s actions; the composed-system view is bounded by Retention Window per Invariants 1, 8, and 9. The change history of successful operations is reconstructable from the records within the active retention window per the per-event and absolute modes named in Generation acceptance. Rejected operations produce no event at this layer; deployments requiring rejection visibility (PCI DSS Req. 10.2.4 invalid-access logging, breach-investigation traces of denied allocations) compose with Event Log around the atom’s call surface. See Edge cases and explicit non-goals → Rejection visibility for the boundary. Invariant 3 gives the terminal closure property — a closed pool cannot be silently reopened, and post-close cleanup via release is the only post-terminal mutation permitted.


Examples

The same atom, five domains, identical mechanic.

Airline — non-overbooking seat pool

A regional carrier configures its booking system to enforce strict no-overbooking for a 50-seat regional jet.

  1. declare_pool(capacity=50, declaring_actor_ref="rev_mgmt_4", reason="flight-NK1234-2026-05-14-seat-inventory") → pool_id = pool_f1234
  2. Reservations arrive. Each successful ticket purchase invokes allocate(pool_f1234, count=1, allocating_actor_ref="booking_svc") → allocation_event_id. After 50 successful allocates, allocated = 50.
  3. The 51st purchase attempt: allocate(pool_f1234, count=1, ...) → rejected(over-capacity). The booking system surfaces the failure to the customer; no allocation event is recorded.
  4. A cancellation: release(pool_f1234, count=1, releasing_actor_ref="booking_svc") → release_event_id. allocated = 49; one more seat is available.
  5. Post-departure, the carrier closes the pool: close_pool(pool_f1234, closing_actor_ref="rev_mgmt_4", reason="flight-departed-on-time") → state_change_id. Refund-driven releases within the carrier’s refund window — a deployment-defined operational window, not enforced at the atom — remain admissible against the closed pool; new bookings are not. The atom itself admits release in Closed indefinitely; the bounded window is the deployment’s policy.

Banking — credit-limit headroom

A retail bank configures a per-customer credit pool corresponding to the customer’s declared credit line.

  1. declare_pool(capacity=10000, declaring_actor_ref="credit_mgr_2", reason="customer-c882-credit-line-usd-10k") → pool_id = pool_c882
  2. The customer makes purchases. Each authorization invokes allocate(pool_c882, count=<amount in cents>, allocating_actor_ref="auth_svc") → allocation_event_id.
  3. A purchase that would exceed the limit: allocate(pool_c882, count=500000, ...) → rejected(over-capacity). The card network surfaces the decline to the merchant.
  4. Settlements (the underlying authorizations clearing as posted transactions) leave the running-total unchanged at this atom — the composing Provisional Commitment plus Settlement Posting pattern handles the per-authorization lifecycle.
  5. The customer’s credit limit is raised by the bank: adjust_capacity(pool_c882, new_capacity=15000, adjusting_actor_ref="credit_mgr_2", reason="credit-line-increase-approved-2026-05-14") → adjustment_event_id. New authorizations now consume against a capacity of $15,000.
  6. The customer closes the account: close_pool(pool_c882, closing_actor_ref="credit_mgr_2", reason="account-closure-customer-request-2026-05-14") → state_change_id. In-flight authorizations may still settle via release; new authorizations are rejected.

Healthcare — ward bed pool

A hospital ward configures a bed-management pool with capacity equal to the ward’s bed count.

  1. declare_pool(capacity=24, declaring_actor_ref="ward_admin_h7", reason="ward-3w-bed-inventory") → pool_id = pool_ward_3w
  2. Admissions invoke allocate(pool_ward_3w, count=1, allocating_actor_ref="admissions_svc"). Discharges invoke release(...).
  3. A renovation removes 4 beds for two weeks: adjust_capacity(pool_ward_3w, new_capacity=20, adjusting_actor_ref="ward_admin_h7", reason="renovation-rooms-308-311-closed-2026-05-14-to-05-28") → adjustment_event_id. If 22 patients are currently admitted (allocated = 22), the adjustment fails: rejected(over-allocated) — the operator must first transfer 2 patients out (releases) before lowering the capacity.
  4. A respiratory-illness surge triggers operational suspension of new admissions while the ward reorganizes: suspend_pool(pool_ward_3w, suspending_actor_ref="ward_admin_h7", reason="resp-illness-surge-cohort-reorganization-2026-05-14") → state_change_id. New admissions are rejected; existing patients can still be discharged (release).

Database operations — connection pool

A service operator declares a connection pool with capacity = 50 concurrent connections.

  1. declare_pool(capacity=50, declaring_actor_ref="ops_4", reason="primary-db-conn-pool-svc-orders") → pool_id = pool_conn_primary
  2. Each connection acquisition invokes allocate(pool_conn_primary, count=1, allocating_actor_ref="orders_svc") → allocation_event_id. The acquisition-event id is returned to the caller for use as a follow-up release key.
  3. Connection release invokes release(pool_conn_primary, count=1, releasing_actor_ref="orders_svc"). Releases are not bound to specific acquisition events at this atom’s layer — the count is the surface; the composing per-connection lifecycle (if needed) lives outside.
  4. The DB undergoes failover: suspend_pool(pool_conn_primary, suspending_actor_ref="ops_4", reason="failover-2026-05-14-0200-utc") → state_change_id. New connections are rejected during the window; in-flight connections may complete and release.
  5. Failover finishes: resume_pool(pool_conn_primary, resuming_actor_ref="ops_4", reason="failover-complete-2026-05-14-0235-utc") → state_change_id. Pool returns to Open.

Warehouse — inventory pool

A fulfillment center configures a per-SKU inventory pool tracking units physically on-hand.

  1. declare_pool(capacity=500, declaring_actor_ref="wh_mgr_3", reason="sku-laptop-z4500-fc-west") → pool_id = pool_sku_z4500
  2. Each order line invokes allocate(pool_sku_z4500, count=<order quantity>, allocating_actor_ref="oms_svc"). Successful allocates produce pick tickets in the composing system.
  3. An order is cancelled before fulfillment: release(pool_sku_z4500, count=<cancelled quantity>, releasing_actor_ref="oms_svc"). The units return to availability.
  4. A receiving event adds 100 units to inventory: adjust_capacity(pool_sku_z4500, new_capacity=600, adjusting_actor_ref="wh_mgr_3", reason="receiving-po-12399-100-units-2026-05-14") → adjustment_event_id. New orders can now consume against the higher capacity.

The mechanic is identical across all five. What differs: what a “unit” means (a seat, a cent of credit, a bed, a connection, a physical unit of stock), the rate of allocate/release calls, and the composing patterns that handle the per-allocation lifecycle.

Rejection paths

The domain examples above exercise over-capacity (airline step 3, banking step 3) and over-allocated (healthcare step 3) rejections. The remaining named rejection reasons are exercised by the following scenarios. These walk the rejection surface that callers and composing patterns must handle without producing an audit-log event at this layer (see Edge cases → Rejection visibility).

over-release — releasing more than is allocated. A connection-pool client mistakenly tracks its own release count and double-releases: release(pool_conn_primary, count=2, releasing_actor_ref="orders_svc") when allocated = 1. The atom rejects with over-release; allocated remains 1, no event is recorded. The caller’s coordination bug is signaled by the rejection reason; the running total stays consistent with reality.

suspended — allocate against a suspended pool. During the database failover window from the connection-pool example, a new request invokes allocate(pool_conn_primary, count=1, allocating_actor_ref="orders_svc") → rejected(suspended). The orders service retries after the resume event, or routes to a fallback. No allocation event is recorded; the pool’s running total is unchanged.

closed — allocate or adjust_capacity against a closed pool. After a carrier closes the flight pool, a late booking attempt: allocate(pool_f1234, count=1, ...) → rejected(closed). Separately, an operator attempts to revise capacity on the same closed pool: adjust_capacity(pool_f1234, new_capacity=55, ...) → rejected(closed). Both are rejected on state-validity before any field or arithmetic check; the pool’s records are unchanged.

not-known — action against an unknown pool_id. A caller passes a stale or typo’d pool reference: query(pool_garbage_id) → rejected(not-known). Same rejection for any action taking pool_id. The atom does not distinguish “never declared” from “purged by deployment policy” — the surface is the same.

already-closed / not-open / not-suspended — state-prereq violations on lifecycle actions. An operator double-closes a pool: close_pool(pool_c882, ...) → rejected(already-closed) on the second call. An operator attempts to resume a pool that’s already Open: resume_pool(pool_ward_3w, ...) → rejected(not-suspended). An operator attempts to suspend a closed pool: suspend_pool(pool_f1234, ...) → rejected(already-closed). Each communicates the specific state-prereq failure; no state change occurs.

invalid-request — malformed call against an otherwise-valid target. An operator passes a negative count to allocate: allocate(pool_conn_primary, count=-3, allocating_actor_ref="orders_svc") against an Open pool. Per the priority ordering, the state check passes (Open) and the field-format check rejects with invalid-request. Same surface for empty actor references, whitespace-only reasons, non-integer capacity values, and a no-op adjust_capacity whose new_capacity equals the pool’s current capacity — the call would not change the bound and is rejected at the boundary rather than admitted as an audit-log no-op.

storage-failure — durable-write failure surfaces as a named rejection. A transient write failure during allocate produces rejected(storage-failure). Per Invariant 14, no partial record is persisted: the running total is unchanged, no audit-log entry exists. The caller’s options are retry (typically via Duplicate Prevention composition to ensure idempotency) or treat the call as having failed cleanly.

Regulated adversarial scenarios

Three scenarios the atom must survive in regulated contexts:

Regulator audit — “show me every allocation that took the pool over its declared capacity.” An auditor querying a credit-line pool under SOX scope (or an airline seat pool under no-overbooking commitments, or a healthcare bed pool under licensed-capacity rules) asks the structural question: at any point in the pool’s history, did allocated exceed capacity? The answer is recoverable from records alone — the auditor replays the audit log in insertion order, maintaining the running (capacity, allocated) pair across each allocation event, release event, and capacity-adjustment event. Invariant 4 is the structural guarantee: there is no action sequence the atom accepts that produces allocated > capacity, so the audit query returns the empty set for a clean implementation. A non-empty result is evidence either of a bug in the atom’s enforcement or of a host-environment serialization failure under concurrency — both auditor-actionable findings. Invariants 8 and 9 (audit-log immutability and append-only insertion order) foreclose the possibility that a violation was recorded and then erased.

Disputed transaction — “the customer claims their credit limit was exceeded on this specific transaction; show me the running total and capacity at the event index of the disputed allocation.” The bank’s compliance team retrieves the allocation event whose allocation_event_id matches the disputed transaction’s pool-allocation reference. The investigator replays the audit log in insertion order up to (but not including) that event; the running (capacity, allocated) pair at that point is the answer to “what was the pool’s state when this allocation was admitted?” Invariant 11 (capacity-adjustment auditability) ensures the capacity in effect at that event is reconstructable — any prior adjust_capacity events name the prior and new capacities with attribution. If the running total + the disputed count exceeded capacity at that event index, the atom’s records will not show the allocation event at all (the precondition would have rejected with over-capacity); if the records do show the allocation event, the structural answer is that the allocation was admitted under the capacity then in effect.

Breach or incident investigation — “during the breach window, were any unauthorized allocations placed against the pool, or were any unauthorized capacity adjustments made?” An investigator filters the audit log by event index (or, when Trusted Timestamping is composed, by wall-time) and inspects each event’s *_actor_ref against the expected actor population. Unexpected attributions (allocations by an actor outside the authorized set, adjustments by an actor outside the operator set) are immediate findings. The append-only, immutable-event discipline (Invariants 8, 9) forecloses the possibility that an attacker altered the audit log to conceal unauthorized events; any gap in event-index continuity is itself a finding. The state-change events (Invariant 10) anchor the investigation to the pool’s state at each window boundary.


Generation acceptance

A derived implementation of Capacity Constraint Enforcement is acceptable — in the regulator-acceptance sense — when an external auditor, given the pool record set and its audit log together with the records of any composing patterns the deployment uses (Provisional Commitment commitments, Duplicate Prevention idempotency tokens, Actor Identity attestations, Audit Trail composite recordings, Retention Window archive manifests and retention-boundary snapshots, Permissions authorization decisions), can do all of the following without recourse to source code, runbooks, or developer narration. The “records alone” framing is bounded in three ways. First, this atom’s records suffice for per-event arithmetic verification within the active retention window with no external dependency. Second, absolute reconstruction across the entire pool lifecycle (the running (capacity, allocated, state) triple at any event index from declaration onward) requires the deployment’s Retention Window to produce boundary snapshots when its purge policy removes declare_pool or other early events; without those snapshots, absolute reconstruction is bounded out for the purged window and the auditor falls back to per-event consistency verification. Third, composing-pattern records supply the cross-reference surface (Provisional Commitment, Duplicate Prevention, Actor Identity), the rejection-visibility surface (Event Log), and the authorization decision surface (Permissions) that this atom does not own.

Reconstruct the pool’s successful-change history within the active retention window. Two reconstruction modes the records support, with different evidentiary scope:

Per-event consistency verification — records-alone verifiable from the active retention window with no external starting state. For each surviving event, the auditor verifies that the snapshots are internally consistent: for allocate events, allocated_after == allocated_before + count and allocated_after ≤ capacity_in_effect_at_this_event_index; for release events, allocated_after == allocated_before - count and allocated_after ≥ 0; for adjustment events, the prior_capacity / new_capacity pair is present and new_capacity differs from prior_capacity (the no-op-adjust rejection rule); for state-change events, the prior_state / new_state pair is present. The per-event symmetric snapshots (Outputs section) and Invariants 8, 9, 10, 11, and 14 together make this mode verifiable from records within the active retention window with no dependency on any pre-window history — each event is internally self-witnessing.

Absolute reconstruction — (capacity, allocated, state) at any event index. Replay the audit log forward in insertion order from declare_pool (canonical starting state: allocated = 0, capacity = declared_capacity, state = Open), maintaining the running triple across each event. This mode is records-alone verifiable for the lifetime of the pool only when declare_pool is among the surviving records. Under composition with Retention Window where purging has removed declare_pool (and possibly other early events), absolute reconstruction starting from the earliest surviving event requires a retention-boundary snapshot — the (capacity, allocated, state) triple immediately before the earliest surviving event — that the composing Retention Window pattern must produce alongside its purge action. Without the boundary snapshot, absolute reconstruction is bounded out for the purged window: the auditor falls back to per-event consistency verification (the mode above) and accepts that the absolute state at any purged-window index is not records-alone derivable from this atom’s records. The atom’s responsibility is the per-event snapshots that make per-event consistency verifiable; Retention Window’s responsibility is the boundary snapshot when its purging policy removes the starting record; together the two records sets restore absolute reconstruction. When the Trusted Timestamping composition binds insertion order to verifiable wall-time, both modes extend to wall-time queries on top of event-index queries; without that composition, both modes are event-index-authoritative and timestamps are advisory.

Verify the capacity constraint holds at every event index from records alone. For every allocation event in the log, the (allocated_before + count) must satisfy ≤ capacity_in_effect_at_this_event_index, and allocated_after must equal allocated_before + count. Invariant 4 is structurally enforced by the atom’s allocate precondition; the auditor’s query is a finite walk over the log returning the empty set for clean records. The auditor may verify Invariant 4 per-event by inspecting allocated_after ≤ capacity_in_effect directly — records-alone verifiable from the active retention window without dependency on pre-window history — or cumulatively by replay from declare_pool when that record survives; both modes are supported by the symmetric snapshot fields. The capacity in effect at any event index is derivable from the audit log: it is the capacity declared at declare_pool adjusted by every adjust_capacity event preceding the current event in insertion order when declare_pool is among the surviving records. Under Retention Window purge that has removed declare_pool, the capacity in effect at the earliest surviving event is supplied by the Retention Window boundary snapshot (see the reconstruction check above); from that snapshot forward, capacity-in-effect derivation proceeds in insertion order over surviving adjust_capacity events.

Verify the non-negativity invariant holds at every event index from records alone. For every release event, the count must satisfy ≤ allocated_before, and allocated_after must equal allocated_before - count and must be ≥ 0. Invariant 5 is enforced by the release precondition; the per-event verification mode is supported by the snapshot fields, and the structural guarantee mirrors Invariant 4.

Confirm every successful state change and capacity adjustment is attributed to an actor with a reason. Each state-change event (Invariant 10) and each capacity-adjustment event (Invariant 11) carries acting_actor_ref and reason. Allocation and release events carry allocating_actor_ref / releasing_actor_ref (no reason field — these are routine arithmetic operations). The auditor can trace every successful change to an attributing actor and, for policy-driven changes (suspend/resume/close/adjust), to a stated rationale. When *_actor_ref or reason fields have been scrubbed under Retention Window composition (per Invariant 8’s audit-identifier/attribution split), the arithmetic chain remains verifiable; the attribution surface is bounded to whatever the retention policy preserved. Attribution is not authorization. The atom’s records establish who acted; they do not establish that the actor was permitted to act. An auditor asking “was this release authorized?” reads Permissions’ records for the decision and this atom’s records for the action the decision admitted — the two surfaces compose, per Edge cases → Authorization and the Permissions Composition note.

Identify the composing patterns active in this deployment from cross-reference records. Whether Provisional Commitment is wired in for per-allocation lifecycle (the auditor inspects PC’s commitment records, each of which cross-references an allocation_event_id from this atom), whether Duplicate Prevention is wired in for idempotent allocation under retry (DP’s idempotency tokens point at the prior allocation_event_id), whether Actor Identity is wired in for non-repudiable attribution (AI’s attestations are keyed by event id), whether Trusted Timestamping is wired in for verifiable wall-time anchoring (TT’s anchor records reference event ids), whether Audit Trail is wired in for tamper-evident composite recording (Audit Trail composes Event Log + Actor Identity + Retention Window + Tamper Evidence around this atom’s event surface), and whether Retention Window is wired in for audit-log lifecycle (RW’s retention manifests name the policy in effect for this pool). Identification is from the composing pattern’s records, not from this atom’s records — the atom emits the event-id surface that composing patterns key against.

Rejection visibility is explicitly out-of-scope at this atom. An auditor asking “show me every rejected allocate during the breach window” or “how many over-capacity rejections did this pool emit?” cannot answer from this atom’s records alone — rejections produce no event at this layer. Deployments under PCI DSS Req. 10.2.4 (invalid-access logging) or whose breach-investigation surface requires denied-attempt visibility compose with Event Log around the atom’s call surface; Event Log records the call site, the rejection reason, and the actor reference, producing the rejection-visibility surface this atom does not. The Generation acceptance bar for rejection visibility is satisfied at the composed-system level, not by this atom in isolation.


Edge cases and explicit non-goals

What this atom does not cover:

Per-allocation identity. Units are fungible at this atom’s grain. An allocate of 5 units produces one allocation event with one id, not five sub-records or five allocation ids. If a caller needs to track specific resources (this seat, this bed, this connection handle), the composing Provisional Commitment atom supplies the per-allocation lifecycle; this atom supplies only the pool’s arithmetic. The boundary is sharp: Provisional Commitment owns “this specific resource is held for this specific requester for this specific window”; Capacity Constraint Enforcement owns “the running total against the pool’s bound.” Reservation Lifecycle is the composition that wires them together.

Fairness, priority, and contention policy. Two concurrent allocates against a pool with one unit of headroom resolve under the host environment’s serialization guarantees; whichever wins the race takes the unit, the loser receives over-capacity. The atom does not implement FIFO ordering, priority queueing, or any other fairness discipline. A deployment that needs fairness composes a Queueing or Priority Scheduling pattern in front of allocate.

Authorization. The atom attributes each action to the caller’s *_actor_ref field but does not constrain who may invoke which action. A caller with knowledge of pool_id and the current allocated value can drive the running total to zero via release calls they are not entitled to make; an actor outside the operator set can adjust capacity downward (subject only to the arithmetic precondition) or close a pool, and the records will show every such action faithfully attributed without recording whether the attribution was permitted. The atom’s actions accept any non-empty actor reference because authorization is a different concept — who is permitted to act on this pool is a separate state machine (role assignments, capability grants, scope checks) that recurs across every regulated atom and ought to compose with the host, not be absorbed into it. The composing pattern is Permissions, which sits in front of allocate, release, adjust_capacity, suspend_pool, resume_pool, and close_pool and rejects unauthorized callers before they reach this atom’s surface. Without that composition, the atom’s records satisfy attribution (Invariants 8, 10, 11 — who acted, with what reason, against what state) but not authorization (was the actor entitled to act); a regulator querying “was this release authorized?” reads Permissions’ records for the decision and this atom’s records for the action that the decision admitted. Deployments under regulators requiring explicit authorization controls (SOX segregation-of-duties for credit-line adjustments, HIPAA minimum-necessary for healthcare bed allocation, PCI DSS Req. 7 for least-privilege access to payment-related capacity pools) compose with Permissions; the atom contributes the call surface and the attribution field, Permissions contributes the authorization decision.

Preemption and eviction. The atom does not evict existing allocations to make room for new ones. A high-priority allocate request against a fully-allocated pool is rejected with over-capacity regardless of any priority signal; the caller’s options are to release something (which requires knowing what to release — a per-allocation concern) or to wait. Preemption logic — releasing the lowest-priority allocation to admit a higher-priority one — is a composing concern at a layer that has per-allocation identity to act on.

Capacity bursting, overcommit, and soft limits. Some operational systems permit short-term overcommit (the airline industry’s overbooking practice, the database engine’s connection-pool-with-burst headroom). This atom enforces a hard constraint and rejects on the bound; deployments needing overcommit compose with a separate Burst Capacity or Soft Limit pattern that maintains a tolerance margin and emits warning signals before hard rejection.

Allocation expiry and per-allocation lifecycle. The atom does not model allocations with a bounded lifetime. An allocate-without-corresponding-release leaves the units consumed indefinitely. A deployment that needs allocations to time out and auto-release composes with Provisional Commitment (which has Held/Confirmed/Released/Expired states) or with a Lease pattern; this atom handles the arithmetic regardless of which lifecycle pattern governs each allocation.

Resource semantics. What a “unit” represents — a seat, a bed, a dollar, a connection handle, a physical SKU — is a host-system policy decision encoded in the count values the caller passes. The atom does not interpret units beyond their arithmetic.

Pool migration, merging, and splitting. The atom does not provide actions to move units between pools or to merge two pools into one. A deployment that needs migration composes by closing the source pool and declaring a new one with adjusted capacity; the per-allocation re-allocation against the new pool is the composing system’s concern.

Notification on state change or near-capacity. Pool transitions (Open → Suspended, drained-condition reached) may be operationally significant signals downstream — page the on-call, throttle upstream traffic, route to a fallback pool. The atom emits state-change events to its audit log; propagating those events to consumers composes with Subscription and Notification.

Concurrency and atomicity (concurrent-call atomicity). Concurrent actions against the same pool_id resolve under the host environment’s serialization guarantees. Each action’s effects (running-total update, audit-log append) are atomic with respect to other concurrent calls — but the atom does not specify the serialization mechanism. Multi-action transactions (e.g., release-N-from-pool-A-and-allocate-N-to-pool-B atomically) belong to a Transaction composition.

Crash atomicity (mid-action process failure). Invariant 14 promises all-or-none commit signaled by storage-failure rejection when the host’s write subsystem surfaces failure as a return value. A crash — host process termination or kernel panic between (a) audit-log append and (b) running-total update, with no rejection returned to the caller — is a distinct failure mode the atom names as a deployment obligation. The deployment must provide crash-atomic multi-record writes: either a transactional store that commits or rolls back the action’s records atomically across host failure (so that crash recovery observes only the “all” or “none” state), or a write-ahead log with replay-on-recovery that achieves the same effect. Without that guarantee, a crash can leave Invariant 4 violable — the audit-log event may show an allocation that did not increment the running total, or vice versa, and a regulator asking “show me your evidence that no in-progress action at the moment of crash violated the capacity bound” cannot be answered from the atom’s records alone. The atom does not specify the mechanism (database transactions, append-only WAL, journaling filesystem, replicated state machine); the obligation lives with the deployment, and the deployment’s choice is auditable as part of the implementation’s claim to Generation acceptance. The caller’s reconciliation surface — “did my call succeed?” after a crash that swallowed the return value — composes with Duplicate Prevention (caller-supplied idempotency token surfaces the prior result without producing a second allocation) and with query against the pool’s post-recovery state.

Integer arithmetic precision. The atom traffics in non-negative integer capacity and positive integer counts; the load-bearing arithmetic invariant (Invariant 4) depends on allocated + count being computable without loss. Integer width (32-bit, 64-bit, arbitrary-precision) is a deployment-shaped concern. A deployment that uses fixed-width signed integers and admits allocated + count > MAX_INT could observe silent wraparound that violates Invariant 4 — the atom’s precondition would compare a wrapped (negative) sum against capacity, see the comparison pass, and commit an allocation that puts the running total above capacity. Implementations are expected to use overflow-safe arithmetic (arbitrary-precision integers, or fixed-width with explicit overflow detection that surfaces as invalid-request); the atom does not specify the mechanism but the obligation lives with the deployment. Similar consideration applies to release (allocated - count, which can’t go negative under the precondition, but the subtraction itself must be computed safely) and adjust_capacity (new_capacity ≥ allocated).

Id-generation discipline. The atom requires the deployment to produce pool_id and event-id values that are unique across the lifetime of the system: no two pools share a pool_id; no two events of the same class share an event id; no event id is reused across classes (Invariants 12 and 13). The atom does not specify the generation mechanism — UUIDv4 / UUIDv7, content-hashed identifiers, or a coordinated monotonic sequence generator are all viable — but the obligation lives with the deployment. A generator that admits collisions under concurrent declare_pool calls (e.g., a sequence counter without coordination across writers) or that re-uses ids after Retention Window purge violates Invariants 12 and 13, and the audit chain’s appeal to “the event identified by allocation_event_id = X” becomes ambiguous: a regulator who finds two events sharing an id has found evidence of a generator failure that invalidates downstream cross-references, and no atom-side discipline can compensate. The cross-reference surfaces this atom supports for composing patterns (Provisional Commitment commitments keyed by allocation_event_id, Actor Identity attestations keyed by event id, Audit Trail composite recordings keyed by event id, Permissions decisions keyed by call surface) depend structurally on id uniqueness. Implementations are expected to use a generator whose collision probability over the deployment’s lifetime is negligible — UUIDv4 is the typical floor for distributed deployments; in-database generators with serialized id allocation are appropriate when a single writer can coordinate. The obligation extends across deployments that perform Retention Window purge: a purged id must not be reused for a subsequently-declared pool or event, even though the originally-bearing record no longer exists in the active log.

Clock semantics. Timestamps on audit-log events come from an implicit clock. Skew, monotonicity, and timezone handling are deployment concerns. Insertion order — not timestamp order — is the authoritative ordering for “after,” “between,” and “most recent” references; Trusted Timestamping composes to bind insertion order to externally-verifiable wall-time.

Retention of audit-log entries and pool records. Invariants 1, 8, and 9 establish what the atom’s actions never modify or remove (the pool record itself; events’ audit-identifier surface; events’ append-only insertion order). The atom does not set the retention policy for how long pool records and audit-log entries remain queryable before archival or purge. Composing systems whose regulators require multi-year retention of capacity-management evidence (SOX-scope credit-limit pools, FRCP-scope inventory adjustments) compose with Retention Window. Under that composition the composed-system view of both surfaces — the pool record and the audit log — is bounded by the retention schedule; the atom’s Generation acceptance reconstruction is scoped to records within the active window, with archived history out-of-scope when the deployment maintains no archive. The split between scrubbing (Retention Window erasing PII-bearing *_actor_ref and reason fields while preserving the audit-identifier surface) and purging (Retention Window removing events or pool records entirely) is named in Invariants 1, 8, and 9 and elaborated in the Retention Window Composition note.

Rejection visibility. A rejected action — over-capacity, over-release, over-allocated, not-known, suspended, closed, not-open, not-suspended, already-closed, invalid-request, storage-failure — produces no event in this atom’s audit log. The atom’s records witness successful state changes; the attempt surface is invisible at this layer. The boundary is deliberate: the load-bearing arithmetic invariant is about pool state, not about call attempts, and recording every rejection (especially not-known and invalid-request, which are typically caller-side bugs in volume) would conflate “the pool’s state changed” with “someone tried.” Deployments whose regulators require visibility into rejected attempts compose with Event Log around the atom’s call surface: Event Log records the call site, the rejection reason, the actor reference, and the wall-time, producing a deployment-grain attempt journal alongside the atom’s pool-grain change journal. The PCI DSS Req. 10.2.4 obligation (logging invalid logical access attempts), the breach-investigation surface for denial-pattern probing, and the regulator-audit question “how many over-capacity rejections did this pool emit during the window?” are all satisfied at the composed-system level through Event Log, not by this atom in isolation. The atom names the rejection reasons precisely so that the composing Event Log has a stable vocabulary to record against.

Cross-pool invariants. The atom maintains per-pool invariants. Cross-pool rules (e.g., “the sum of allocations across all flight pools serving a corridor cannot exceed the carrier’s network-wide cap”) are composing concerns at a layer that aggregates over pools.

Where the atom breaks down: when the underlying resource is not actually fungible at any meaningful grain (every seat is distinct because of legroom or premium status — at which point per-allocation identity belongs at this layer too, which is a sign the deployment wants Provisional Commitment, not this atom); when capacity is not a single integer but a multi-dimensional vector (memory bytes and CPU shares and network bandwidth — a generalized resource-bundle pool, not the single-resource pool this atom models); when the constraint must be probabilistic rather than hard (a TCP-style admission control with backoff — that’s a Rate Limiter pattern, not this atom).


Composition notes

Capacity Constraint Enforcement is freestanding and is designed to compose with other atoms rather than absorb their concerns:

  • Provisional Commitment — for the per-allocation lifecycle. The composing system calls allocate on a Capacity Constraint pool at the moment Provisional Commitment moves a commitment into Held; calls release at the moment the commitment moves to Released or Expired; the Confirmed transition does not release (the unit remains consumed in the binding allocation). The composition is realized as the Reservation Lifecycle application (C9 — forthcoming as a distinct composition once authored; the existing Idempotent Reservation composition is a precursor that uses Provisional Commitment + Duplicate Prevention but does not yet wire pool arithmetic). The boundary: Provisional Commitment owns per-commitment state and the absorbing terminal transitions; Capacity Constraint Enforcement owns the running total and the bound.
  • Duplicate Prevention — for idempotent allocation under retry. The composing system supplies an idempotency token (a client-supplied token that makes repeated submissions safe); on a retry of allocate with the same token, Duplicate Prevention returns the prior allocation_event_id rather than producing a second allocation. The atom itself is not idempotent — a retry without the composition produces two allocations and double-counts the resource.
  • Event Log — for two distinct deployment-grain journals built around the atom’s call surface. First, a unified system-wide event stream that includes pool-management events (the successful state changes this atom records internally) alongside other systems’ events; the atom’s internal audit log is the canonical record at the pool’s grain, Event Log is the journal at the deployment’s grain. Second, the attempt-journal that captures rejected calls — Event Log records the call site, rejection reason, actor, and wall-time for every over-capacity, over-release, over-allocated, suspended, closed, invalid-request, storage-failure, etc. that this atom emits. The second use case is what makes the atom’s rejection-visibility boundary (see Edge cases → Rejection visibility) tractable: deployments under PCI DSS Req. 10.2.4 or with breach-investigation requirements for denied-attempt visibility wire Event Log around the call surface and read the rejection journal from there.
  • Actor Identity — for non-repudiable attribution. The atom’s *_actor_ref fields supply attribution; Actor Identity supplies the cryptographic or procedural binding that makes the attribution survive a regulated audit. Each allocate, release, adjust_capacity, suspend_pool, resume_pool, and close_pool action’s event id is the surface Actor Identity attests against.
  • Permissions — for authorization, the boundary this atom does not enforce (see Edge cases → Authorization). Where Actor Identity binds an action to a specific actor (who acted?), Permissions decides whether that actor was entitled to act (was the action permitted?). The composition sits in front of allocate, release, adjust_capacity, suspend_pool, resume_pool, and close_pool; an unauthorized caller is rejected at the Permissions layer before reaching this atom, so the atom’s records contain only actions whose Permissions check passed. Deployments under regulators requiring explicit authorization controls (SOX segregation-of-duties for capacity adjustments, HIPAA minimum-necessary for healthcare allocations, PCI DSS Req. 7 for least-privilege access to payment-related capacity pools) compose this atom with Permissions and Actor Identity together — Permissions for the authorization decision, Actor Identity for the non-repudiable attribution of the admitted action.
  • Trusted Timestamping (forthcoming) — for binding the atom’s insertion-order audit log to externally-verifiable wall-time. The atom’s recorded_at timestamps are best-effort wall-time metadata from the implicit clock; under skew or clock adjustment they may not be monotonic, and insertion order is authoritative for the atom’s own consistency. Trusted Timestamping composes by anchoring event ids (or batches of event ids) to externally-verifiable wall-time, producing a record that both (a) the event was recorded at the claimed wall-time and (b) the insertion order is consistent with monotonic wall-time. Deployments whose regulators audit wall-time claims (SOX-scope material transactions with end-of-period cutoff, healthcare event-time attribution under HIPAA, payment-network settlement windows) compose Trusted Timestamping to make the wall-time surface defensible. Without that composition, the Clock semantics edge case applies: timestamps are advisory, insertion order is authoritative.
  • Retention Window — for governing how long audit-log entries and pool records themselves remain actively accessible, including the distinction between scrubbing (erasing personally-identifying attribution while preserving the audit-identifier surface) and purging (removing entries entirely). Invariants 1, 8, and 9 each name the atom’s contribution (no atom-defined action removes a pool record or modifies/removes an event) and acknowledge that the composed-system view differs under retention schedules: Invariant 1 covers the pool record; Invariant 8 covers the event-level attribution/audit-identifier split; Invariant 9 covers the audit log’s append-only discipline. Scrubbing scope: when the composing deployment’s *_actor_ref or reason fields contain personally-identifying information (a credit-line pool’s declaration_reason referencing a customer by id; a healthcare pool’s reason naming a patient cohort), Retention Window may erase those fields under GDPR Article 17 or post-retention obligations. The audit-identifier surface that survives scrubbing is: pool_id, all event ids (allocation_event_id, release_event_id, adjustment_event_id, state_change_id), declared_at, per-event recorded_at, arithmetic fields (capacity, count, allocated_before, allocated_after, prior_capacity, new_capacity, prior_state, new_state), and event-class indicator. The arithmetic chain remains reconstructable across scrubbing — Invariant 4 is verifiable from the scrubbed records. Purging scope: Retention Window may additionally remove entries entirely from the active log under post-retention regulatory schedules, optionally moving them to an archive maintained by the deployment. Once purged, the arithmetic chain for pre-purge history is no longer reconstructable from active records; Generation acceptance scopes reconstruction to the active retention window (see Generation acceptance preamble). Deployments that need verifiable pre-retention history must maintain the archive; deployments that operate under purge-without-archive (regulatory minimums met by the active retention) accept the bounded reconstruction surface.
  • Audit Trail — the canonical regulated-audit composition (Event Log + Actor Identity + Retention Window + Tamper Evidence) wrapped around the atom’s event surface to produce tamper-evident composite recording. Where the Event Log composition supplies a deployment-grain journal and the Actor Identity composition supplies attribution binding, Audit Trail supplies the composite — including the Tamper Evidence layer that makes any post-hoc modification to the recorded event stream detectable. Deployments whose regulators require tamper-evident audit (SOX §404 records-alone-defensible evidence, PCI DSS Req. 10.5 audit-trail integrity, GDPR Article 30 records-of-processing under tamper-evident discipline) compose Audit Trail rather than Event Log alone. The atom’s contribution to the composition is the event-id surface and the immutable audit-identifier fields under Invariant 8; Audit Trail layers the other obligations on top.
  • Subscription + Notification — for propagating pool state changes (Open → Suspended, drained-condition reached, capacity adjusted) to downstream consumers. Composes via the existing Notification Fanout pattern.
  • Burst Capacity / Soft Limit (forthcoming) — for deployments that need to permit short-term overcommit with warnings before hard rejection. Wraps allocate with a tolerance margin and emits warnings before rejecting at the burst bound.
  • Queueing / Priority Scheduling (forthcoming) — for deployments that need fairness or priority under contention. Sits in front of allocate and orders concurrent requests before they hit the atom’s serialization layer.
  • Reservation Lifecycle (C9) (forthcoming) — the canonical composition wiring this atom with Provisional Commitment and Duplicate Prevention to produce a full reservation arc.

Standards references

Capacity Constraint Enforcement is a utility primitive; no single regulator owns capacity enforcement directly. Its standards relevance comes through composition with regulated patterns whose audit surface relies on the running-total invariant.

  • ISO 9001:2015 §8.1 (Operational planning and control) — production systems must operate within declared capacity boundaries; the atom is the structural enforcement for that obligation when the constrained resource is a production asset (manufacturing-line slots, certified-operator headroom, equipment utilization).
  • Basel III Liquidity Coverage Ratio (BCBS 238) — bank credit-line and counterparty-limit pools must be enforced as hard constraints with auditable adjustments; the atom is the operational form of a regulator-facing credit-limit headroom pool.
  • Sarbanes-Oxley §404 (Internal Control over Financial Reporting) — where confirmed allocations against a pool are material to the books (credit-limit consumption flowing to the balance sheet, inventory allocation flowing to cost-of-goods-sold), the controls around pool adjustments and the audit trail of who-allocated-what-when become SOX-scope. Composes with Audit Trail to produce the records-alone-defensible evidence §404 attestations require.
  • PCI DSS Requirement 10 (Logging and monitoring) — when the pool governs payment-related capacity (a payment-gateway connection pool, a card-authorization headroom pool), every successful allocation and state change must be logged with attribution; this atom’s audit-log invariants supply the structural form for the successful-change surface. Req. 10.2.4 specifically requires logging of invalid logical access attempts (rejected calls), which this atom does not produce events for; the composing Event Log around the atom’s call surface (see Composition notes → Event Log) records the rejection journal. The full PCI DSS Req. 10 obligation is satisfied by the atom + Event Log composition, not by the atom alone.
  • The Joint Commission, Provision of Care, Treatment, and Services — healthcare bed-management and ward-capacity standards require capacity changes (closures for renovation, surge expansions) to be auditable with attribution and reason. The atom’s adjust_capacity event-recording discipline is the operational form.
  • GDPR Article 30 (Records of processing) — where the pool’s allocation events touch personal data (per-customer credit-line pools, per-patient bed allocations referencing the patient by id), the audit log is itself a processing activity subject to controller-records obligations. Composes with Audit Trail and Retention Window for the full obligation surface.
  • Authorization-related standards (SOX §404 segregation-of-duties, HIPAA Privacy Rule §164.508 minimum-necessary, PCI DSS Requirement 7 restrict access by business need-to-know) — these regimes require enforcement of who may act, not merely attribution of who did act. This atom records the attribution surface (*_actor_ref) for every successful action but does not constrain who may invoke which action; the authorization decision composes with Permissions (see Edge cases → Authorization and the Permissions Composition note). Composing with Permissions and Actor Identity together produces the was-permitted-and-was-attested surface these standards require — Permissions for the decision that admitted the call, Actor Identity for the non-repudiable record of who the decision admitted.

The atom inherits from:

  • Daniel Jackson, The Essence of Software — the freestanding-atom posture and the explicit refusal to absorb per-allocation identity, fairness policy, preemption, and overcommit.
  • Eiffel’s design-by-contract — preconditions on each action, named rejection reasons, and the preserve-by-precondition discipline (rejecting actions that would violate an invariant rather than silently clamping).
  • Database connection pooling and operating-system semaphore conventions — the count-up / count-down arithmetic the atom abstracts; here exposed as visible business state rather than hidden inside a transactional or kernel primitive.
  • Token-bucket and leaky-bucket rate-limiter constructions — for the kinship the atom has with rate-limit enforcement; the rate-limit pattern is a sibling primitive with time-varying capacity rather than a fixed bound.

Status

grounded (passed all required review passes and is stable enough to generate from) — 2026-05-20 — capacity arithmetic atom; load-bearing invariant allocated ≤ capacity enforced by preconditions on allocate and adjust_capacity; suspend/resume/close state machine; audit-log discipline with the two-surfaces split (audit-identifier vs. attribution lifecycles); four host obligations named (serializable per-pool execution, overflow-safe integer arithmetic, crash-atomic multi-record writes, system-wide id uniqueness); regulated overlay complete. Foundation round complete (Pass 1 GRID + 1 foundational finding closed; Pass 2 EOS clean; Pass 3 Linus + 5 foundational findings closed). Phase 4 Opus clearance gate (Angry Torvalds X2) ran twice on 2026-05-15: round 1 closed 11 foundational findings + 2 sub-finding style notes; round 2 closed 3 foundational + 5 refining + 1 rhetorical. Grounded at this revision per the 93%-good threshold (PRESSURE_TESTING.md §”The 93%-good grounding threshold”): foundational density after round 2 = 0. Authored 2026-05-14; revised 2026-05-15 (round 1); revised 2026-05-15 (round 2).


Lineage notes

Capacity Constraint Enforcement is atom #8 in the ROADMAP’s draft order and the third entry in atoms/resource-lifecycle/ after Provisional Commitment and Soft Delete. The closest existing pattern is Provisional Commitment — same category, same kind of resource-encumbrance concern — and the draft mirrors its shape, identity-model discipline, and authoring conventions. The regulated-overlay conventions (Regulated adversarial scenarios, Generation acceptance) are included per the methodology’s required-when clause — three of the five domain examples (airline, banking, healthcare) invoke regulated domains. The conventions are inherited from the methodology directly rather than re-derived from predecessor patterns.

The composing forthcoming-link debts named in this draft are: Reservation Lifecycle (C9), Burst Capacity / Soft Limit, Queueing / Priority Scheduling. None are yet authored. The existing Composition notes in Provisional Commitment named Capacity Constraint Enforcement as a forthcoming composing atom; that link will resolve in Provisional Commitment’s next touch-triggered re-pass.

Pass 1 — GRID. Clean (1 finding closed in-pattern).

Nine MUSE v1.1 nodes resolved; reference graph walked (Friction → Flow; Decision → State/Behavior; Proof → Intent).

  • F-P1.1 — Release-in-Closed silent disagreement between action signature and transitions table — foundational → action signature, Decision points for release, and Invariant 3 now all explicitly state release is admitted in every state including Closed for cross-pattern unwinding.

Pass 2 — EOS. Clean (no over-absorptions).

Tested: audit log (kept internal — tightly bound to pool lifecycle); Open/Suspended/Closed state machine (kept internal — Suspended means “rejects allocate”); idempotency (external — Duplicate Prevention); non-repudiation (external — Actor Identity); fairness/preemption (external — Queueing forthcoming); burst/overcommit (external — Soft Limit forthcoming); notification (external — Subscription + Notification); retention (external — Retention Window); per-allocation lifecycle (external — Provisional Commitment).

Pass 3 — Linus. Clean (5 findings, all foundational, closed in-pattern).

  • F-P3.1 — Priority ordering listed arithmetic before field-format — foundational → reordered to not-known → state-validity → field-format → arithmetic → store; rationale in-line.
  • F-P3.2 — Integer overflow / arithmetic precision unaddressed — foundational → new Integer arithmetic precision edge case; Invariant 4 made contingent on host overflow-safety.
  • F-P3.3 — Release-in-Closed defense conflated two concerns — foundational → Invariant 3 rationale rewritten as cross-pattern data consistency.
  • F-P3.4 — Regulated overlay missing despite required-when clause applying — foundational → Regulated adversarial scenarios (3 reads) + Generation acceptance (5 checks) added; inherited from methodology directly.
  • F-P3.5 — Retention Window scrubbing surface unspecified — foundational → Retention Window Composition note names the immutable audit-identifier surface vs. the scrubbable attribution surface.

Phase 4 — Opus clearance gate (Angry Torvalds X2). Two rounds on 2026-05-15.

Round 1 — 11 foundational findings + 2 sub-finding style notes; all closed in-pattern. Pass 1 and Pass 2 clean at standard intensity; Pass 3 at X2 attacked defenses. Gate ran with fresh-reader discipline modulo one disclosed priming caveat (prior chat had named four contested decisions by category without rationale).

  • F-R1.1 — Rejection visibility absent from records; regulated-overlay claims assumed it — foundational → Rejection visibility edge case; Generation acceptance preamble scoped; PCI DSS Req. 10.2.4 framed as composed-system obligation; Event Log composition note covers both successful-event and rejection-attempt journals.
  • F-R1.2 — Allocate events lacked the running-total snapshot release events carried — foundational → symmetric allocated_before / allocated_after on both classes.
  • F-R1.3 — Priority-ordering defense covered field-before-arithmetic but not state-before-field — foundational → each ordering decision now defended in-line separately.
  • F-R1.4 — Crash atomicity conflated with concurrent-call atomicity — foundational → split into two edge cases; preamble names three host obligations.
  • F-R1.5 — Invariant 8 overstated immutability vs. Retention Window scrubbing — foundational → Invariant 8 split into immutable audit-identifier surface and mutable attribution surface.
  • F-R1.6 — Invariant 9 promised monotonic growth that Retention Window purge breaks — foundational → Invariant 9 qualified; Generation acceptance scoped to active retention window; scrubbing vs. purging split formalized.
  • F-R1.7 — Trusted Timestamping referenced in body but absent from Composition notes — foundational → added as *(forthcoming)* entry; wall-time-anchor role named.
  • F-R1.8 — Audit Trail in Standards refs but not Composition notes — foundational → added as distinct entry; relationship to Event Log clarified.
  • F-R1.9 — 8 of 10 rejection paths unexercised in examples — foundational → Rejection paths subsection added covering all 8.
  • F-R1.10 — Preamble blockquote elided host conditions — foundational → blockquote names three host obligations (fourth added in round 2).
  • F-R1.11 — Generation acceptance “records alone” overstated for check #5 — foundational → preamble scoped; check #5 names cross-reference fields each composing pattern keys against.

Sub-finding style notes (neither blocking nor counted toward foundational class):

  • Airline example “24-hour” release window → rephrased to “carrier’s refund window — a deployment-defined operational window” (atom admits release in Closed indefinitely).
  • Action-signature rejection-reason listing order → signatures are reference enumerations not check sequences; not changed.

Round 2 — 9 findings (3 foundational, 5 refining, 1 rhetorical); all closed in-pattern. Strict fresh-reader discipline against spec body lines 1–435 before Lineage notes read. Pass 1 and Pass 2 clean.

  • F-R2.1 — Authorization silently absent; attribution mis-sold as covering it — foundational → Authorization edge case; Permissions Composition note; Generation acceptance check #3 split: attribution ≠ authorization.
  • F-R2.2 — Invariants 4 & 14 conditionality landed in preamble (R1.F10 fix), not on the invariant texts — refining → conditionality moved into Invariant 4 & 14 statements with edge-case cross-refs.
  • F-R2.3 — “At this atom’s grain” qualifier on Invariants 8/9 was tautological prose — rhetorical → Invariants 8 & 9 rewritten to foreground the operational composing-side reality; “under this atom’s actions” now load-bearing rather than tautological.
  • F-R2.4 — Invariant 1 lacked the grain-aware framing R1 applied to Invariants 8/9 — refining → Invariant 1 mirrors Invariant 9’s structure; Retention Window scope extended to pool records.
  • F-R2.5 — query return shape (4-field projection) contradicted Outputs section (full record) — refining → Outputs section distinguishes persisted pool record from runtime read surface; projection-vs-record split explicit.
  • F-R2.6 — No-op adjust_capacity (new == current) silently admitted; asymmetric with allocate/release positive-count rule — foundational → rejected as invalid-request across Inputs/Outputs, Decision points, State transitions, Behavior, Rejection paths, Generation acceptance per-event check.
  • F-R2.7 — Generation acceptance reconstruction needed a starting snapshot the atom didn’t specify — refining → reconstruction split into per-event consistency (records-alone from active window) and absolute reconstruction (requires Retention Window boundary snapshot when declare_pool purged).
  • F-R2.8 — Uniform validation rule was thin for strings — foundational → expanded with codepoint length, rejection of control / zero-width / bidi-override characters, no-normalization and no-case-folding stances.
  • F-R2.9 — System-wide id-uniqueness deployment obligation unstated — refining → preamble names a fourth host obligation; Id-generation discipline edge case added.

Grounding decision — 2026-05-15. Round 2 closed 3 foundational findings (F-R2.1, F-R2.6, F-R2.8); refining and rhetorical findings closed in-pattern alongside. Foundational density after round 2 = 0 → grounded per the 93%-good threshold (PRESSURE_TESTING.md §”The 93%-good grounding threshold”). Touch-rule applies on any future edit; methodology amendment was applied to PRESSURE_TESTING.md in the same session that closed round 2.

Scheduled rescan: 2026-05-20 — clean.


Grace Commons — open foundation for business logic patterns.

This site uses Just the Docs, a documentation theme for Jekyll.