Skip to content

gateway: allowance stays fail-closed on a partial snapshot; atomic snapshot rebuild (D257) - #119

Merged
jaredLunde merged 2 commits into
mainfrom
snapshot-failclosed
Oct 3, 2026
Merged

jaredLunde merged 2 commits into
mainfrom
snapshot-failclosed

Conversation

@jaredLunde

Copy link
Copy Markdown
Contributor

Summary

D257 (Reliability/Security, med). rebuild_snapshot removed the file, appended Puts, and wrote the cursor last, all in place. A crash midway left some Puts and no cursor. The next boot with NATS unreachable installed that file as a ready allowance-set holding only part of the exhausted tenants: the rest were served, and /readyz returned 200.

  • Allowance policy, set through the trait: WatchedSet::SEED_FROM_CURSORLESS_SNAPSHOT. Deny (fail-open) still seeds from any snapshot. Allowance (fail-closed) seeds only from a snapshot that has a cursor record, and this is decided before the slot or the ready gauge is touched. Only a revision > 0 cursor skips the scan on connect, same as before.
    • Deviation from the brief: the brief gated on is_resumable. That made an edge gateway with an empty allowance bucket (empty scan, cursor revision 0) unready with NATS down, and the existing e2e on_disk_snapshot_enforces_across_restart_without_nats failed. Every writer puts the cursor after the data it covers, so a cursor record of any revision proves the read was complete.
  • Atomic rewrite for every set: the rebuild writes {path}.tmp, fsyncs it, renames it over path, fsyncs the directory, and reopens path for appends. It removes any stale .tmp first, and any failure (removal or rename) aborts to snapshot-less mode.
  • beyond-slipstream 0.1.0 → 0.8.0: same v2 on-disk format and API-compatible for us. 0.8 adds no public atomic rebuild API (its compact_to_file is private), so the temp-file-and-rename lives here.

Test plan

  • reliability_snapshot.rs (hermetic, NATS on a closed port):
    • cursorless allowance snapshot → 503 and /readyz 503;
    • with a cursor → 402 for the exhausted tenant, 200 for others, ready;
    • cursorless deny snapshot still returns 403.
  • Unit tests:
    • a crash between staging and rename → load() returns the previous complete snapshot with its cursor;
    • a stale .tmp is discarded, and the reopened writer appends to the renamed file.
  • With the allowance policy flipped to true, the fail-closed test fails (402 served).
  • cargo nextest run -p beyond-ai: 1093/1093 passed. cargo clippy --all-targets -D warnings, cargo fmt --check, dprint check and verify gate (static) all pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk

jaredLunde and others added 2 commits October 3, 2026 08:53
… snapshot rebuild (D257)

A rebuild removed the snapshot, appended Puts, and wrote the cursor last, in
place. A crash midway left some Puts and no cursor; the next boot with NATS
down installed them as a READY allowance-set holding only part of the
exhausted tenants, so the rest were served and /readyz said 200.

- WatchedSet::SEED_FROM_CURSORLESS_SNAPSHOT: deny (fail-open) still seeds from
  any snapshot; allowance (fail-closed) seeds only from one with a cursor
  record, decided before anything is installed or the ready gauge flips.
- rebuild_snapshot writes {path}.tmp, fsyncs it, renames over path, fsyncs
  the directory, and reopens path for appends. A stale .tmp is removed first;
  any failure aborts to snapshot-less.
- beyond-slipstream 0.1.0 -> 0.8.0 (same v2 on-disk format, API compatible).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk
@jaredLunde
jaredLunde enabled auto-merge (squash) October 3, 2026 15:58
@jaredLunde
jaredLunde merged commit c47f5fc into main Oct 3, 2026
20 checks passed
@jaredLunde
jaredLunde deleted the snapshot-failclosed branch October 3, 2026 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant