Plan 04-02 Summary: Sweep move, SessionStore.list, startup reaper (RT-08)
Status: implemented. Unit suites were written but not run in this lane; the orchestrator runs the gates after the wave lands. Live behavior is not claimed (the host runs Podman 4.9.3; the sweep and reaper are proven here with in-process fakes only).
Files
src/runtime/sweep.ts(new):removeAllRealmContainers(libpod)moved fromsession-execution.tswith the same double-listing zero-survivor proof. It now resolves with the removed count (D-84).SweepClientmoved with it.src/runtime/openshell.ts:MANAGED_LABEL_VALUEadded besideMANAGED_LABEL_KEY(D-84).buildCreateArgvuses the constant for theio.realm.managedlabel; the emitted argv is unchanged.src/runtime/session-execution.ts: the sweep body,SweepClient, and the localMANAGED_LABEL_VALUEare removed. The module re-exportsremoveAllRealmContainersandSweepClientfromsweep.ts, so existing callers keep resolving. The header comment points atsweep.ts.src/session/store.ts:SessionStore.list()(D-85). Name-only enumeration; no file is parsed.src/session/reap.ts(new):reapStartup({ store, sweep })(D-84, D-86, D-87). Imports nosrc/runtime/module (D-50).test/runtime/down-sweep.test.ts(migrated): imports fromsrc/runtime/sweep.js; asserts the removed count.test/session/store.test.ts(extended): three RT-08list()cases.test/session/reap.test.ts(new): six RT-08 reaper cases.
must_haves evidence
Truth 1 (sweep removes every realm-labelled container, touches no foreign container, returns the removed count, proves zero survivors or reports REALM_STOPPING; RT-08, D-84):
RT-08: the sweep removes every realm-labelled container, leaves a foreign container untouched, and returns the removed count(test/runtime/down-sweep.test.ts)RT-08: the sweep lists before and after removal on every run, and a repeated sweep over a clean host is a no-op returning zero(test/runtime/down-sweep.test.ts)RT-08: a container that cannot be removed gives REALM_STOPPING, and a repeated sweep clears it and returns its count(test/runtime/down-sweep.test.ts)
Truth 2 (SessionStore.list() returns ids from file names, including corrupt records, and ignores non-matching names; RT-08, D-85):
RT-08: list() returns the sorted ids of record files and ignores names that do not match the record pattern (D-85)(test/session/store.test.ts)RT-08: list() includes the id of a record whose contents are corrupt, so the record can still be deleted (D-85)(test/session/store.test.ts)RT-08: list() on an absent sessions directory returns an empty array, not null (D-85)(test/session/store.test.ts)
Truth 3 (reapStartup removes containers before records, reaps a crash during realm up in both record states, is a no-op on repeat, and deletes nothing when the sweep fails; RT-08, D-84, D-86):
RT-08: reapStartup removes every container before it deletes any record, checked against the live container store (D-84)(order check: each record deletion asserts zero realm-labelled containers remain, and the call log reads sweep, then records)RT-08: reapStartup reaps a crash during realm up with a null container id, found through its label (D-86)RT-08: reapStartup reaps a record that holds a container id together with that container (D-86)RT-08: reapStartup leaves a foreign unlabelled container untouched (D-86)RT-08: a second reapStartup is a no-op that removes nothing and deletes nothing (D-84)RT-08: a surviving container makes reapStartup reject with REALM_STOPPING, deletes no record, and a later reap completes (D-84)
Prohibitions:
src/session/reap.tsimports only./store.jsas a type. No runtime import (D-50).- No error code, policy key, or dependency was added (D-101).
Decisions and deviations
- The sweep returns
found.length. Once the second listing is empty, every container the first listing found is gone, so this is the removed count. reapStartupresolves with{ containersRemoved, recordsDeleted }. The plan did not fix a return shape; the counts make the reap observable.SessionStore.list()keeps only regular files (Dirent.isFile()). A directory named*.jsonwould otherwise be listed, anddeleteon it would fail. Contents are still never read.buildCreateArgvnow reads the label value fromMANAGED_LABEL_VALUEinstead of the literaltrue. This is the sharing the pattern map asks for; the argv string is identical.test/integration/podman-gate.test.tsis outside this lane's owned files. It still importsremoveAllRealmContainersandSweepClientfromsrc/runtime/session-execution.js. The re-export keeps it compiling. Its owning lane should migrate the import tosrc/runtime/sweep.js(the pattern map lists this migration).test/quality/suite-taxonomy.test.tsis not edited here.test/session/reap.test.tsis not yet inD45_FILES, and the Phase 4 ID assertions belong to that lane (D-102).
Open items (live-gated or residual)
- OQ-P4-06 (residual risk, Phase 6): reaping has no cross-process owner check. The single-owner precondition (D-87) is documented in
src/session/reap.ts. - The OpenShell create's label application by the Podman driver is live-gated (OQ-P3-01). The orphan-by-label reap of a null-id record is proven only against the fake libpod.