Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ Gate: legacy versus typed authorization decisions, hashes, approvals, receipts a

Progress (2026-07-13): the `current` checkout has unwired ring, safe sealed-page
oracle, append-only published-prefix, and failure-atomic single-page admission
prototypes under Proposed ADR-0006 through ADR-0009. The volatile composite
prototypes under Proposed ADR-0006 through ADR-0009, and a design-only
Proposed ADR-0010 specifying bounded page rotation with generation-tagged
reuse (fixed pool of P slots, epoch-addressed, single released-epoch
reclamation edge, typed PageQuotaExhausted backpressure) — no rotation
code exists yet. The volatile composite
validates before reservation, Release-publishes the page before the ring,
withholds capacity until a must-use validated frame lease commits, and reports
clean, faulted, and orphaned-prefix terminal states. Test sources include safe
Expand Down
7 changes: 4 additions & 3 deletions docs/Documentation_Quality_Report.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This inventory makes the all-documentation improvement program measurable. Each
|---|---:|---:|---:|
| authoring | 2 | 90% | 0 |
| component | 15 | 96% | 0 |
| decision | 11 | 95% | 0 |
| decision | 12 | 94% | 0 |
| flow | 11 | 95% | 0 |
| guide | 53 | 67% | 38 |
| landing | 10 | 93% | 0 |
Expand All @@ -21,7 +21,7 @@ This inventory makes the all-documentation improvement program measurable. Each
| reference | 11 | 85% | 0 |
| runbook | 7 | 100% | 0 |

**Total:** 127 active Markdown pages · **Migration backlog:** 38 pages below 75%.
**Total:** 128 active Markdown pages · **Migration backlog:** 38 pages below 75%.

## Scoring signals

Expand Down Expand Up @@ -88,7 +88,8 @@ Pages are sorted by structural coverage, then path. Improve factual accuracy and
| [adr/0007-sealed-generation-tagged-slab-pages.md](adr/0007-sealed-generation-tagged-slab-pages.md) | decision | 267 | A | 100% |
| [adr/0008-append-only-published-prefix-slab-pages.md](adr/0008-append-only-published-prefix-slab-pages.md) | decision | 409 | A | 100% |
| [adr/0009-failure-atomic-slab-ring-admission.md](adr/0009-failure-atomic-slab-ring-admission.md) | decision | 427 | A | 100% |
| [adr/index.md](adr/index.md) | decision | 51 | B | 83% |
| [adr/0010-bounded-page-rotation-generation-reuse.md](adr/0010-bounded-page-rotation-generation-reuse.md) | decision | 289 | B | 83% |
| [adr/index.md](adr/index.md) | decision | 53 | B | 83% |
| [adr/template.md](adr/template.md) | decision | 46 | A | 100% |
| [AegisAgent_Agent_Cage.md](AegisAgent_Agent_Cage.md) | guide | 581 | B | 78% |
| [AegisAgent_Agent_SOC_Design.md](AegisAgent_Agent_SOC_Design.md) | guide | 757 | D | 56% |
Expand Down
2 changes: 1 addition & 1 deletion docs/Implementation_Status.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ separate documentation change.

| Artifact | Status | Evidence in this checkout | Authority / traffic | Remaining gates |
|---|---|---|---|---|
| Unwired SPSC, slab-page, published-prefix, and single-page admission prototypes | current | `lib/event/`; ring FIFO/full/wrap/drop/layout plus cancelable permits and commit-delayed claims; safe sealed and short-trace differential oracles; packed page publication; page-before-ring admission; must-use frame leases; clean/faulted/orphan terminal checks; native stress; shipping-algorithm Loom; Miri-oriented borrow/drop tests; ASan/TSan CI lanes defined; zero-allocation append/resolve and admission/claim checks; ADR-0006 through ADR-0009 | No production or `shadow` traffic; cannot carry protected evidence; volatile admission is not a receipt or durability acknowledgement; no performance claim | Formal ADR acceptance/security review, green hosted sanitizer artifacts, UBSan support, authenticated registry, bounded page rotation/outstanding pages, WAL durability/replay, generation reuse/epochs, NUMA-owner reclamation, priority lanes, production shadow wiring, release-artifact rollback, qualification |
| Unwired SPSC, slab-page, published-prefix, and single-page admission prototypes | current | `lib/event/`; ring FIFO/full/wrap/drop/layout plus cancelable permits and commit-delayed claims; safe sealed and short-trace differential oracles; packed page publication; page-before-ring admission; must-use frame leases; clean/faulted/orphan terminal checks; native stress; shipping-algorithm Loom; Miri-oriented borrow/drop tests; ASan/TSan CI lanes defined; zero-allocation append/resolve and admission/claim checks; ADR-0006 through ADR-0009 | No production or `shadow` traffic; cannot carry protected evidence; volatile admission is not a receipt or durability acknowledgement; no performance claim | Formal ADR acceptance/security review, green hosted sanitizer artifacts, UBSan support, authenticated registry, bounded page rotation/outstanding pages + generation reuse (design Proposed as ADR-0010, no prototype yet), WAL durability/replay, epochs for any multi-reader future, NUMA-owner reclamation, priority lanes, production shadow wiring, release-artifact rollback, qualification |
| Thread-per-core reactor | target | `ARCHITECTURE.md`, `docs/LLD.md` | None | runtime ADR, core-affinity/io_uring implementation, migration and benchmark gates |
| HCMT telemetry store | target | `ARCHITECTURE.md`, `docs/LLD.md` | None; SQL remains authoritative/current | WAL/segment ADR, recovery corpus, dual write, shadow equality, qualification |

Expand Down
288 changes: 288 additions & 0 deletions docs/adr/0010-bounded-page-rotation-generation-reuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
# ADR-0010: Bounded page rotation with generation-tagged reuse

**Status:** Proposed
**Date:** 2026-07-14
**Issue/PR:** pending

> **Design-first ADR.** No implementation accompanies this document. Per
> [architecture.md](../architecture.md) §14 and the ADR index rule, a Proposed
> ADR permits only unwired prototypes, and this one intentionally lands before
> any prototype so the rotation state machine is reviewed as a design, not as
> a diff. ADR-0009 §"Revisit when" required exactly this stop.

## Context

ADR-0009's `VolatileAdmissionChannel<N>` binds one preallocated
published-prefix page (ADR-0008) to one SPSC descriptor ring (ADR-0006). When
the page's byte or descriptor capacity is exhausted the lane is finished: the
typed `page full` error is terminal for the channel's useful life, and the
producer's only recovery is to tear down and rebuild the composite, losing the
ring, sequence continuity, and the consumer binding.

A production event fabric cannot run on one page per lane lifetime. It needs
the producer to continue admitting into a fresh page while the consumer
finishes draining the previous one — with the number of simultaneously live
pages **bounded by construction**, page memory **reused** rather than
reallocated, and reuse **provably safe** against stale descriptors addressing
a recycled page (the ABA problem).

Scope boundaries inherited from ADR-0009 remain: one producer, one consumer,
volatile, process-local, unwired, non-authoritative. This ADR adds only
rotation, bounded outstanding pages, and generation-tagged reuse. It does
**not** add WAL durability/replay, authenticated registry lookup, NUMA pool
placement policy, priority lanes, crossbeam-epoch, multi-producer or
multi-consumer topology, or any protected-evidence semantics.

## Decision

Add a `RotatingAdmissionChannel<N, P>` in `aegis-event`: one SPSC descriptor
ring bound to a **fixed pool of `P >= 2` identically-configured page slots**,
all preallocated at construction. No allocation, deallocation, or `Arc`
reference-count traffic occurs after `split`.

### Page epochs and slot addressing

Pages are identified by a monotonically increasing **page epoch** `e`
(`u64`, starting at 0). Epoch `e` occupies pool slot `e mod P` and stamps its
descriptors with `arena_generation = e as u32` (wrapping). The consumer
recovers the slot index from a descriptor as
`descriptor.arena_generation as u64 mod P` and then requires **exact
generation equality** with the slot's currently-bound page before any
Comment on lines +45 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is a potential correctness issue in the slot addressing logic when the epoch e exceeds $2^{32}$ if P is not a power of two (or more generally, a divisor of $2^{32}$).\n\nSince arena_generation is stored as e as u32 (wrapping), the consumer recovers the slot index using descriptor.arena_generation as u64 mod P, which is equivalent to $(e \bmod 2^{32}) \pmod P$. However, the producer assigns the slot using e mod P.\n\nIf P is not a divisor of $2^{32}$ (for example, if P = 3), then $(e \bmod 2^{32}) \pmod P \neq e \pmod P$ for $e \ge 2^{32}$.\nSpecifically, for $e = 2^{32}$:\n- Producer slot: $2^{32} \bmod 3 = 1$\n- Consumer slot: $(2^{32} \bmod 2^{32}) \bmod 3 = 0 \bmod 3 = 0$\n\nThis discrepancy will cause the consumer to look in the wrong slot, leading to a generation mismatch and a terminal failure.\n\nRecommendation:\nExplicitly restrict P to be a power of two (e.g., $P = 2^k$). This ensures that P always divides $2^{32}$ (for any $P \le 2^{32}$), making the modulo mapping wrapping-safe, while also allowing the compiler to optimize the modulo operations to fast bitwise ANDs (e & (P - 1)).

Suggested change
Pages are identified by a monotonically increasing **page epoch** `e`
(`u64`, starting at 0). Epoch `e` occupies pool slot `e mod P` and stamps its
descriptors with `arena_generation = e as u32` (wrapping). The consumer
recovers the slot index from a descriptor as
`descriptor.arena_generation as u64 mod P` and then requires **exact
generation equality** with the slot's currently-bound page before any
Pages are identified by a monotonically increasing **page epoch** `e`\n(`u64`, starting at 0). Epoch `e` occupies pool slot `e mod P` (where `P` is restricted to a power of two) and stamps its\ndescriptors with `arena_generation = e as u32` (wrapping). The consumer\nrecovers the slot index from a descriptor as\n`descriptor.arena_generation as u64 mod P` and then requires **exact\ngeneration equality** with the slot's currently-bound page before any

descriptor-cell or payload access — the same identity check ADR-0007/0008
already enforce, now doing double duty as the reuse guard.

Sequence space is continuous across pages: page epoch `e+1` is constructed
with `first_sequence` equal to the sealed page `e`'s `next_sequence`, so ring
sequence and page sequence remain a single unbroken wrapping sequence exactly
as in ADR-0009.

### Producer rotation algorithm

Rotation is attempted only inside admission, only on the typed
`page full` / `descriptor full` validation results, and **before** ring
reservation — a rotated-then-admitted event keeps ADR-0009's phase discipline
unchanged:

