Skip to content

fix: audit hardening — resync OOM, cursor hygiene, Synadia timeout fallback#9

Merged
jaredLunde merged 2 commits into
mainfrom
jared/audit
Jun 12, 2026
Merged

fix: audit hardening — resync OOM, cursor hygiene, Synadia timeout fallback#9
jaredLunde merged 2 commits into
mainfrom
jared/audit

Conversation

@jaredLunde

@jaredLunde jaredLunde commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Six fixes from a deep technical audit, all inside the model-checked envelope: protocol.rs untouched, all 4 Stateright models — including the three #[ignore]-gated deep-bounds proofs — re-verified green.

Fixes

  • Resync OOM — the cursor-expired diff streams fold keys via for_each_in_range instead of range(); an All-scope resync against an on-disk backend no longer materializes the entire fold (values included) on the repair path. Both fjall and RocksDB override the streaming scan.
  • KvReader::entry() is now required — the get()-delegating default silently hid tombstones on any backend that forgot to override it, which breaks ExportLease::try_acquire's takeover of abandoned (CAS-deleted) leases. Backends without tombstone semantics delegate explicitly.
  • Unknown-version cursor hygiene — unparseable ACK subjects yield VersionToken::unknown() instead of a fabricated revision 0, and watch_applied skips unknown versions for batch_high: they can neither mint nor clobber a cursor position. Pre-fix, a single unparseable ACK on the hand-built multi-prefix consumer path regressed the persisted cursor to 0 (full replay on next restart). Pinned by unknown_version_update_does_not_move_or_clobber_cursor.
  • Synadia timeout fallback — a create_key_value timeout now falls through to the raw JetStream fallback instead of ?-propagating past the exact workaround built for that deployment.
  • BatchConfig::channel_capacity — the watch-task → loop channel depth (previously hardcoded 256) is now tunable alongside max, since during state-sync hydration it can become the effective batch boundary.
  • LeaseGuard must-use — documented that the inherent future must_use + crate-level deny(unused_must_use) already make an unawaited abandon()/complete() a compile error (an explicit attribute mis-fires on the awaited () output).

Verification

Suite Result
Lib unit tests (incl. new pin) 92 passed
SnapshotStore conformance (3 backends) 70 passed
Live-NATS integration / resync / transport 44 / 5 / 15 passed
Bootstrap + multi-export (live NATS + fjall/RocksDB) 8 passed
Stateright standard bounds (4 models, all mutations) 15 passed
Stateright deep bounds (3 exporters, extended revisions) 3 passed (8m19s)
clippy --all-features --all-targets, rustfmt clean

🤖 Generated with Claude Code

jaredLunde and others added 2 commits June 12, 2026 10:06
…llback

Six fixes from a deep technical audit, all inside the model-checked
envelope (protocol.rs untouched; all 4 Stateright models incl. the
three deep-bounds proofs re-verified green):

- resync diff streams fold keys via for_each_in_range instead of
  range(): an All-scope cursor-expired resync against an on-disk
  backend no longer materializes the entire fold (values included)
  on the repair path
- KvReader::entry() is now a required method: the get()-delegating
  default silently hid tombstones on any backend that forgot to
  override it, breaking ExportLease takeover of abandoned leases
- unparseable ACK subjects yield VersionToken::unknown() instead of
  a fabricated revision 0, and watch_applied skips unknown versions
  for batch_high — they can neither mint nor clobber a cursor
  position (pre-fix: persisted cursor regressed to 0, full replay on
  next restart); pinned by
  unknown_version_update_does_not_move_or_clobber_cursor
- create_key_value timeout now falls through to the Synadia Cloud
  raw fallback instead of ?-propagating past the exact workaround
  built for that deployment
- BatchConfig grows channel_capacity (default 256, previously
  hardcoded) so hydration tuning covers the real batch boundary
- LeaseGuard::abandon/complete: documented that the inherent future
  must_use + crate-level deny(unused_must_use) already make an
  unawaited call a compile error (an explicit #[must_use] attribute
  would mis-fire on the awaited () output)

Deliberately NOT fixed: a prefix-scope live floor guard (the model
checker rejected periodic-only detection, and sparse delivery makes
probes false-positive into spurious resyncs) — documented as a
Failure Modes row instead; replay_log's truncation/corruption
conflation (needs a format change, stays a documented limitation).

BREAKING: KvReader::entry() lost its default impl — trait
implementors must add it (consumers of Arc<dyn KvReader> unaffected).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e breaking

KvReader::entry() lost its default impl (trait implementors must add
it) and BatchConfig gained channel_capacity (breaks struct-literal
construction without FRU). Pre-1.0 semver: breaking → minor bump.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jaredLunde jaredLunde merged commit cb4d3ef into main Jun 12, 2026
1 check passed
@jaredLunde jaredLunde deleted the jared/audit branch June 12, 2026 17:45
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