practice bug fix — the boot sweep stamped a LIVE run (owner live test 2026-09-29)

Owner report: created a set, 'Interrupted (server restarted)' showed
on list + set views, generation proceeded and overwrote it. The
sweep's 'generating + no registry row' test had a false-positive
mode: it ran at EVERY pipeline instantiation, and an instance could
evaluate against a registry whose fresh row it couldn't observe.

Three guards, per ruling:

1. HEARTBEAT: ProblemSet.updatedAt? (contract + doc) — bumped on
   every store write; createSet stamps it = createdAt; saveSet
   re-stamps (validator tolerates absence for legacy rows).

2. ONCE PER PROCESS: the sweep guards on globalThis
   .__lhPracticeSweepDone — no instance re-evaluates after the
   process's first sweep.

3. GRACE WINDOW: only sets whose heartbeat (updatedAt ?? createdAt)
   is older than 120s are stamped. A live run saves at every phase/
   problem transition, so it is structurally unreachable; a true
   orphan is minutes stale at next boot. One [practice <id>] log
   line per stamped set (the extraction/jobs console.error idiom).

Tests: stale orphan stamped; FRESH generating set untouched (grace);
once-guard (second factory instantiation never re-sweeps); live-row
protection re-armed via resetSweep() test seam; store pins for
updatedAt birth-stamp + re-stamp + legacy tolerance. Architecture.md
sweep sentence updated (once-per-process + grace semantics).

1142/1142, tsc 0, lint 0, build 0.
This commit is contained in:
amosblomqvist
2026-09-29 20:19:46 +03:00
parent e92679254d
commit 8d39f3717c
6 changed files with 116 additions and 14 deletions
+4
View File
@@ -133,6 +133,7 @@ function createSet(init: Omit<ProblemSet, "id">): ProblemSet {
const set: ProblemSet = {
...init,
id: `ps_${Date.now().toString(36)}_${crypto.randomUUID().slice(0, 8)}`,
updatedAt: init.updatedAt ?? init.createdAt,
};
writeSetFile(set);
return set;
@@ -164,6 +165,9 @@ function listSets(): ProblemSet[] {
function saveSet(set: ProblemSet): void {
if (!isProblemSet(set)) throw new Error("problem set record failed validation");
// The heartbeat: every write re-stamps (the boot sweep's grace window
// reads this — a live run is structurally never 120s stale).
set.updatedAt = new Date().toISOString();
writeSetFile(set);
}