```text
validate payload length against the ACTIVE page
on page/descriptor full:
Acquire-load consumer released_epoch [A]
require active_epoch + 1 <= released_epoch + P (slot free?)
on failure: return typed PageQuotaExhausted — nothing mutated
Comment on lines +69 to +71

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is an initialization/sentinel bug in the quota check protocol.\n\nIf released_epoch is initialized to 0, the producer's check active_epoch + 1 <= released_epoch + P will falsely succeed for the first P rotations even if the consumer has not actually released any epochs yet.\n\nFor example, if P = 2 and released_epoch is initialized to 0:\n1. Producer starts at active_epoch = 0 (slot 0).\n2. Producer rotates to active_epoch = 1 (slot 1): 1 <= 0 + 2 (True).\n3. Producer rotates to active_epoch = 2 (slot 0): 2 <= 0 + 2 (True).\n\nAt this point, the producer overwrites slot 0 (epoch 0) even though the consumer has not yet stored released_epoch = 0 (which only happens when the consumer validates the first descriptor of epoch 1).\n\nRecommendation:\nSpecify that released_epoch must be initialized to a sentinel value indicating that no epochs have been released yet (for example, u64::MAX or -1 in wrapping/signed comparison terms), so that the first overwrite of slot 0 (rotating to epoch P) is correctly blocked until the consumer explicitly stores released_epoch = 0.

Suggested change
Acquire-load consumer released_epoch [A]
require active_epoch + 1 <= released_epoch + P (slot free?)
on failure: return typed PageQuotaExhausted — nothing mutated
Acquire-load consumer released_epoch [A]\n require active_epoch + 1 <= released_epoch.wrapping_add(P) (slot free, with released_epoch initialized to u64::MAX)\n on failure: return typed PageQuotaExhausted — nothing mutated

Comment on lines +68 to +71

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Data race / ABA guard flaw: released_epoch ambiguity and off-by-one overwrite.

The rotation algorithm uses an ambiguous released_epoch index (if initialized to 0, it means both "no epochs released" and "epoch 0 released") and an off-by-one condition (active_epoch + 1 <= released_epoch + P). This mathematical flaw allows P+1 live epochs, permitting the producer to overwrite the live tail page before the consumer ever releases it. The design must track a released_count (starting at 0) and use a strict < bound.

  • docs/adr/0010-bounded-page-rotation-generation-reuse.md#L68-L71: Change the load to released_count and the check to require active_epoch + 1 < released_count + P.
  • docs/adr/0010-bounded-page-rotation-generation-reuse.md#L109-L111: Change step 2 to Release-stores released_count = e + 1.
  • docs/adr/0010-bounded-page-rotation-generation-reuse.md#L120-L125: Update the reclamation edge text and sequence diagram to use released_count.
  • docs/adr/0010-bounded-page-rotation-generation-reuse.md#L139-L141: Update the window bounds text to [released_count, active_epoch].
  • docs/adr/0010-bounded-page-rotation-generation-reuse.md#L179-L180: Rename released_epoch to released_count in the memory layout block.
  • docs/adr/0010-bounded-page-rotation-generation-reuse.md#L225-L226: Rename released-epoch counter to released-count counter in the alternatives text.
  • ROADMAP.md#L70-L74: Rename released-epoch reclamation edge to released-count reclamation edge in the Week 4 progress narrative.
📍 Affects 2 files
  • docs/adr/0010-bounded-page-rotation-generation-reuse.md#L68-L71 (this comment)
  • docs/adr/0010-bounded-page-rotation-generation-reuse.md#L109-L111
  • docs/adr/0010-bounded-page-rotation-generation-reuse.md#L120-L125
  • docs/adr/0010-bounded-page-rotation-generation-reuse.md#L139-L141
  • docs/adr/0010-bounded-page-rotation-generation-reuse.md#L179-L180
  • docs/adr/0010-bounded-page-rotation-generation-reuse.md#L225-L226
  • ROADMAP.md#L70-L74
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/adr/0010-bounded-page-rotation-generation-reuse.md` around lines 68 -
71, Update the bounded page rotation specification to replace the ambiguous
released_epoch with a released_count initialized at zero, use the strict check
active_epoch + 1 < released_count + P, and release by storing released_count = e
+ 1. In docs/adr/0010-bounded-page-rotation-generation-reuse.md lines 68-71,
109-111, 120-125, 139-141, 179-180, and 225-226, update the algorithm,
reclamation edge, sequence diagram, window bounds, memory layout, and
alternatives terminology; in ROADMAP.md lines 70-74, rename the reclamation edge
accordingly.

