Skip to content

refactor(persist): extract shared durable-write + quarantine primitives#289

Merged
send merged 1 commit into
mainfrom
refactor/persist-extract
Jul 4, 2026
Merged

refactor(persist): extract shared durable-write + quarantine primitives#289
send merged 1 commit into
mainfrom
refactor/persist-extract

Conversation

@send

@send send commented Jul 4, 2026

Copy link
Copy Markdown
Owner

What

Extract the on-disk persistence primitives that user_history (LXUD) already
uses into a new crate-internal crate::persist module, and redirect the three
consumers (persistence.rs, recovery.rs, wal.rs) to it:

  • atomic durable write — tmp → sync_all (F_FULLFSYNC) → rename → best-effort dir fsync
  • quarantine + rotation — rename a corrupt file aside as <name>.corrupt-<ts> (preserve bytes, never delete), cap retained copies
  • fixint bincode readers — with an allocation cap; strict (reject-trailing) for CRC-framed bodies vs lenient (allow-trailing) for no-CRC/v1-class formats

Why

This is the prerequisite for hardening the user dictionary (LXUW) without a
second, drift-prone copy of privacy/durability-critical code. Those primitives
were pub(super)/private inside user_history, unreachable by the other
durable store — the gap that left user_dict::save with an unsynced write and
the stray-.tmp bug that LXUD had already fixed (an unfixed copy of the same
bug = the exact drift CLAUDE.md's 「同種処理は単一の正準形に収束させる」 targets).
Centralizing makes the durability/quarantine invariants hold by construction
for both stores.

Scope — parity-only

  • No behavior change, no on-disk format change, no FFI change, no
    dict/corpus/hot-path touch. user_dict is deliberately not wired to
    crate::persist here — that adoption (plus fsync/quarantine/with_limit) is
    the follow-up PR3b.
  • The primitives move verbatim. Two deliberate generalizations, both
    behavior-preserving:
    • quarantine returns an outcome enum (Renamed/Removed/Failed) and takes
      an injected ts, so it is a clock-free leaf with no dependency on any
      store's domain types or clock. recovery.rs keeps a thin adapter mapping the
      outcome back to its old bool + report-path-push (exact isomorphism).
    • rotate_quarantined(path, keep) takes the retention count as a parameter;
      QUARANTINE_KEEP = 3 stays the caller's policy in recovery.rs.
  • now_epoch stays in user_history (already the crate-wide time util, 34 call
    sites incl. the FFI crate) and is injected into quarantine.

Net: crate::persist (+226) centralizes what the three consumers shed (−198).

Why split (PR3a / PR3b)

Design-lens adjudicated this session (and passed /lexime-plan-review):
単一正準形 → extract; 束ね規律 → split the parity-only refactor of freshly-merged
crash-critical code (recovery/WAL from #281/#285) from the risk-bearing
user_dict hardening. PR3a carries one invariant set — did user_history stay
byte-for-byte identical
— whose whole proof is the green regression suite; PR3b
carries is the new user_dict recovery correct. Isolating them keeps either
independently bisectable/revertible.

Verification

  • cargo fmt --all --check, cargo clippy --workspace --all-features -- -D warnings
  • cargo test --workspace --all-features587 tests, 0 failed (incl. the
    51KB tests_recovery.rs recovery matrix, the authoritative parity oracle)
  • msrv cargo +1.88.0 check --workspace --locked; supply-chain screen
    (quarantine / build.rs baseline) + cargo deny/vet/machete
  • Pre-push design gate: /simplify (4 agents) + /code-review high (correctness
    • conventions) + /lexime-review (Axis 3 structural + Axis 5 context) — all 0 findings

Test plan

  • Full workspace test suite green (parity oracle)
  • clippy -D warnings clean — every pub(crate) item live, no dead code / unused imports
  • msrv 1.88.0, screen, audit green
  • CI green (accuracy + swift deferred to CI: zero conversion-path / FFI-surface, unchanged by construction)

🤖 Generated with Claude Code

Introduce `crate::persist`, the single-source home for the on-disk
persistence primitives the durable stores share: atomic durable write
(tmp → fsync → rename → dir fsync), corruption quarantine + rotation
(rename aside as `.corrupt-<ts>`, preserve bytes), and the fixint bincode
readers with an allocation cap. These lived as `pub(super)`/private
helpers in `user_history` (persistence.rs / recovery.rs), where a second
durable store (user_dict, LXUW) could not reach them — the gap that left
user_dict with an unsynced write and the stray-`.tmp` bug LXUD already
fixed. Centralizing them is the prerequisite for hardening user_dict
without a second, drift-prone copy (CLAUDE.md: 同種処理は単一正準形に収束).

Parity-only: the primitives move verbatim (quarantine generalized to
return an outcome enum + an injected `ts`, so it stays clock-free and
`persist` depends on no store's domain types), and the three consumers —
persistence.rs, recovery.rs, and wal.rs — are redirected to the shared
module. `now_epoch` stays in `user_history` (already the crate-wide time
util, 34 call sites) and is injected into `quarantine`, keeping `persist`
a leaf both stores depend on. Behavior is unchanged; the full suite
(incl. the recovery matrix in tests_recovery.rs) is the proof.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@send

send commented Jul 4, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: c0bb8fe5ec

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@send send merged commit 2473994 into main Jul 4, 2026
13 checks passed
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