fix(slice-21 FR-5c test determinism): epoch config mtime + simulated-uptime discriminator (OPR.0.3.2.21.FR-5)

Forward-fix on guard BLOCKING qitem-20260602042720-e27ec982.
Test-determinism issue; product code unchanged.

Where it bit:
- workspace-routes.test.ts /doctor "healthy" fixture set the
  test config mtime to `Date.now() - 60_000`.
- The route handler computes daemon start as `Date.now() -
  process.uptime() * 1000`.
- When the Vitest worker has been alive longer than 60 seconds
  (common in full-repo runs or worker re-use), daemon start is
  OLDER than the config mtime → check #5 flips to warn →
  summary becomes {ok:6, warn:1, fail:0} → test fails for
  reasons unrelated to product code.

Fix:
- Set the route-test config mtime to `new Date(0)` (epoch
  1970-01-01) so the route handler's process.uptime()-derived
  daemon start is GUARANTEED newer than the config mtime
  regardless of worker uptime.
- Added a `daemon_reload_needed` deterministic comment hint at
  the fixture site so the next contributor sees why epoch was
  chosen over a relative offset.

Required discriminator added (guard's recipe):
- New test "POST /doctor stays healthy under simulated long
  worker uptime" patches `process.uptime` via
  Object.defineProperty to return 10 years in seconds, hits the
  healthy fixture, and asserts summary stays {ok:7, warn:0,
  fail:0} + check #5 status stays "ok". Restores the original
  process.uptime in a finally block so subsequent tests aren't
  poisoned.
- Mutation target: if the route-test config mtime were set
  back to `Date.now() - 60_000`, this test would fail under the
  patched uptime regardless of actual worker timing.

Why this matters beyond the immediate fix:
The orchestrator-level tests in workspace-doctor.test.ts pass
daemonStartTime explicitly (`new Date(Date.now() - 5_000)`)
rather than relying on process.uptime(). Those are
deterministic. The route test is the only place where
process.uptime() drives daemonStartTime; the fixture's mtime
ANCHOR therefore needs to be absolute-old, not relative.

Verification:
- packages/daemon tsc EXIT=0
- packages/daemon workspace-doctor.test.ts + workspace-routes.test.ts:
  44/44 PASS (43 + 1 new uptime-determinism discriminator)
- Pre-commit staged-diff grep for forbidden-attribution pattern:
  ZERO matches
- Pre-commit FULL-CONTENT grep on every touched file (touch-event-
  scope rule): ZERO matches
- Commit body verification grep clean per banked discipline
- git diff --cached --stat: 1 file (+48 / -2)

Branch: slice-21-fr-5-rig-workspace-doctor off LOCAL main e154a5ce.
Chain: a4b4b67d (FR-5a, CLEAR) → e5d61983 (FR-5b, BLOCKED) →
bc5ec05f (FR-5b fix-up, CLEAR) → 6b21ce3c (FR-5c, BLOCKED on
test determinism; product code untouched in this fix-up) →
this commit (FR-5c test determinism fix-up). LOCAL only, NO push.

Files:
- packages/daemon/test/workspace-routes.test.ts (+48 / -2)
This commit is contained in:
mvschwarz committed 2026-06-01 21:28:51 -07:00
1 parent 6b21ce3c86
commit 784222d30d
1 file changed
+48 -2
+48 -2
View File
@@ -100,8 +100,14 @@ describe("workspace doctor HTTP route (slice-21 FR-5)", () => {
// shape (value/source/defaultValue).
configPath = path.join(doctorDir, ".test-config.json");
fs.writeFileSync(configPath, "{}");
const oldMtime = new Date(Date.now() - 60_000);
fs.utimesSync(configPath, oldMtime, oldMtime);
// Config-file mtime is forced to epoch (1970-01-01) so the route's
// process.uptime()-derived daemon start is GUARANTEED newer than
// the config mtime regardless of Vitest worker uptime. A relative
// offset like Date.now() - 60_000 flips check #5 to warn when the
// worker has been alive longer than the offset (banked guard
// BLOCKER on FR-5c qitem-20260602042720-e27ec982).
const epochMtime = new Date(0);
fs.utimesSync(configPath, epochMtime, epochMtime);
const stubStore = {
configPath,
@@ -205,4 +211,44 @@ describe("workspace doctor HTTP route (slice-21 FR-5)", () => {
const body = await res.json() as { workspaceRoot: string };
expect(body.workspaceRoot).toBe(doctorDir);
});
// GUARD BLOCKER-1 (qitem-20260602042720-e27ec982) determinism
// discriminator: even under a long process.uptime() (simulating a
// long-running Vitest worker), the healthy fixture must still
// return summary {ok:7, warn:0, fail:0}. A regression where the
// route-test config mtime was set relative to Date.now() instead
// of an absolute-old epoch would flip check #5 to warn under any
// worker uptime greater than the relative offset.
it("POST /doctor stays healthy under simulated long worker uptime", async () => {
const origUptime = process.uptime;
// Force daemon start to a far past time (≈10 years ago at the
// current Date.now()); the epoch-1970 config mtime must still be
// older.
Object.defineProperty(process, "uptime", {
value: () => 60 * 60 * 24 * 365 * 10,
writable: true,
configurable: true,
});
try {
const res = await doctorApp.request("/api/workspace/doctor", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({}),
});
expect(res.status).toBe(200);
const body = await res.json() as {
summary: { ok: number; warn: number; fail: number };
checks: Array<{ check: string; status: string }>;
};
expect(body.summary).toEqual({ ok: 7, warn: 0, fail: 0 });
const reload = body.checks.find((c) => c.check === "daemon_reload_needed");
expect(reload?.status).toBe("ok");
} finally {
Object.defineProperty(process, "uptime", {
value: origUptime,
writable: true,
configurable: true,
});
}
});
});