Release-close the active page writer (seal, ADR-0008) [S]
rebind slot (active_epoch + 1) mod P:
new generation = (active_epoch + 1) as u32
first_sequence = sealed page next_sequence
active_epoch += 1
re-validate payload against the fresh page
then the unchanged ADR-0009 admission sequence:
reserve ring slot, InFlight, append + page Release [P],
ring slot write, ring Release [R], Open
```

Properties:

- **Bounded work, no waiting.** Rotation is a seal, one Acquire load, one
slot re-initialization over preallocated memory, and bookkeeping. If the
successor slot is still outstanding, the producer returns
`PageQuotaExhausted` with **zero page and zero ring mutation** — the caller
applies its declared event-class policy (bounded retry, spool, or
best-effort drop). No spin, sleep, allocation, or overflow queue.
- **A payload larger than one empty page** is still the ADR-0008 typed
invalid-input error; rotation never loops.
- **Failure atomicity is preserved.** The quota check happens before the seal
`[S]`, so a refused rotation leaves the active page open and usable for
smaller payloads. An unwind during rotation follows ADR-0009's caught-unwind
contract: ordered faulted closure of the page (sealed or fresh), terminal
word, and ring.

### Consumer release protocol

The ring is FIFO and consumption is strictly in-order (ADR-0009's exact
expected-sequence rule), so descriptors arrive grouped by epoch in epoch
order. The consumer tracks `current_epoch` and, upon validating the first
descriptor of epoch `e+1`:

1. requires its per-epoch committed count to equal the sealed page `e`'s
published count — a shortfall is the terminal orphaned-prefix/count
mismatch of ADR-0009, now detected at the page boundary instead of only at
end of stream;
2. Release-stores `released_epoch = e` `[E]`;
3. proceeds with ADR-0009 validation of the new epoch's descriptor against
the freshly Acquire-loaded slot binding.

Clean or faulted end-of-stream releases the final epoch after the ADR-0009
terminal checks, which now aggregate: total committed count must equal the
sum of sealed published counts plus the final page's published count.

### Reclamation proof (why no epochs are needed)

The single reclamation edge is consumer → producer over `released_epoch`:

```text
last frame commit for epoch e, ring-tail Release [C]
-> released_epoch Release-store [E]
-> producer released_epoch Acquire [A]
-> slot (e mod P) rebind and first cell write of epoch e+P
```

- A payload lease (`AdmittedEvent`) mutably borrows the consumer, so no lease
can be alive when the consumer later executes `[E]` inside `try_next` — the
borrow checker, not a runtime count, proves no reader holds bytes of a page
being released. This is the property crossbeam-epoch would otherwise buy,
and it holds only because the topology is exactly one consumer; any
multi-reader future (query snapshots, secondary indexes) must not reuse
this argument and gets its own ADR.
- The producer never rebinds a slot without Acquire-observing `[E]` for its
previous occupant, so every cell write of epoch `e+P` happens-after every
committed read of epoch `e`.
- **ABA/wrap safety:** at most `P` epochs are ever live, and the ring is
FIFO, so a descriptor observable by the consumer references an epoch in
`[released_epoch, active_epoch]`, a window of width `<= P`. The `u32`
generation tag is unambiguous while `P < 2^32`, which the type-level
`P: usize` bound enforces absurdly early; the epoch counter itself is `u64`
and non-wrapping for any realistic process lifetime (`2^64` rotations).
Comment on lines +141 to +144

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

usize does not bound P < 2^32 on 64-bit architectures.

On 64-bit platforms, usize::MAX is 2^64 - 1, which easily permits P >= 2^32. The proof relies on a false claim about the type system. If P exceeds 2^32, the u32 generation tag will wrap and ABA safety is lost. Either clarify that a const assertion is required, or correct the claim about usize.

💡 Proposed fix
-  FIFO, so a descriptor observable by the consumer references an epoch in
-  `[released_epoch, active_epoch]`, a window of width `<= P`. The `u32`
-  generation tag is unambiguous while `P < 2^32`, which the type-level
-  `P: usize` bound enforces absurdly early; the epoch counter itself is `u64`
+  FIFO, so a descriptor observable by the consumer references an epoch in
+  `[released_count, active_epoch]`, a window of width `<= P`. The `u32`
+  generation tag is unambiguous while `P < 2^32`, which a `const` assertion
+  must enforce (since `usize` allows larger values on 64-bit systems); the epoch counter itself is `u64`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`[released_epoch, active_epoch]`, a window of width `<= P`. The `u32`
generation tag is unambiguous while `P < 2^32`, which the type-level
`P: usize` bound enforces absurdly early; the epoch counter itself is `u64`
and non-wrapping for any realistic process lifetime (`2^64` rotations).
FIFO, so a descriptor observable by the consumer references an epoch in
`[released_count, active_epoch]`, a window of width `<= P`. The `u32`
generation tag is unambiguous while `P < 2^32`, which a `const` assertion
must enforce (since `usize` allows larger values on 64-bit systems); the epoch counter itself is `u64`
and non-wrapping for any realistic process lifetime (`2^64` rotations).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/adr/0010-bounded-page-rotation-generation-reuse.md` around lines 141 -
144, Correct the bounded-generation proof in the ADR: remove the claim that the
type-level P: usize bound enforces P < 2^32, and state that implementations must
enforce this limit with an explicit const assertion or equivalent validation.
Preserve the requirement that the u32 generation tag remains unambiguous only
when P < 2^32.

