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
+48
View File
@@ -49,6 +49,54 @@ export default [
],
},
// ---------------------------------------------------------------------
// 3. Phase-2 sim-purity rule (CONTEXT D-33, RESEARCH Pitfall 1).
//
// Bans Date.now() and setInterval() inside src/sim/** to enforce the
// "Sim modules are pure — no Date.now(), no setInterval" rule from
// CLAUDE.md Code Style. The single allowed wall-clock owner is
// src/sim/scheduler/clock.ts (which exports the Clock interface and
// the wallClock + FakeClock implementations).
//
// Severity is `error` so `npm run lint --max-warnings 0` fails on a
// violation. The deliberate-violation fixture under
// src/sim/__test_violation__/ is excluded; it exists ONLY to be lint-
// tested by Task 3's Vitest test (which runs ESLint programmatically
// with `ignore: false`).
// ---------------------------------------------------------------------
{
files: ['src/sim/**/*.{ts,tsx}'],
// Per-block ignores. Note: src/sim/__test_violation__/** is NOT
// listed here even though it's globally ignored by Block 1 — the
// programmatic ESLint test (with `ignore: false`) overrides the
// global ignore, and we WANT the rule to apply to the violator
// fixture in that test path so the test can assert it fires.
ignores: ['src/sim/scheduler/clock.ts'],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: { jsx: true },
},
},
rules: {
'no-restricted-syntax': [
'error',
{
selector: "CallExpression[callee.object.name='Date'][callee.property.name='now']",
message:
"src/sim/** must inject time; only src/sim/scheduler/clock.ts may read Date.now() (CONTEXT D-33).",
},
{
selector: "CallExpression[callee.name='setInterval']",
message:
"src/sim/** must not use setInterval; the scheduler drives ticks via the Phaser game loop (CORE-02).",
},
],
},
},
// ---------------------------------------------------------------------
// 2. Phase-1 architectural firewall (CORE-10).
//