fix(session-persistence-jsonl): make loadLive cwd-scope-exact (Codex review)
Codex's converge pass on PR B found a cross-cwd adoption hole: the coordinator calls loadLive(id, session.header.cwd) for HMR live-adoption, but JSONL's loadLive delegated to findLog(id, cwd) which, for cwd === undefined, scanned ALL cwd buckets. So a live NO-CWD session could adopt a same-id log from a real cwd bucket, ending with a live cwd: undefined but a persisted meta.cwd: '/w'. loadLive must treat `undefined` as the DEFINITE no-cwd bucket, not "unknown": it now goes straight to logPath(cwd, id) (which maps undefined -> _no-cwd), never the all-buckets scan. loadStored/deleteStored keep the any-cwd scan (resume/removal identify by id alone), so findLog is now a pure scan-all and loses its dead cwd-direct branch. The coordinator's has() relied on loadLive(id, undefined) meaning "any scope" for an untracked id — fixed to use loadStored for the untracked (unknown-cwd) case and loadLive only for a tracked session's known cwd. Adds a regression test: a no-cwd live session reusing an id persisted in a real cwd bucket no longer cross-cwd-adopts — it falls through to createCore's any-cwd collision probe and REJECTS, leaving the original log untouched. Also fixes the README to say `tornMarker !== undefined` (a marker may be falsy, 0).
This commit is contained in:
@@ -35,7 +35,7 @@ The `PersistenceBackend<TornMarker>` hooks (the only seam between the coordinato
|
||||
| `loadStored(id)` | Read a stored prefix by id, scanning ANY storage scope. Used by resume/load and, via `!== undefined`, the create-collision probe. Returns an opaque `tornMarker` iff a torn tail must be truncated. |
|
||||
| `loadLive(id, cwd)` | Read a stored prefix SCOPED to `cwd` (HMR live-adoption must only adopt a log at the SAME cwd; a same-id log elsewhere is a collision, not a resume). A globally-unique-id backend ignores `cwd`. |
|
||||
| `appendBatch(meta, events, isMaterialized)` | Durably append a contiguous batch, lazily materializing ATOMICALLY when not yet materialized. |
|
||||
| `commitRepair(meta, tornMarker, closers)` | Make a crash repair durable: truncate the torn tail (iff `tornMarker`) and append `closers`. NOT required to be atomic. Used by load (truncate + closers) and live-adoption (truncate only). |
|
||||
| `commitRepair(meta, tornMarker, closers)` | Make a crash repair durable: truncate the torn tail (iff `tornMarker !== undefined` — a marker may be falsy, e.g. seq/offset `0`) and append `closers`. NOT required to be atomic. Used by load (truncate + closers) and live-adoption (truncate only). |
|
||||
| `deleteStored(id)` / `list()` | Remove a stored artifact / list all stored metadata. |
|
||||
| `close?()` | Optional lifecycle teardown (e.g. close a db handle), awaited after the dispose drain. |
|
||||
|
||||
|
||||
@@ -287,13 +287,15 @@ export class PersistenceCoordinator<TornMarker = unknown> {
|
||||
async has(id: SessionId): Promise<boolean> {
|
||||
const state = this.states.get(id)
|
||||
if (state?.materialized) return true
|
||||
// Probe storage scoped to the tracked cwd if known, else any scope. A tracked
|
||||
// lazy session has a known cwd, so loadLive(id, cwd) hits the exact artifact
|
||||
// path — a storage fault there (e.g. a non-ENOENT lookup error) must surface,
|
||||
// not be masked by an any-scope scan that filters a non-directory bucket out.
|
||||
// For an untracked id `cwd` is undefined, where loadLive scans any scope (=
|
||||
// loadStored), so this single call covers both.
|
||||
return (await this.backend.loadLive(id, state?.meta.cwd)) !== undefined
|
||||
// A TRACKED lazy session has a known cwd: probe that exact bucket via
|
||||
// loadLive(id, cwd) — including the no-cwd bucket when its cwd is undefined.
|
||||
// An UNTRACKED id has a genuinely UNKNOWN cwd, so it must scan ANY scope via
|
||||
// loadStored — loadLive(id, undefined) would (correctly) look ONLY in the
|
||||
// no-cwd bucket and miss a materialized session that lives in a real cwd.
|
||||
const probe = state !== undefined
|
||||
? await this.backend.loadLive(id, state.meta.cwd)
|
||||
: await this.backend.loadStored(id)
|
||||
return probe !== undefined
|
||||
}
|
||||
|
||||
/** Remove a session and all its persisted artifacts. */
|
||||
|
||||
Reference in New Issue
Block a user