A descriptor whose generation fails slot equality is the ADR-0009 terminal
identity mismatch — never a skip, never a fallback read.

### Terminal-state extension

The composite terminal word and its `OPEN/CLEAN/FAULTED` values are unchanged.
`finish` seals the **active** page before storing `CLEAN` and closing the
ring. Clean end-of-stream now additionally requires every sealed epoch to have
been fully committed at its boundary (checked incrementally by the release
protocol) — so the aggregate clean condition remains "total committed equals
total published", with page-boundary early detection as a strengthening, not
a replacement, of ADR-0009's end-of-stream checks.

## Public result semantics

`AdmittedSequence` gains nothing: it already carries page identity, generation,
and wrapping sequence. Success semantics are ADR-0009's verbatim — volatile
publication only; never consumption, durability, a receipt, or authorization.

New/changed typed errors:

| Error | Page mutation | Ring mutation | Retry meaning |
|---|---:|---:|---|
| `PageQuotaExhausted` (all `P` slots outstanding) | none | none | consumer lagging; bounded retry/spool per event class |
| `page full` / `descriptor full` | no longer surfaced when rotation succeeds; surfaced unchanged when the payload exceeds one empty page | none | correct input |

All other ADR-0009 error rows are unchanged.

## Memory and ownership layout

```text
RotatingAdmissionChannel<N, P>
pool: [page slot 0][page slot 1]...[page slot P-1] (preallocated)
ring, terminal word (as ADR-0009)
released_epoch: cache-line-aligned AtomicU64 (consumer writes,
producer reads)

producer: [active writer][active_epoch][pool handles][ring producer][terminal]
consumer: [ring consumer][current_epoch][per-epoch committed][pool handles]
```

`released_epoch` lives on its own cache line: it is written once per page
lifetime, not per event, so rotation metadata adds no steady-state false
sharing to the `[P]`/`[R]`/`[C]` hot lines. Slot rebinding reuses the page
allocation in place; the pool never grows, shrinks, or reallocates.

## Failure, overload, and security behavior

- `PageQuotaExhausted` is backpressure, not data loss: nothing is admitted,
nothing is dropped, and the caller's event-class policy decides. The
critical/WAL lane semantics remain future work — this channel still cannot
carry protected evidence.
- A slow or stalled consumer bounds producer memory at exactly `P` pages plus
the ring; there is no unbounded queue anywhere in the composite
(architecture law §2.7).
- Generation mismatch, epoch-boundary count shortfall, sequence discontinuity,
CRC failure, and corrupt terminal state all remain terminal, fail-closed
lane errors — rotation adds detection points, never recovery-by-skipping.
- Descriptors remain non-capabilities; slot addressing via
`generation mod P` selects memory already owned by this channel's bound
pool and never a registry, tenant, or foreign allocation. Authenticated
registry lookup remains explicitly out of scope and future work.

## Progress and performance hypothesis

