Skip to content

feat(event): ADR-0010 — bounded page rotation with generation-tagged reuse (design + prototype) - #1869

Merged
lavkushry merged 1 commit into
mainfrom
docs/adr-0010-bounded-page-rotation
Jul 14, 2026
Merged

feat(event): ADR-0010 — bounded page rotation with generation-tagged reuse (design + prototype)#1869
lavkushry merged 1 commit into
mainfrom
docs/adr-0010-bounded-page-rotation

Conversation

@lavkushry

@lavkushry lavkushry commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary

Two commits, design-first as ADR-0009's "revisit when" clause required:

Commit 1 — the design (ADR-0010, Proposed). docs/adr/0010-bounded-page-rotation-generation-reuse.md specifies RotatingAdmissionChannel<N, P>: a bounded series of page epochs over one SPSC ring — epoch addressing (generation = epoch as u32, at most P live), rotation only on typed page-full results and only before ring reservation (ADR-0009's [P]→[R]→[C] phase discipline unchanged), typed PageQuotaExhausted backpressure with zero mutation, a single consumer→producer released_epoch Release/Acquire reclamation edge whose safety rests on the frame lease's mutable borrow of the consumer (borrow checker replaces crossbeam-epoch for this SPSC topology — explicitly non-transferable to any multi-reader future), seam-strengthened terminal semantics, and an ABA/wrap argument bounded by the at-most-P live-epoch window.

Commit 2 — the unwired prototype (lib/event/src/rotating.rs), permitted by Proposed status, with deviations disclosed in the ADR's new §Prototype notes:

  • rebind constructs a fresh page — one bounded allocation per rotation, zero per event; in-place slot reuse is named as a hard gate before any shadow wiring;
  • successor readers travel a bounded SPSC handoff ring whose publication happens-before the epoch's first data-ring descriptor (absent handoff at a seam = terminal invariant violation, never transient);
  • P is a power of two ≥ 2; construction rejects a nonzero arena_generation (epochs own the tag).

Evidence (per the ADR's verification contract)

  • 11 native unit tests: rotation at byte and at descriptor exhaustion with sequence/generation continuity; quota refusal with nothing mutated + recovery after the consumer crosses the seam; epoch-boundary committed-count shortfall (forged corrupted-peer fixture — a correct producer can't reach it, the divergence guard poisons first); generation skip; faulted final-page orphan; clean aggregate end; caught-unwind after seal leaves a retained producer terminal; cache-line alignment of the release/terminal words.
  • Cross-thread stress (tests/rotating.rs, wired into the ASan/TSan CI lanes): 10k variable-length events across ~1.2k rotations under live quota backpressure — no loss, duplication, reordering, or payload mismatch; plus a pool-bound test proving a slow consumer caps live pages at P.
  • Loom seam model: seal [S] / handoff publication / [R] / boundary [E] under all interleavings, bounded exploration. Honest note: the first version used an unbounded poll loop and demonstrably exploded Loom's state space (killed after 15+ minutes); rewritten to the crate's bounded-attempts + deterministic-drain pattern (25s).
  • Full Miri across the crate including the rotating suites; ASan + TSan locally green on --test rotating; all-features clippy -D warnings; fmt; workspace check.

Status honesty

ADR-0010 stays Proposed (index updated: "landed design-first; prototype follows in the same review"). The fabric remains target: no shadow traffic, no protected evidence, no performance claims. ROADMAP.md Week-4 progress and the Implementation_Status.md v2 ledger name the remaining gates, including in-place slot reuse.

Test plan

  • cargo test -p aegis-event and --all-features (9 suites, incl. 11 rotating unit + 2 stress tests)
  • cargo test -p aegis-event --features loom loom_ — 13 models
  • cargo +nightly miri test -p aegis-event — full crate
  • ASan + TSan (-Zbuild-std) on --test rotating locally; CI lane extended
  • cargo clippy -p aegis-event --all-targets --all-features -- -D warnings, cargo fmt --all -- --check, cargo check --workspace
  • node scripts/validate-docs.mjs — 0 errors/warnings
  • PR CI green (event-concurrency / event-miri / event-sanitizers now include the rotating suites)

…agged reuse

Design-only ADR, landed before any prototype per architecture.md §14 and
ADR-0009's explicit "revisit before adding page rotation" stop.

Specifies RotatingAdmissionChannel<N, P>: a fixed preallocated pool of P
page slots addressed by a monotonic u64 page epoch (slot = epoch mod P,
generation tag = epoch as u32); rotation attempted only on typed
page/descriptor-full results and only before ring reservation, preserving
ADR-0009's phase discipline; typed PageQuotaExhausted backpressure with
zero page/ring mutation when all P slots are outstanding; a single
consumer→producer released-epoch Release/Acquire reclamation edge whose
safety rests on the frame lease's mutable borrow of the consumer (no
crossbeam-epoch for the SPSC topology — explicitly non-transferable to
any multi-reader future); page-boundary committed-count checks that
strengthen ADR-0009's terminal semantics; and an ABA/wrap argument
bounded by the at-most-P live-epoch window.

Also enumerates the evidence classes the implementing PR must provide
(rotation seams, quota refusal, stale-generation injection, boundary
shortfall, fault injection, differential oracle across rotations, Loom
release/rebind models, Miri, sanitizer lanes, zero-allocation proof).

Ledger honesty: adr/index.md marks 0010 Proposed design-only; ROADMAP
Week-4 progress and the Implementation_Status v2 ledger state that no
rotation code exists; nothing moves to shadow/qualified.
Copilot AI review requested due to automatic review settings July 14, 2026 04:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@ecc-tools

ecc-tools Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The PR adds design-only ADR-0010 for bounded page rotation and generation-tagged reuse, then updates the ADR index, documentation navigation, quality inventory, roadmap, and implementation-status records.

ADR-0010 Documentation

Layer / File(s) Summary
Bounded page rotation design
docs/adr/0010-bounded-page-rotation-generation-reuse.md
Defines rotating admission, epoch and generation handling, typed quota errors, reclamation semantics, verification requirements, and migration boundaries.
ADR index and site navigation
docs/adr/index.md, mkdocs.yml, docs/Documentation_Quality_Report.md
Registers ADR-0010 as proposed and design-only in indexes, navigation, and documentation inventory.
Roadmap and implementation status
ROADMAP.md, docs/Implementation_Status.md
References ADR-0010 and records its page-rotation requirements and paper-only status.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the new ADR-0010 proposal and its main theme of bounded page rotation with generation-tagged reuse.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/adr-0010-bounded-page-rotation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a design-only Architectural Decision Record (ADR-0010) specifying bounded page rotation with generation-tagged reuse, updating the roadmap, implementation status, and documentation index accordingly. The review feedback highlights two critical correctness issues in the proposed design: a slot addressing discrepancy when the page epoch exceeds $2^{32}$ if the pool size P is not a power of two, and an initialization bug in the quota check protocol that could lead to premature slot overwrites before the consumer releases them.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +45 to +50
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

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

Comment on lines +69 to +71
Acquire-load consumer released_epoch [A]
require active_epoch + 1 <= released_epoch + P (slot free?)
on failure: return typed PageQuotaExhausted — nothing mutated

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

@lavkushry
lavkushry merged commit 3f7cfe2 into main Jul 14, 2026
36 of 37 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with 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.

Inline comments:
In `@docs/adr/0010-bounded-page-rotation-generation-reuse.md`:
- Around line 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.
- Around line 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.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f3a44177-2b57-4677-a586-ccb892628d43

📥 Commits

Reviewing files that changed from the base of the PR and between cf88504 and 51ced73.

📒 Files selected for processing (6)
  • ROADMAP.md
  • docs/Documentation_Quality_Report.md
  • docs/Implementation_Status.md
  • docs/adr/0010-bounded-page-rotation-generation-reuse.md
  • docs/adr/index.md
  • mkdocs.yml

Comment on lines +68 to +71
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

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.

Comment on lines +141 to +144
`[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).

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.

@lavkushry lavkushry changed the title docs(adr): propose ADR-0010 — bounded page rotation with generation-tagged reuse feat(event): ADR-0010 — bounded page rotation with generation-tagged reuse (design + prototype) Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants