Skip to content

Expire runtime peer records through atomic overlap batches - #340

Merged
myobie merged 7 commits into
mainfrom
agent/runtime-peer-expiry338
Oct 29, 2026
Merged

myobie merged 7 commits into
mainfrom
agent/runtime-peer-expiry338

Conversation

@schickling-assistant

@schickling-assistant schickling-assistant commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Problem

An old runtime-peer record cannot currently be given a bounded, immutable lifetime. A consumer publishing its prepared NodeID therefore cannot atomically install the new exact grants and schedule retirement of the old record before switching identities.

Goal

Implement the runtime-peer expiry slice from Nathan's decided #338 direction: one atomic add-next/set-grants/expire-old batch, with ordinary revocation and observable cancellation receipts when the old record expires. No dual-identity serving.

Decisions

  • set_expiry accepts strictly future signed Unix seconds, at most 600 seconds ahead. The first deadline is immutable; setting exactly the same deadline is a no-op, not a renewal.
  • Effective policy has prevalidated timed generations. Authorization expires independently of delayed persistence or publication locks; wall/monotonic observations prevent clock rollback from reauthorizing an old identity.
  • Installing a deadline reserves its future revocation receipt capacity and exposes an expiry_request_id in runtime provenance. Expiry uses existing revocation/cancellation machinery and preserves access justified by static or other-owner sources.
  • Startup removes elapsed records before admitting connections and fails closed on persisted clock rollback.

Verification

Review repair head: 18c2134999897052c30d57169e3fcdacc967df6f. Independent reviewer FabricRekey338.ExpiryReviewDelta returned GO at that exact head before push; source review only.

Local: gate-slot --class heavy -- env CARGO_TARGET_DIR=/home/schickling/.cache/fabric-mesh-cargo/fabric nix develop -c cargo test --locked -p fabric --lib expiry -- --test-threads=1 — 22 passed, clean exact head.

Review regression tests: expiry_receipt_capacity_accounts_for_reservations_and_preserves_withdrawal_reserve; simultaneous_expiry_receipts_name_only_their_own_cancelled_work; fresh_record_expiry_accepts_only_the_next_one_through_600_seconds; expiry_is_bounded_immutable_and_overlap_batch_replays_without_extension (changed-deadline replay); expiry_request_id_prefix_is_system_only_before_and_after_cleanup.

Hosted CI: test success; Nix success. No watches used for this revision. Nathan merged this exact repair head in fbf74b700ed10080e2ca1ec50cbf19b205af59a7; ancestry was checked before updating #341. No follow-up PR or duplicate A fixes are needed.

Complexity

Timed policy views are needed because delayed disk I/O must not extend authorization. They are prevalidated and selected without hot-path allocation; expiry persistence continues through the existing transaction/cancellation path.

Concerns

Expiry is per owner record, not a blanket denial of a NodeID: independent static or other-owner trust remains effective. Clock rollback conservatively retires expiring records rather than extending their lifetime.

Open questions: none for this runtime-peer slice.

Friction & bottlenecks

No measured build or runtime bottleneck claimed. Local proofs are targeted and admission-gated; hosted CI is the merge proof.

Follow-ups

The stacked identity prepare/activate/status PR consumes this publish-before-activation contract. Consumer orchestration is outside this PR.

References

Refs #338. Direction: #338 (comment)

Posted on behalf of @schickling
field value
agent_identity agent/dotfiles/fabric-mesh
session 01a17e44
agent_persona generalist
agent_supervisor unavailable
agent_tool OMP
agent_tool_version 18.12.5
agent_runtime OMP 18.12.5
agent_model anthropic/claude-opus-5-5
worktree smalltalk/main

@myobie

myobie commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator

Review of #340 at 1b54624. I verified the following in code (source review; I ran no builds).

Contract: OK. A deadline must be strictly in the future and at most 600 s ahead (runtime_peers.rs:1001-1010, u64::try_from plus saturating_sub; the wire caps at i64::MAX). The first deadline is immutable, and the same deadline is a no-op that creates no receipt. The add-next/set-grants/expire-old batch is one prepare/persist. Authorization reads evaluate the monotone clock (config.rs:736), so a delayed commit cannot extend a lifetime, and in-process rollback cannot reauthorize. Startup sweeps before the endpoint and control socket exist. Removal is owner-scoped, so static and other-owner access survives. Nothing I traced lets an expired record authorize.

