fix(close): a read-only brain writes no clean-shutdown evidence — the marker is the writer's word about itself
Some checks are pending
CI / Node 22 (push) Waiting to run
CI / Node 24 (push) Waiting to run
CI / Integration + conformance (Node 22) (push) Waiting to run
CI / Bun (latest) (push) Waiting to run
Delta Gate / Delta gate — candidate vs control (push) Waiting to run

This commit is contained in:
David Snelling 2026-09-02 10:56:32 -07:00
parent a79db434ac
commit 367ca721a5
3 changed files with 280 additions and 4 deletions

View file

@ -805,7 +805,16 @@ export class GenerationStore {
if (uncleanOpen) await this.advanceFoldCheckpointUnlocked()
// The marker is consumed: any session that can write invalidates it
// at first commit (see the commit paths); a clean close re-writes it.
await this.clearCleanShutdownMarker()
// A READER NEVER CONSUMES IT. The marker is the writer's own evidence
// about the writer's own process — clearing it here exists so that
// if THIS session goes on to write and then dies before its next
// clean close, the marker's absence correctly reads as unclean. A
// reader can never write, so it can never leave the store in a state
// its own crash would mis-describe; clearing the marker for it would
// only cost the store's actual writer a needless whole-log fold on
// its next open, for a generation the reader merely observed. Leave
// `_system/` exactly as found.
if (!options?.readOnly) await this.clearCleanShutdownMarker()
}
await this.factLog.open(this.committed)
} else {
@ -895,7 +904,11 @@ export class GenerationStore {
}
}
/** Consume the clean-shutdown marker (every open; a clean close re-writes it). */
/**
* Consume the clean-shutdown marker (every WRITER open; a clean close
* re-writes it). Callers must gate this on `!options.readOnly` a reader
* never consumes the marker, see the call site in {@link open}.
*/
private async clearCleanShutdownMarker(): Promise<void> {
try {
await this.storage.deleteRawObject(CLEAN_SHUTDOWN_PATH)