cubesys: durability fixes A/B/C verified live on VM
- Fix A (store.rs): checkpoint boundary persists wal.committed_seq (highest fsync'd) instead of wal.seq() (next-to-assign), which skipped all WAL entries since last checkpoint -> silent data loss on reboot. - Fix B (commands.rs open): run decrypted program in isolated read_snapshot() clone instead of put_raw plaintext over sealed envelope (stopped reboot-time EnvelopeTooShort / clobber). - Fix C (commands.rs keyinit): flush OS key cells to WAL via log_put so they fold into base snapshot and survive reboot (keyinit #2 issues 0, not 2); previously re-minted random material each boot -> sealed records unopenable. - Regression guards durable_sealed_record_survives_restart + open_does_not_clobber_sealed_record in cubesys/src/commands.rs. - STARTUP-README: replace stale 'EPHEMERAL across restarts' caveat with the fixed/verified durability note. Verified live: systemctl restart cube-server (VM reboot path) -> sealed record decrypts+executes after reboot; key cell byte-identical; keyinit idempotent.
This commit is contained in:
+17
-7
@@ -801,9 +801,16 @@ fn checkpoint_store(
|
||||
));
|
||||
}
|
||||
if f.write_all(buf.as_bytes()).is_ok() && f.flush().is_ok() && f.sync_all().is_ok() {
|
||||
persist_seq(cp_seq_path, wal.seq.load(Ordering::SeqCst));
|
||||
wal.set_cp_seq_wal(wal.seq.load(Ordering::SeqCst));
|
||||
wal.set_base_seq(wal.seq.load(Ordering::SeqCst));
|
||||
// Boundary must be the highest *durable* (fsync'd) WAL seq,
|
||||
// NOT `wal.seq()` (which is the next-to-assign counter and
|
||||
// sits one past the last entry). Persisting the next-to-
|
||||
// assign value made `replay_after` skip every still-valid
|
||||
// WAL entry on restart — i.e. silent data loss of any
|
||||
// record written since the previous checkpoint.
|
||||
let durable = wal.committed_seq.load(Ordering::SeqCst);
|
||||
persist_seq(cp_seq_path, durable);
|
||||
wal.set_cp_seq_wal(durable);
|
||||
wal.set_base_seq(durable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -857,10 +864,13 @@ fn fold_delta_into_base(
|
||||
}
|
||||
// Delta is now fully represented by the base; truncate it.
|
||||
let _ = fs::write(delta_path, b"");
|
||||
let seq = wal.seq.load(Ordering::SeqCst);
|
||||
persist_seq(cp_seq_path, seq);
|
||||
wal.set_cp_seq_wal(seq);
|
||||
wal.set_base_seq(seq);
|
||||
// Boundary = highest *durable* WAL seq (fsync'd), not `wal.seq()` (the
|
||||
// next-to-assign counter, which sits one past the last entry and would
|
||||
// make `replay_after` skip still-valid entries on restart).
|
||||
let durable = wal.committed_seq.load(Ordering::SeqCst);
|
||||
persist_seq(cp_seq_path, durable);
|
||||
wal.set_cp_seq_wal(durable);
|
||||
wal.set_base_seq(durable);
|
||||
}
|
||||
|
||||
/// Read `db_path` (full base) then apply the delta file; returns the
|
||||
|
||||
Reference in New Issue
Block a user