chore(02-01): eslint sim-purity rule + Date.now violator fixture

- eslint.config.js block 3: no-restricted-syntax bans Date.now() and
  setInterval() inside src/sim/**, with src/sim/scheduler/clock.ts as
  the single allowed wall-clock owner (CONTEXT D-33, RESEARCH Pitfall 1)
- date-now-violator.ts deliberate-violation fixture (excluded from
  default lint by Block 1's top-level ignores; the programmatic ESLint
  test passes ignore: false to override)
- lint-firewall.test.ts gains 2 new cases: positive (rule fires on
  violator) + negative (rule does NOT fire on clock.ts the one exception)
- Existing CORE-10 firewall test left untouched and remains green
This commit is contained in:
2026-05-09 09:20:44 -04:00
parent fe99058040
commit 2a8d354b58
3 changed files with 96 additions and 0 deletions
@@ -0,0 +1,14 @@
// DELIBERATE VIOLATION OF CONTEXT D-33 — DO NOT USE OUTSIDE THE FIREWALL TEST.
//
// This file lives under src/sim/__test_violation__/ and is excluded from
// `npm run lint` via the `ignores` block in eslint.config.js. Its sole
// purpose is to be lint-tested by lint-firewall.test.ts to prove the
// no-restricted-syntax rule (Phase 2 sim-purity) actually fires.
//
// The Vitest test runs ESLint programmatically with `ignore: false`
// against this file and asserts that `no-restricted-syntax` fires with
// the D-33 message.
export function violator(): number {
return Date.now(); // intentional violation — Phase 2 Plan 02-01 Task 3
}
@@ -47,3 +47,37 @@ describe('CORE-10: src/sim/ cannot import from src/render/ or src/ui/', () => {
expect(combined).toMatch(/render|ui/i);
});
});
describe('Phase 2 sim-purity rule (CONTEXT D-33)', () => {
it('eslint flags Date.now() inside src/sim/** as no-restricted-syntax', async () => {
const eslint = new ESLint({
overrideConfigFile: resolve(process.cwd(), 'eslint.config.js'),
ignore: false,
});
const fixturePath = resolve(
process.cwd(),
'src/sim/__test_violation__/date-now-violator.ts',
);
const results = await eslint.lintFiles([fixturePath]);
expect(results).toHaveLength(1);
const violations = results[0].messages.filter(
(m) => m.ruleId === 'no-restricted-syntax',
);
expect(violations.length).toBeGreaterThanOrEqual(1);
expect(violations[0].message).toMatch(/inject time|D-33/);
});
it('does NOT flag Date.now() inside src/sim/scheduler/clock.ts (the one exception)', async () => {
const eslint = new ESLint({
overrideConfigFile: resolve(process.cwd(), 'eslint.config.js'),
ignore: false,
});
const clockPath = resolve(process.cwd(), 'src/sim/scheduler/clock.ts');
const results = await eslint.lintFiles([clockPath]);
const noRestrictedViolations = results[0].messages.filter(
(m) => m.ruleId === 'no-restricted-syntax',
);
expect(noRestrictedViolations).toHaveLength(0);
});
});