Steady-state admission and consumption are byte-for-byte the ADR-0009 paths;
rotation adds one Acquire load on the page-full branch only. The hypothesis is
that amortized cost per event is unchanged and rotation cost is `O(1)`
bounded, paid once per page. No measurement accompanies this ADR; the fabric
remains `target` with no performance claim, and qualification requirements are
unchanged from ADR-0009.

## Alternatives considered

- **Allocate a fresh page per rotation, drop the old one** — rejected:
per-page allocation/free in the hot path, unbounded live pages under a slow
consumer, and no reuse story; violates the bounded-everything law.
- **crossbeam-epoch reclamation now** — rejected: the SPSC borrow-checker
argument above makes epochs redundant for this topology; epochs enter with
multi-reader query snapshots (LLD §epoch retirement) under their own ADR.
- **Per-slot busy/free atomic flags** — rejected: a single released-epoch
counter is sufficient under FIFO in-order consumption and keeps one
reclamation edge to prove instead of `P`.
- **Producer blocks/spins when the pool is exhausted** — rejected: hot crates
admit no unbounded waits; typed backpressure lets the declared event class
decide.
- **Encode slot index in `arena_id`** — rejected: `arena_id` identifies the
lane/arena binding and participates in identity checks across the channel;
overloading it conflates lane identity with position. The epoch already
determines the slot.
- **Skip the page-boundary count check and rely on end-of-stream totals** —
rejected: boundary checking converts a silent mid-stream orphan into an
immediate terminal error while the evidence is fresh.

## Verification (required before the prototype can merge)

The implementing PR must provide, mirroring ADR-0009's evidence classes:

- rotation at byte exhaustion and at descriptor exhaustion; sequence
continuity across the seam; admission token generations advancing;
- `PageQuotaExhausted` with zero page/ring mutation, then successful admission
after the consumer crosses the boundary;
- generation-tagged reuse: slot rebinding after release, stale-descriptor
injection failing identity terminally, wrap of the `u32` tag under a small
`P` fixture;
- epoch-boundary committed-count shortfall detected terminally at the seam;
- caught-unwind fault injection at: before quota check, after seal `[S]`
before rebind, after rebind before re-validation, plus all ADR-0009 points;
- differential oracle (safe sealed pages + `VecDeque`) across many rotations
with variable-length payloads;
- Loom models for the release/rebind race (`[C]→[E]→[A]→rebind`), quota
refusal versus in-flight release, and faulted closure mid-rotation;
- full Miri; ASan/TSan lanes extended to the rotation suites; zero
steady-state allocations including across a rotation.

## Migration and rollback

While Proposed, nothing ships. The implementing PR lands
`RotatingAdmissionChannel` as isolated, unwired prototype code beside the
single-page composite, which remains the reviewed baseline and differential
reference. Rollback deletes the rotating module and this ADR's index row;
ADR-0006..0009 artifacts are untouched. Production wiring still additionally
requires WAL durability/replay, authenticated registry lookup, NUMA-owner
reclamation policy, priority lanes, shadow equality, qualification, and
release-artifact rollback — rotation removes exactly one blocker from that
list, not several.

## Revisit when

Revisit before adding WAL/durability classes, an authenticated page registry,
NUMA placement or cross-node pools, priority lanes, crossbeam-epoch or any
second reader, or any multi-producer/multi-consumer topology — each invalidates
at least one proof above (most immediately the borrow-checker reclamation
argument, which is single-consumer-only).

## References

- [Mandatory architecture law](../architecture.md)
- [ADR-0007: sealed generation-tagged slab pages](0007-sealed-generation-tagged-slab-pages.md)
- [ADR-0008: append-only published-prefix slab pages](0008-append-only-published-prefix-slab-pages.md)
- [ADR-0009: failure-atomic single-page slab/ring admission](0009-failure-atomic-slab-ring-admission.md)
- [Target LLD: epoch retirement and reclamation](../LLD.md)
- [Migration matrix](../../MIGRATION_MATRIX.md#10-gap-analysis-against-target)
- [Contribution and unsafe-code standard](../../CONTRIBUTING.md#lock-free-structures)
Loading
Loading