chore(#3053): clock-seam ADR-456 amendment + backfill — H2 (#3332)

* test(#3314): backfill deterministic clock-seam coverage (failing-first)

Replaces loose regex/range assertions with exact-value and boundary
tests for the CLI-subprocess and in-process clock-touching call sites
identified by H2's audit (epic #3053): cmdCurrentTimestamp,
_wsParseRetryAfter (commands.cts), cmdInitManager's is_active gate and
cmdInitQuick's quick_id generation (init.cts), and reapStaleTempFiles
(io.cts). The CLI-subprocess-pinned tests are expected RED until a
follow-up commit routes those call sites through realClock so
GSD_TEST_MODE+GSD_NOW_MS can reach them.

* refactor(#3314): route CLI-subprocess clock reads through realClock

cmdCurrentTimestamp, cmdInitManager's is_active gate, and cmdInitQuick's
quick_id generation read Date directly, which the GSD_TEST_MODE+GSD_NOW_MS
subprocess pin cannot reach (it only fires inside realClock.now()).
Behavior-preserving: realClock.now() falls through to Date.now() whenever
GSD_TEST_MODE is unset, which is every real invocation.

* docs(#3314): amend ADR-456 with reachability-based clock-control rule

ADR-456 §(a) documented one mechanism (injected {clock=Date} +
t.mock.timers). Adds the two this repo already relies on: t.mock.timers
for in-process direct-Date reads, and the GSD_TEST_MODE+GSD_NOW_MS
subprocess pin (routed through realClock) for CLI-spawned code. Updates
TESTING-STANDARDS.md's matching passages, which already referenced this
issue by number as the no-elapsed-assertion promotion precondition.

* fix(#3314): address orthogonal review findings

Spec-axis findings: file and link the no-elapsed-assertion promotion
follow-up (#3331) instead of leaving TESTING-STANDARDS.md pointing at
a dead #1885, and ship the module-by-module audit table in the ADR
itself rather than only in a gitignored phase artifact.

Standards-axis finding: pin the "hour-old file = not active" test via
GSD_TEST_MODE+GSD_NOW_MS for consistency with the reachability rule
this PR's own ADR amendment now documents.

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-08-10 15:24:39 -04:00
committed by GitHub
parent fae2a0fa8e
commit 80734a9694
8 changed files with 290 additions and 12 deletions

View File

@@ -968,6 +968,56 @@ describe('current-timestamp command', () => {
});
});
// ─────────────────────────────────────────────────────────────────────────────
// cmdCurrentTimestamp exact-value tests (#3314 — ADR-456 subprocess clock pin)
// ─────────────────────────────────────────────────────────────────────────────
describe('current-timestamp command — exact value under GSD_NOW_MS pin', () => {
let tmpDir;
// Pinned instant with a non-zero millisecond fraction so the 'full' format
// assertion can't accidentally pass against a truncated value.
const PINNED_MS = 1_700_000_000_123; // 2023-11-14T22:13:20.123Z
const PIN_ENV = { GSD_TEST_MODE: '1', GSD_NOW_MS: String(PINNED_MS) };
beforeEach(() => {
tmpDir = createTempProject();
});
afterEach(() => {
cleanup(tmpDir);
});
test('date format: exact value for pinned instant', () => {
const result = runGsdTools('current-timestamp date', tmpDir, PIN_ENV);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
const expected = new Date(PINNED_MS).toISOString().split('T')[0];
assert.strictEqual(output.timestamp, expected);
});
test('filename format: exact value for pinned instant', () => {
const result = runGsdTools('current-timestamp filename', tmpDir, PIN_ENV);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
const expected = new Date(PINNED_MS).toISOString().replace(/:/g, '-').replace(/\..+/, '');
assert.strictEqual(output.timestamp, expected);
});
test('full format: exact value for pinned instant', () => {
const result = runGsdTools('current-timestamp full', tmpDir, PIN_ENV);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.timestamp, new Date(PINNED_MS).toISOString());
});
test('default format: exact value for pinned instant', () => {
const result = runGsdTools('current-timestamp', tmpDir, PIN_ENV);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.timestamp, new Date(PINNED_MS).toISOString());
});
});
// ─────────────────────────────────────────────────────────────────────────────
// cmdListTodos tests (CMD-02)
// ─────────────────────────────────────────────────────────────────────────────
@@ -2476,17 +2526,48 @@ describe('_wsParseRetryAfter (#308)', () => {
assert.strictEqual(_wsParseRetryAfter('120000'), 60000);
});
test('future HTTP-date → value in (0, 60000]', () => {
const futureDate = new Date(Date.now() + 5000).toUTCString();
const v = _wsParseRetryAfter(futureDate);
assert.ok(typeof v === 'number' && v > 0 && v <= 60000, `expected (0,60000], got ${v}`);
// ADR-456 §(a) reachability rule: this function is required directly
// (in-process), so t.mock.timers reaches it without any production change —
// it patches the global `Date` that `Date.now()` reads from regardless of
// whether the SUT goes through realClock. Fixed, second-aligned pin so the
// HTTP-date's whole-second precision doesn't round the expected value away.
const PINNED_MS = 1_700_000_000_000; // 2023-11-14T22:13:20.000Z
test('future HTTP-date 5s ahead → exactly 5000 (deterministic)', (t) => {
t.mock.timers.enable(['Date']);
t.mock.timers.setTime(PINNED_MS);
const futureDate = new Date(PINNED_MS + 5000).toUTCString();
assert.strictEqual(_wsParseRetryAfter(futureDate), 5000);
});
test('past HTTP-date → 0', () => {
const pastDate = new Date(Date.now() - 5000).toUTCString();
test('past HTTP-date 5s behind → exactly 0 (deterministic)', (t) => {
t.mock.timers.enable(['Date']);
t.mock.timers.setTime(PINNED_MS);
const pastDate = new Date(PINNED_MS - 5000).toUTCString();
assert.strictEqual(_wsParseRetryAfter(pastDate), 0);
});
test('boundary: HTTP-date 59s ahead → 59000, not clamped', (t) => {
t.mock.timers.enable(['Date']);
t.mock.timers.setTime(PINNED_MS);
const d = new Date(PINNED_MS + 59_000).toUTCString();
assert.strictEqual(_wsParseRetryAfter(d), 59_000);
});
test('boundary: HTTP-date 60s ahead → 60000, at cap exactly', (t) => {
t.mock.timers.enable(['Date']);
t.mock.timers.setTime(PINNED_MS);
const d = new Date(PINNED_MS + 60_000).toUTCString();
assert.strictEqual(_wsParseRetryAfter(d), 60_000);
});
test('boundary: HTTP-date 61s ahead → clamped to 60000', (t) => {
t.mock.timers.enable(['Date']);
t.mock.timers.setTime(PINNED_MS);
const d = new Date(PINNED_MS + 61_000).toUTCString();
assert.strictEqual(_wsParseRetryAfter(d), 60_000);
});
test('"garbage" → null', () => {
assert.strictEqual(_wsParseRetryAfter('garbage'), null);
});

View File

@@ -475,6 +475,69 @@ describe('init manager', () => {
assert.ok(output.phases[0].last_activity !== null);
});
test('activity detection: hour-old file = not active', () => {
writeState(tmpDir);
writeRoadmap(tmpDir, [{ number: '1', name: 'Stale Phase' }]);
const PINNED_NOW_MS = 1_700_000_000_000; // 2023-11-14T22:13:20.000Z (second-aligned)
const phaseDir = scaffoldPhase(tmpDir, 1, { slug: 'stale-phase', context: true });
const files = fs.readdirSync(phaseDir);
const old = new Date(PINNED_NOW_MS - 60 * 60 * 1000); // 1 hour before the pinned "now"
for (const f of files) {
fs.utimesSync(path.join(phaseDir, f), old, old);
}
const result = runGsdTools('init manager', tmpDir, {
GSD_TEST_MODE: '1', GSD_NOW_MS: String(PINNED_NOW_MS),
});
const output = JSON.parse(result.output);
assert.strictEqual(output.phases[0].is_active, false);
});
// #3314 — ADR-456 subprocess clock pin: cmdInitManager's `is_active` gate
// (nowMs - newestMtime < 300000) is CLI-subprocess tested, so only the
// GSD_TEST_MODE+GSD_NOW_MS pin can control "now" here (t.mock.timers in the
// test process cannot reach the spawned child). newestMtime is set via
// fs.utimesSync to an exact epoch offset from the pinned "now" so the
// 300000ms boundary is provable exactly, not just "roughly recent"/"roughly old".
describe('is_active boundary (#3314 — exactly 300000ms, condition is strict <)', () => {
const PINNED_NOW_MS = 1_700_000_000_000; // 2023-11-14T22:13:20.000Z (second-aligned)
const PIN_ENV = { GSD_TEST_MODE: '1', GSD_NOW_MS: String(PINNED_NOW_MS) };
function scaffoldWithMtimeOffset(tmpDirLocal, offsetMs) {
writeState(tmpDirLocal);
writeRoadmap(tmpDirLocal, [{ number: '1', name: 'Boundary Phase' }]);
const phaseDir = scaffoldPhase(tmpDirLocal, 1, { slug: 'boundary-phase', context: true });
const mtimeMs = PINNED_NOW_MS - offsetMs;
const mtimeDate = new Date(mtimeMs);
for (const f of fs.readdirSync(phaseDir)) {
fs.utimesSync(path.join(phaseDir, f), mtimeDate, mtimeDate);
}
}
test('boundary: 299999ms since last activity → is_active true', () => {
scaffoldWithMtimeOffset(tmpDir, 299_999);
const result = runGsdTools('init manager', tmpDir, PIN_ENV);
const output = JSON.parse(result.output);
assert.strictEqual(output.phases[0].is_active, true);
});
test('boundary: exactly 300000ms since last activity → is_active false', () => {
scaffoldWithMtimeOffset(tmpDir, 300_000);
const result = runGsdTools('init manager', tmpDir, PIN_ENV);
const output = JSON.parse(result.output);
assert.strictEqual(output.phases[0].is_active, false);
});
test('boundary: 300001ms since last activity → is_active false', () => {
scaffoldWithMtimeOffset(tmpDir, 300_001);
const result = runGsdTools('init manager', tmpDir, PIN_ENV);
const output = JSON.parse(result.output);
assert.strictEqual(output.phases[0].is_active, false);
});
});
test('conflict filter: blocks dependent phase execute when dep is active', () => {
writeState(tmpDir);
writeRoadmap(tmpDir, [

View File

@@ -1713,6 +1713,80 @@ describe('cmdInitQuick', () => {
});
});
// ─────────────────────────────────────────────────────────────────────────────
// cmdInitQuick quick_id exact-value tests (#3314 — ADR-456 subprocess clock pin)
// ─────────────────────────────────────────────────────────────────────────────
describe('cmdInitQuick quick_id — exact value under GSD_NOW_MS+TZ pin', () => {
let tmpDir;
beforeEach(() => {
tmpDir = fs.realpathSync(createFixture());
});
afterEach(() => {
cleanup(tmpDir);
});
// Computes the expected quick_id independently from the SAME algorithm
// documented in src/init.cts's cmdInitQuick — NOT copy-pasted from its
// runtime output — so this test can actually catch a broken implementation.
function expectedQuickId(ms) {
const d = new Date(ms);
const yy = String(d.getFullYear()).slice(-2);
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
const dateStr = yy + mm + dd;
const secondsSinceMidnight = d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds();
const timeBlocks = Math.floor(secondsSinceMidnight / 2);
const timeEncoded = timeBlocks.toString(36).padStart(3, '0');
return dateStr + '-' + timeEncoded;
}
test('quick_id: exact value for pinned instant', () => {
const PINNED_MS = 1_700_000_000_000; // 2023-11-14T22:13:20.000Z
const result = runGsdTools('init quick "pinned task"', tmpDir, {
GSD_TEST_MODE: '1', GSD_NOW_MS: String(PINNED_MS), TZ: 'UTC',
});
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.quick_id, expectedQuickId(PINNED_MS));
});
test('boundary: two calls in the same 2s block share quick_id (documented collision, not a bug)', () => {
const BLOCK_START_MS = 1_700_000_000_000; // aligned so +0 and +1000 fall in the same 2s block
const r1 = runGsdTools('init quick "task a"', tmpDir, {
GSD_TEST_MODE: '1', GSD_NOW_MS: String(BLOCK_START_MS), TZ: 'UTC',
});
const r2 = runGsdTools('init quick "task b"', tmpDir, {
GSD_TEST_MODE: '1', GSD_NOW_MS: String(BLOCK_START_MS + 1000), TZ: 'UTC',
});
assert.ok(r1.success && r2.success);
const o1 = JSON.parse(r1.output);
const o2 = JSON.parse(r2.output);
assert.strictEqual(o1.quick_id, expectedQuickId(BLOCK_START_MS));
assert.strictEqual(o2.quick_id, expectedQuickId(BLOCK_START_MS + 1000));
assert.strictEqual(o1.quick_id, o2.quick_id, 'both instants are in the same 2-second block and must share a quick_id');
});
test('boundary: two calls straddling a 2s block edge get different quick_id', () => {
const BEFORE_EDGE_MS = 1_700_000_000_000; // even second → block boundary at +2000ms
const AFTER_EDGE_MS = BEFORE_EDGE_MS + 2000;
const r1 = runGsdTools('init quick "task a"', tmpDir, {
GSD_TEST_MODE: '1', GSD_NOW_MS: String(BEFORE_EDGE_MS), TZ: 'UTC',
});
const r2 = runGsdTools('init quick "task b"', tmpDir, {
GSD_TEST_MODE: '1', GSD_NOW_MS: String(AFTER_EDGE_MS), TZ: 'UTC',
});
assert.ok(r1.success && r2.success);
const o1 = JSON.parse(r1.output);
const o2 = JSON.parse(r2.output);
assert.strictEqual(o1.quick_id, expectedQuickId(BEFORE_EDGE_MS));
assert.strictEqual(o2.quick_id, expectedQuickId(AFTER_EDGE_MS));
assert.notStrictEqual(o1.quick_id, o2.quick_id, 'instants 2000ms apart cross a 2-second block edge and must differ');
});
});
// ─────────────────────────────────────────────────────────────────────────────
// cmdInitMapCodebase (INIT-05)
// ─────────────────────────────────────────────────────────────────────────────

View File

@@ -311,6 +311,45 @@ describe('reapStaleTempFiles (via io)', () => {
io.reapStaleTempFiles('gsd-io-nonexistent-prefix-xyz-', { maxAgeMs: 0 });
});
});
// #3314 — ADR-456 in-process reachability: t.mock.timers patches the
// process-global Date, so it controls `now` inside reapStaleTempFiles with
// no production code change needed. Both sides of the comparison (mocked
// "now" and the fs.utimesSync mtime) use second-aligned epoch values to
// avoid filesystem mtime sub-second-precision truncation on filesystems
// that round mtime to the nearest second.
describe('boundary: age exactly at maxAgeMs (condition is strictly-greater)', () => {
const MTIME_MS = 1_700_000_000_000; // second-aligned
const MAX_AGE_MS = 5000;
function plantFileAtAge(t, ageMs) {
fs.mkdirSync(io.GSD_TEMP_DIR, { recursive: true });
const p = path.join(io.GSD_TEMP_DIR, TEST_PREFIX + `boundary-${ageMs}.json`);
fs.writeFileSync(p, '{}');
fs.utimesSync(p, new Date(MTIME_MS), new Date(MTIME_MS));
t.mock.timers.enable(['Date']);
t.mock.timers.setTime(MTIME_MS + ageMs);
return p;
}
test('boundary: age exactly maxAgeMs-1 is kept', (t) => {
const p = plantFileAtAge(t, MAX_AGE_MS - 1);
io.reapStaleTempFiles(TEST_PREFIX, { maxAgeMs: MAX_AGE_MS });
assert.ok(fs.existsSync(p), 'file at maxAgeMs-1 must be kept');
});
test('boundary: age exactly maxAgeMs is kept (condition is strictly-greater)', (t) => {
const p = plantFileAtAge(t, MAX_AGE_MS);
io.reapStaleTempFiles(TEST_PREFIX, { maxAgeMs: MAX_AGE_MS });
assert.ok(fs.existsSync(p), 'file at exactly maxAgeMs must be kept — condition is strictly-greater, not >=');
});
test('boundary: age exactly maxAgeMs+1 is removed', (t) => {
const p = plantFileAtAge(t, MAX_AGE_MS + 1);
io.reapStaleTempFiles(TEST_PREFIX, { maxAgeMs: MAX_AGE_MS });
assert.ok(!fs.existsSync(p), 'file at maxAgeMs+1 must be removed');
});
});
});