Two changes block the merge:

  1. set_expiry can spend the 64-slot revocation reserve. runtime_peers.rs:1096 treats SetExpiry as withdrawal-only, so a batch of only set_expiry on existing records is admitted up to limit 1024 instead of 960 (:1105-1111). Each one takes a receipt plus a reservation. About 32 such batches fill the reserve; a real remove_peer from any owner then fails with RuntimeReceiptCapacity, and receipts live 24 h. Scheduling a future revocation is not a withdrawal: drop SetExpiry from that arm.
    Test: expiry_receipt_capacity_is_reserved_when_the_deadline_is_installed (:2195) cannot catch this. It sets 1023 receipts, which already exceeds the 960 ordinary limit, so the add fails either way. Replace it with a case where receipts+reserved is 1024 while receipts alone are below it: a withdrawal-only remove_peer is refused at the full pool, and a set_expiry-only batch cannot enter the reserve.

  2. Automatic-expiry receipts are not scoped to their record. runtime_control.rs:838-846 merges the whole sweeper pending list into whichever record's cleanup commits first, then the writer keeps only entries that receipt did not name (:60-62). With two records expiring in one sweep the first receipt claims both peers' cancelled work and the second reports none, so the receipt cannot be trusted per record. Scope each receipt to the cancellations of its own record, and add a test with two records expiring together.

Also required, tests: the expiry_is_bounded… test (:2014) feeds deadline±1, 0, -1, i64::MAX to a record that already has an expiry, so immutability rejects them, not the bounds. Add the real bound cases on a fresh record: ts, ts+1, ts+600, ts+601. Add: the same request_id with a changed deadline gives request_id_conflict.

Small, in this PR: reserve the expiry: request-id prefix for the system. An owner can pre-register expiry:<blake3> and make another owner's batch fail with a conflict.

Tracked in #343, not blocking: the O(deadlines x peers) compose on every read; mass expiry holding policy_update per commit; a stale expiry_last_seen across a crash before cleanup (persist the anchor on first observation and at shutdown); silent rollback handling, with a warning; the reconcile rollback check; and the other items listed there.

Once the two blockers and the test fixes are pushed I review only the delta and merge, then #341.

…ng expiry

agent-identity: agent/dotfiles/fabric-mesh
agent-persona: generalist
agent-supervisor: unavailable
agent-tool: OMP
agent-tool-version: 18.12.5
agent-runtime: OMP 18.12.5
agent-model: anthropic/claude-opus-5-5
agent-identity: agent/dotfiles/fabric-mesh
agent-persona: generalist
agent-supervisor: unavailable
agent-tool: OMP
agent-tool-version: 18.12.5
agent-runtime: OMP 18.12.5
agent-model: anthropic/claude-opus-5-5
…eplay

agent-identity: agent/dotfiles/fabric-mesh
agent-persona: generalist
agent-supervisor: unavailable
agent-tool: OMP
agent-tool-version: 18.12.5
agent-runtime: OMP 18.12.5
agent-model: anthropic/claude-opus-5-5
agent-identity: agent/dotfiles/fabric-mesh
agent-persona: generalist
agent-supervisor: unavailable
agent-tool: OMP
agent-tool-version: 18.12.5
agent-runtime: OMP 18.12.5
agent-model: anthropic/claude-opus-5-5
@myobie
myobie merged commit fbf74b7 into main Oct 29, 2026
4 checks passed
@myobie

myobie commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

Delta reviewed at 18c2134 and merged (main fbf74b7). Both blockers are fixed in code: set_expiry no longer counts as a withdrawal (the 64-slot reserve is preserved), and automatic-expiry receipts and revocation selection are scoped to their own NodeID. The expiry: prefix is system-reserved, and the new tests exercise the real boundaries (receipts+reserved at 1024, ts..ts+601 on a fresh record, changed-deadline replay, two records expiring together). The follow-ups are in #343.

#341 is next: it is now based on main, and my review items there are still open.

@schickling-assistant

Copy link
Copy Markdown
Contributor Author
  1. Withdrawal reserve: aa43caf; expiry_receipt_capacity_accounts_for_reservations_and_preserves_withdrawal_reserve.
  2. Per-record cancellation receipts: 0482383; simultaneous_expiry_receipts_name_only_their_own_cancelled_work.
  3. Fresh ts/ts+1/ts+600/ts+601 bounds and changed-deadline replay conflict: 2b1b3e8; fresh_record_expiry_accepts_only_the_next_one_through_600_seconds, expiry_is_bounded_immutable_and_overlap_batch_replays_without_extension.
  4. System-only expiry: prefix: 18c2134; expiry_request_id_prefix_is_system_only_before_and_after_cleanup.
    Exact-head review GO; gated expiry tests 22 passed; test/Nix CI green. These commits are included in your merge fbf74b7. Deferred Follow-ups from runtime expiry (#340) and identity activation (#341) reviews #343 items unchanged.
Posted on behalf of @schickling
field value
agent_identity agent/dotfiles/fabric-mesh
session 01a17e44
agent_persona generalist
agent_supervisor unavailable
agent_tool OMP
agent_tool_version 18.12.5
agent_runtime OMP 18.12.5
agent_model anthropic/claude-opus-5-5
worktree smalltalk/main

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