test(01-02): add CORE-10 firewall test + violator fixture

- src/sim/__test_violation__/violator.ts deliberately imports from
  src/render/__firewall_target__.ts to trigger the firewall rule.
- src/sim/__test_violation__/lint-firewall.test.ts runs ESLint
  programmatically (with ignore: false) against the violator and
  asserts boundaries/element-types fires with severity=error and the
  message mentions both 'sim' and 'render'.
- src/render/__firewall_target__.ts is a minimal export so the
  boundaries plugin can resolve the import to a real path on disk.
  Without a real target, the plugin marks the import as isUnknown
  and silently skips the rule (verified empirically; see SUMMARY).
- eslint.config.js gains an import/resolver: typescript block so the
  TS-aware resolver follows extension-less imports
  ('../../render/foo' -> src/render/foo.ts). Required by the
  boundaries plugin's element classification of import targets.
- tsconfig.app.json excludes *.test.ts and src/sim/__test_violation__/
  so 'tsc -b' does not try to typecheck Node-API-using test code with
  the DOM-only project's lib settings; vitest still discovers them
  via its own include glob.
- Added eslint-import-resolver-typescript as devDep.

Verifies green:
  npm run lint        -> 0 errors, 0 warnings (violator excluded)
  npm test            -> 2/2 pass (sentinel + firewall)
  npm run build       -> tsc -b clean, vite build clean
  npx eslint --no-ignore src/sim/__test_violation__/violator.ts
                      -> exits 1 with the expected
                         boundaries/element-types error

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 23:34:01 -04:00
parent e9b742da79
commit 8c1d839adf
7 changed files with 573 additions and 1 deletions
+19
View File
@@ -0,0 +1,19 @@
// Target stub for the CORE-10 firewall test fixture.
//
// The deliberate-violation fixture at
// src/sim/__test_violation__/violator.ts imports from this file so the
// boundaries plugin can resolve the import to a real path under
// src/render/ and classify it as the `render` element type.
//
// Without a real file to resolve to, eslint-plugin-boundaries marks the
// target as `isUnknown: true` and the boundaries/element-types rule
// silently skips the check (verified empirically via the plugin's
// debug output during Plan 02 execution).
//
// This file is otherwise unused. It is NOT part of the runtime render
// layer; src/render/ is intentionally empty in Phase 1 (only .gitkeep
// existed before this file). Phase 2 will populate src/render/ with
// real Phaser scenes and remove this stub if the firewall test is
// rewritten to point at a real render module.
export const FIREWALL_TARGET_MARKER = 'render-target-for-firewall-test';
@@ -0,0 +1,49 @@
// CORE-10 firewall test: programmatically run ESLint against the
// deliberate-violation fixture and assert that
// `eslint-plugin-boundaries` flags the sim → render import.
//
// Per the Nyquist Rule, the rule needs an automated end-to-end check —
// not just "lint exits 0 on clean code, trust me". This test invokes
// the rule machinery via the ESLint Node API and inspects the messages
// directly.
//
// The fixture (./violator.ts) is excluded from `npm run lint` via the
// `ignores` block in eslint.config.js so it doesn't break CI. We pass
// `ignore: false` to the programmatic ESLint instance below to override
// that exclusion for this single test.
import { describe, it, expect } from 'vitest';
import { ESLint } from 'eslint';
import { resolve } from 'node:path';
describe('CORE-10: src/sim/ cannot import from src/render/ or src/ui/', () => {
it('eslint-plugin-boundaries flags a sim → render import as an error', async () => {
const eslint = new ESLint({
overrideConfigFile: resolve(process.cwd(), 'eslint.config.js'),
ignore: false,
});
const fixturePath = resolve(
process.cwd(),
'src/sim/__test_violation__/violator.ts',
);
const results = await eslint.lintFiles([fixturePath]);
expect(results).toHaveLength(1);
const messages = results[0].messages;
const boundaryErrors = messages.filter(
(m) => m.ruleId === 'boundaries/element-types' && m.severity === 2,
);
expect(boundaryErrors.length).toBeGreaterThan(0);
// The error message should mention 'sim' (the offending element)
// and either 'render' or 'ui' (the disallowed targets). Both terms
// are checked separately so a regression that drops either side is
// caught.
const combined = boundaryErrors.map((m) => m.message).join(' | ');
expect(combined).toMatch(/sim/i);
expect(combined).toMatch(/render|ui/i);
});
});
+19
View File
@@ -0,0 +1,19 @@
// DELIBERATE VIOLATION OF CORE-10 — 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
// boundaries/element-types rule actually fires (CORE-10).
//
// The import below targets a real file under src/render/ —
// __firewall_target__.ts — because eslint-plugin-boundaries needs to
// resolve the import to a real path on disk to classify the target's
// element type. If the import path does not resolve, the plugin marks
// the target as `isUnknown` and silently skips the check (verified
// empirically against eslint-plugin-boundaries 6.0.2 during Plan 02
// execution; see 01-02-SUMMARY.md "Deviations").
import { FIREWALL_TARGET_MARKER } from '../../render/__firewall_target__';
export const VIOLATION_MARKER = 'sim-imports-render';
export const _ref = FIREWALL_TARGET_MARKER;