Files
josh de39c1b7c3 docs(01-02): complete eslint-firewall plan
SUMMARY documents the ESLint flat config, the 9 element types, the
single CORE-10 rule, the deliberate-violation fixture, the Vitest test
that runs ESLint programmatically against the violator, and the four
auto-fixed deviations (typescript-eslint parser-only integration,
real render target file for the violator import, eslint-import-resolver-
typescript wiring, tsconfig.app.json test-file exclusion).

Verifies: npm run lint -> 0 errors / 0 warnings; npm test -> 2/2 pass;
npm run build -> green; eslint --no-ignore on violator -> exits 1.

Self-Check: PASSED.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 23:38:19 -04:00

269 lines
26 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 01-foundations-and-doctrine
plan: 02
subsystem: infra
tags: [eslint, eslint-plugin-boundaries, typescript-eslint, firewall, lint, vitest, architectural-firewall]
# Dependency graph
requires:
- phase: 01-foundations-and-doctrine/01
provides: "Phaser 4 + React 19 + Vite 8 + TS 6 scaffold with the seven src/ firewall directories pre-created (sim, render, ui, save, content, audio, store), ESLint 9.39.4 + eslint-plugin-boundaries 6.0.2 pre-installed, npm 'lint' script pre-declared with --max-warnings 0"
provides:
- "ESLint 9 flat config (eslint.config.js) declaring 9 element types — the seven Phase-1 firewall subsystems plus the template's app + game — and one rule (severity: error): src/sim/ MUST NOT import from src/render/ or src/ui/ (CORE-10)"
- "Deliberate-violation fixture (src/sim/__test_violation__/violator.ts) excluded from default lint glob via the eslint.config.js ignores block"
- "Vitest test (src/sim/__test_violation__/lint-firewall.test.ts) that runs ESLint programmatically against the violator and asserts boundaries/element-types fires with severity=error and message containing both 'sim' and 'render|ui'"
- "Render-side stub file (src/render/__firewall_target__.ts) — minimal export so the boundaries plugin can resolve the violator's import to a real path on disk. Without this, the plugin marks the import target as isUnknown and silently skips the rule (verified empirically; see Deviations)."
- "TypeScript-aware import resolution for ESLint via eslint-import-resolver-typescript (devDep), wired through eslint.config.js settings.import/resolver"
- "Build-glob exclusions in tsconfig.app.json for *.test.ts and src/sim/__test_violation__/** so 'tsc -b' does not try to typecheck Vitest test files (which use Node APIs) under the DOM-only project lib settings"
affects: [01-03-save-layer, 01-04-content-pipeline, 01-05-asset-provenance, 01-07-ci-workflow, 02-onwards]
# Tech tracking
tech-stack:
added:
- "typescript-eslint@^8.59.2 — parser only (provides @typescript-eslint/parser bundled). NO rule sets enabled. Required because ESLint's default Espree parser cannot parse .ts/.tsx syntax. Documented as a Plan 02 deviation (Rule 3 — Blocking) below."
- "eslint-import-resolver-typescript@^4 — required by eslint-plugin-boundaries to follow extension-less TS imports ('../../render/foo' -> src/render/foo.ts). Without it, the boundaries plugin marks all TS-import targets as isUnknown and the firewall rule silently skips (verified via the plugin's debug output). Documented as a Plan 02 deviation (Rule 1 — Bug fix)."
patterns:
- "Single ESLint flat config at repo root with element-types + ignores + parser-only typescript-eslint integration. No legacy .eslintrc.* file. Plan 02 owns ONE architectural rule; broader code-quality lint sets (js.configs.recommended, tseslint.configs.recommended) are deliberately omitted to keep Phase 1 scope tight."
- "Default posture is 'allow' — Phase 1 enforces ONE rule (CORE-10), not a closed-by-default architecture. Future phases may add cross-subsystem restrictions (e.g., render cannot import save) by adding entries to the rules array without changing the default."
- "Lint-rule-correctness-via-Vitest pattern: the firewall rule's end-to-end correctness is proven by a Vitest test that runs ESLint via the JS API against a deliberate-violation fixture, NOT by 'lint exits 0 on clean code' (which proves nothing about the rule). The fixture is excluded from the default lint glob so CI stays green; the test passes ignore:false to override the exclusion."
- "Test-violation fixtures live under __test_violation__/ subdirectories and are doubly-excluded — from eslint.config.js ignores AND from tsconfig.app.json's exclude block — so neither 'npm run lint' nor 'tsc -b' trip on them. Vitest's own include glob (src/**/*.test.ts) discovers the test inside that directory."
key-files:
created:
- eslint.config.js (ESLint 9 flat config — 9 element types, 1 rule, parser+resolver wiring, default-lint exclusions)
- src/sim/__test_violation__/violator.ts (deliberate sim → render import)
- src/sim/__test_violation__/lint-firewall.test.ts (Vitest test that asserts the rule fires)
- src/render/__firewall_target__.ts (minimal render-side export stub the violator targets)
modified:
- package.json (added typescript-eslint and eslint-import-resolver-typescript devDeps)
- package-lock.json (lockfile entries for the two new devDeps and their transitive closure: 18 + 14 packages)
- tsconfig.app.json (added exclude block for *.test.ts and src/sim/__test_violation__/**)
key-decisions:
- "Omitted js.configs.recommended and tseslint.configs.recommended rule sets. Plan 02 owns exactly one architectural rule (CORE-10); broader code-quality lint is out of Phase 1 scope. Future phases may layer more rules on top of this config without touching the firewall block. Plan 01's SUMMARY confirmed no template eslint baseline existed to preserve."
- "Created src/render/__firewall_target__.ts as a real TS module (not a non-existent path) for the violator to import. The plan's Step 1 said 'doesn't need to exist as a real module' but empirical testing showed the boundaries plugin classifies unresolvable imports as isUnknown and silently skips the rule — the import MUST resolve to a real file under src/render/ to be classified as type:render and trigger the disallow."
- "Wired eslint-import-resolver-typescript via the import/resolver setting (boundaries plugin reads this). Without it, ext-less TS imports cannot be followed and EVERY in-repo TS import is marked isUnknown — the firewall rule would silently no-op even when called against the right shape of code."
- "Used the legacy boundaries/element-types rule + array-form selectors ({ from: ['sim'], disallow: ['render', 'ui'] }) per the plan's Pattern 5 spec. The plugin emits stderr deprecation notices recommending boundaries/dependencies + object-form selectors (the v6 modern shape), but those notices are informational — they do NOT count as ESLint warnings (verified via -f json: 0 errors, 0 warnings) and do NOT trip --max-warnings 0. Migration to the v6 modern shape is deferred to a future phase if it ever becomes load-bearing."
- "Excluded src/sim/__test_violation__/** and *.test.ts from tsconfig.app.json's build glob (added an exclude block). Vitest discovers test files via its own include glob, completely independent of tsconfig — so this only narrows what 'tsc -b' compiles, not what 'npm test' runs. Required because the firewall test imports node:path / process which aren't in the DOM-only app lib config."
- "Suppressed the 'Multiple projects found' notice from eslint-import-resolver-typescript via noWarnOnMultipleProjects:true. The referenced-projects tsconfig layout (root tsconfig with references to tsconfig.app.json + tsconfig.node.json) is deliberate Plan 01 design — the resolver sees both as 'projects' and warns; we explicitly opt out."
patterns-established:
- "Lint-rule correctness via Vitest + ESLint Node API pattern: any architectural rule landed in this project should be paired with a Vitest test that imports the ESLint class, runs it programmatically against a deliberate-violation fixture (under __test_violation__/), and asserts the expected ruleId + severity fires. This satisfies the Nyquist Rule for static-analysis rules ('lint exits 0' proves nothing about whether a specific rule actually works)."
- "Double-exclusion pattern for test-violation fixtures: ignored by eslint.config.js (so npm run lint stays green) AND excluded from tsconfig.app.json (so tsc -b doesn't typecheck them). The Vitest test that consumes them passes ignore:false to ESLint to override the lint-side exclusion."
- "Real-target-required pattern for boundaries plugin tests: deliberate-violation fixtures must import from REAL files under the target element directory, not from non-existent paths. The boundaries plugin classifies import targets via element pattern after resolving the import to a file path; unresolvable imports are isUnknown and silently skip rule evaluation."
requirements-completed: [CORE-10]
# Metrics
duration: 22min
completed: 2026-05-09
---
# Phase 01 Plan 02: ESLint Firewall Summary
**ESLint 9 flat config + eslint-plugin-boundaries 6.0.2 enforcing CORE-10 (src/sim/ cannot import src/render/ or src/ui/) at error severity, with a Vitest test that runs ESLint programmatically against a deliberate-violation fixture and asserts the rule fires end-to-end.**
## Performance
- **Duration:** 22 min
- **Started:** 2026-05-09T03:12:34Z (worktree spawn — first action ran ~3 min after spawn due to dependency install)
- **Completed:** 2026-05-09T03:34:09Z
- **Tasks:** 2 (both completed atomically)
- **Files created:** 4 (eslint.config.js, violator.ts, lint-firewall.test.ts, __firewall_target__.ts)
- **Files modified:** 3 (package.json, package-lock.json, tsconfig.app.json)
## Accomplishments
- **CORE-10 firewall is structurally enforced.** `eslint.config.js` declares the seven Phase-1 subsystem element types (`sim`, `render`, `ui`, `save`, `content`, `audio`, `store`) plus the template's `app` (the React/Phaser bridge files) and `game` (`src/game/**`) types — 9 total. One rule, severity `error`: `{ from: ['sim'], disallow: ['render', 'ui'] }`. Default posture `allow` so Phase 1 enforces only this one architectural constraint, not a closed-by-default architecture.
- **The rule is provably correct end-to-end.** `src/sim/__test_violation__/lint-firewall.test.ts` instantiates the ESLint class, runs it against `src/sim/__test_violation__/violator.ts` (which imports from `src/render/__firewall_target__.ts`), and asserts the result includes a `boundaries/element-types` message at severity 2 (error) whose text contains both `sim` and `render|ui`. Test passes in ~1 second. This satisfies the Nyquist Rule — the rule's correctness is automated, not assumed from "lint exits 0 on clean code".
- **`npm run lint` exits 0 on the clean codebase.** Zero errors, zero warnings (verified via `-f json` formatter). The violator fixture is excluded by the `ignores` block in `eslint.config.js`, so it doesn't break CI; the test reaches it via `ignore: false` on the programmatic ESLint instance.
- **`npm run build` and `npm test` continue to pass.** TypeScript strict-mode build is green; the test suite is now 2/2 (sentinel from Plan 01 + this firewall test).
- **Wave 2 sibling plans are unblocked.** Plans 03/04/05/06 can now land their config and code without colliding on `eslint.config.js`. Plan 07's CI workflow can compose `npm run lint && npm run test` and rely on both being green for this rule.
## Task Commits
Each task was committed atomically on `worktree-agent-adaed29911349f3f4`:
1. **Task 1: ESLint flat config + boundaries plugin + CORE-10 firewall rule**`e9b742d` (chore)
2. **Task 2: CORE-10 firewall test + violator fixture + render target stub**`8c1d839` (test)
**Plan metadata:** _(this commit, by the orchestrator after merge)_`docs(01-02): complete eslint-firewall plan`
## Final shape of `eslint.config.js`
```javascript
import boundaries from 'eslint-plugin-boundaries';
import tseslint from 'typescript-eslint';
export default [
// 1. Default-lint exclusions (the violator fixture lives under
// src/sim/__test_violation__/ and must NOT trip CI).
{ ignores: ['src/sim/__test_violation__/**', 'dist/**', 'node_modules/**', 'coverage/**', '*.tsbuildinfo'] },
// 2. Phase-1 architectural firewall (CORE-10).
{
files: ['src/**/*.{ts,tsx,js,jsx,mjs,cjs}'],
plugins: { boundaries },
languageOptions: {
parser: tseslint.parser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module', ecmaFeatures: { jsx: true } },
},
settings: {
'boundaries/elements': [
{ type: 'sim', pattern: 'src/sim/**' },
{ type: 'render', pattern: 'src/render/**' },
{ type: 'ui', pattern: 'src/ui/**' },
{ type: 'save', pattern: 'src/save/**' },
{ type: 'content', pattern: 'src/content/**' },
{ type: 'audio', pattern: 'src/audio/**' },
{ type: 'store', pattern: 'src/store/**' },
{ type: 'app', pattern: 'src/{main,App,PhaserGame}.{ts,tsx}' },
{ type: 'game', pattern: 'src/game/**' },
],
'boundaries/include': ['src/**/*'],
'boundaries/ignore': ['src/vite-env.d.ts', 'src/__sentinel__.test.ts'],
'import/resolver': {
typescript: {
alwaysTryTypes: true,
project: ['./tsconfig.app.json', './tsconfig.node.json'],
noWarnOnMultipleProjects: true,
},
},
},
rules: {
'boundaries/element-types': ['error', {
default: 'allow',
rules: [
{ from: ['sim'], disallow: ['render', 'ui'] },
],
}],
},
},
];
```
## ESLint version landscape
Per Plan 01's drift report:
- **ESLint:** `^9.39.4` (installed by Plan 01, untouched here). Flat config only — no legacy `.eslintrc.*` file ever existed in the repo, so Task 1 was a creation, not a migration.
- **eslint-plugin-boundaries:** `^6.0.2` (installed by Plan 01, untouched here).
- **typescript-eslint:** `^8.59.2` — added by THIS plan (Task 1, devDep). Parser only (`tseslint.parser`); no rule sets are enabled.
- **eslint-import-resolver-typescript:** `^4.x` (latest installed) — added by THIS plan (Task 2, devDep). Required for the boundaries plugin's element classification to follow extension-less TS imports to disk.
## Verification snapshot
| Gate | Command | Result |
|------|---------|--------|
| Lint clean codebase | `npm run lint` | exit 0, 0 errors, 0 warnings |
| Firewall test passes | `npx vitest run src/sim/__test_violation__/lint-firewall.test.ts` | exit 0, 1/1 pass |
| Full test suite | `npm test` | exit 0, 2/2 pass (sentinel + firewall) |
| TS-strict build | `npm run build` | exit 0, dist/ produced |
| Rule fires when invoked | `npx eslint --no-ignore src/sim/__test_violation__/violator.ts` | exit 1, `boundaries/element-types` error mentioning sim/render |
## Decisions Made
See the `key-decisions` frontmatter block above. Brief rationale for each:
1. **Omitted broader rule sets** — Plan 02 owns ONE rule (CORE-10). Pulling in `js.configs.recommended` would expand scope to dozens of code-quality rules on a clean greenfield codebase that Plan 01's TS-strict + manual-curation discipline already covers. Future phases may add rule sets on top without disturbing the firewall block.
2. **Real render target file (not a non-existent path)** — empirical override of the plan's "doesn't need to exist as a real module" guidance. See Deviations below.
3. **TypeScript resolver wired in** — required by the boundaries plugin to classify import targets. See Deviations below.
4. **Kept `boundaries/element-types` (not `boundaries/dependencies`)** — followed the plan's Pattern 5 spec verbatim. The plugin's stderr deprecation notices are informational; they don't count as ESLint warnings and don't trip `--max-warnings 0`.
5. **Test-fixture dir excluded from `tsconfig.app.json`** — the firewall test uses `node:path` and `process`, which aren't in the DOM-only app lib config. Vitest discovers tests via its own glob.
6. **Suppressed multi-project resolver warning** — Plan 01 deliberately uses the referenced-projects tsconfig layout; the resolver's warning is asking us to undo Plan 01's design.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 — Blocking] Added `typescript-eslint` (parser only) so ESLint can parse .ts/.tsx**
- **Found during:** Task 1, Step 4 (running `npm run lint` after writing the initial config).
- **Issue:** ESLint's default parser (Espree) cannot parse TypeScript syntax or JSX. Initial `npm run lint` produced 5 "Parsing error: Unexpected token" errors against `src/main.tsx`, `src/App.tsx`, `src/PhaserGame.tsx`, `src/game/main.ts`, `src/game/scenes/Boot.ts`. Without a TS-aware parser, `npm run lint` cannot exit 0 on a TypeScript-strict scaffold, which violates Task 1's `<verify>` gate.
- **Fix:** Installed `typescript-eslint@^8.59.2` (the meta-package that bundles `@typescript-eslint/parser`). Wired ONLY the parser via `languageOptions.parser: tseslint.parser` in `eslint.config.js`. NO `tseslint.configs.*` rule sets are enabled — Plan 02's discipline of owning exactly one architectural rule is preserved.
- **Files modified:** `package.json`, `package-lock.json`, `eslint.config.js`.
- **Verification:** `npm run lint` exits 0 with 0 errors / 0 warnings (verified via `-f json` JSON formatter).
- **Committed in:** `e9b742d` (Task 1 commit).
**2. [Rule 1 — Bug] Created `src/render/__firewall_target__.ts` as a real import target for the violator**
- **Found during:** Task 2, Step 3 (running the Vitest test for the first time — it failed with `expected 0 to be greater than 0`, meaning the rule did not fire even though the violator was clearly a sim-importing-render shape).
- **Issue:** The plan's Step 1 said "ESLint's boundaries plugin lints the import path against element-type rules without resolving the module" — implying the violator could import a non-existent path like `'../../render/this-file-does-not-exist'`. This is empirically false. Running the boundaries plugin with `ESLINT_PLUGIN_BOUNDARIES_DEBUG=1` showed the import target classified as `{ type: null, isUnknown: true }`, and the rule then has nothing to disallow against and silently skips. The plugin REQUIRES the import target to resolve to a real file on disk so it can match the file path against element patterns.
- **Fix:** (a) Created `src/render/__firewall_target__.ts` exporting a single marker constant. (b) Updated the violator to import from this real file: `import { FIREWALL_TARGET_MARKER } from '../../render/__firewall_target__';`. Added documentation comments in both files explaining the role.
- **Files modified:** `src/sim/__test_violation__/violator.ts`, `src/render/__firewall_target__.ts` (new).
- **Verification:** `ESLINT_PLUGIN_BOUNDARIES_DEBUG=1 npx eslint ...` now shows the target classified as `{ type: 'render', isUnknown: false }`. After also fixing #3 below, the rule fires with: `Dependencies to elements of type "render" are not allowed in elements of type "sim" and captured "null". Denied by rule at index 0 boundaries/element-types`.
- **Committed in:** `8c1d839` (Task 2 commit).
**3. [Rule 1 — Bug] Added `eslint-import-resolver-typescript` so the boundaries plugin can resolve extension-less TS imports**
- **Found during:** Task 2, Step 3 (after fix #2, the target was STILL `isUnknown` because `'../../render/__firewall_target__'` has no `.ts` extension and Node-style resolution doesn't add one).
- **Issue:** `eslint-plugin-boundaries` uses `eslint-plugin-import`'s resolver mechanism to follow imports to disk. The default resolver is Node-style and refuses to add a `.ts` extension to extension-less imports. Without a TS-aware resolver, EVERY in-repo TS import is marked `isUnknown` and the firewall rule silently no-ops — even with a real target file present. This is a load-bearing wiring requirement the plan didn't anticipate (the plan focused on the rule-config shape; resolver wiring was implicit).
- **Fix:** Installed `eslint-import-resolver-typescript` (latest, ^4.x) as a devDep. Added `'import/resolver': { typescript: { alwaysTryTypes: true, project: ['./tsconfig.app.json', './tsconfig.node.json'], noWarnOnMultipleProjects: true } }` to the boundaries config block in `eslint.config.js`. The two-project array reflects Plan 01's referenced-projects tsconfig layout.
- **Files modified:** `package.json`, `package-lock.json`, `eslint.config.js`.
- **Verification:** Debug output now shows imports resolving to disk paths and classifying correctly; the rule fires against the violator; the Vitest test passes. `npm run lint` still exits 0 with 0 errors / 0 warnings.
- **Committed in:** `8c1d839` (Task 2 commit).
**4. [Rule 3 — Blocking] Excluded `*.test.ts` and `src/sim/__test_violation__/**` from `tsconfig.app.json` build glob**
- **Found during:** Task 2, Step 4 (running `npm run build` after Task 2 created the test file — `tsc -b` failed with 3 TS2591 errors on the test file's `node:path` and `process` references).
- **Issue:** The firewall test uses Node APIs (`node:path` for `resolve`, `process.cwd()`) but `tsconfig.app.json` has `lib: ["ES2022", "DOM", "DOM.Iterable"]` and `types: ["vite/client"]` — no Node types. The original `tsconfig.app.json` had `include: ["src"]` with no `exclude` block, so `tsc -b` tried to compile all of `src/` including test files. The test file was correct TypeScript for its target environment (Node, via Vitest), but wrong for the app's DOM-only project.
- **Fix:** Added an `exclude: ["src/**/*.test.ts", "src/**/*.test.tsx", "src/sim/__test_violation__/**"]` block to `tsconfig.app.json`. Vitest discovers tests via its own `include` glob in `vitest.config.ts`, completely independent of tsconfig — so this only narrows what `tsc -b` compiles, not what `npm test` runs.
- **Files modified:** `tsconfig.app.json`.
- **Verification:** `npm run build` now exits 0 (`tsc -b` clean, `vite build` clean); `npm test` still exits 0 with 2/2 passing.
- **Committed in:** `8c1d839` (Task 2 commit).
---
**Total deviations:** 4 auto-fixed (2 blocking, 2 bug fixes — all under the Rule 1/2/3 auto-fix umbrella; none required architectural changes per Rule 4).
**Impact on plan:** All four deviations are mechanical wiring requirements the plan's high-level spec didn't anticipate. The plan's intent (CORE-10 enforced + provably tested) is satisfied exactly. No scope creep — the only added dependencies are tooling (`typescript-eslint` parser, `eslint-import-resolver-typescript`); no rule sets, no broader lint coverage. Wave-2 sibling plans (0306) are unaffected.
## Issues Encountered
- **`node_modules/` was not present in the worktree at agent spawn.** Worktrees inherit `.git` but not the working tree's installed dependencies. Ran `npm ci --no-audit --no-fund` (10s, 209 packages) before any other work. Time cost: ~10s.
- **Boundaries plugin debug spelunking.** Three full debug-output cycles (`ESLINT_PLUGIN_BOUNDARIES_DEBUG=1`) were needed to diagnose deviations #2 and #3. The plugin's debug output is excellent — it shows the classification of both source and target files, which made the root causes visible immediately. Time cost: ~5 min.
## Authentication Gates
None — Phase 1 is build/dev tooling only; no external auth needed.
## Threat Flags
None — this plan is static-analysis tooling (no network, no auth, no file IO outside the build), and the boundary rule's mitigation effect is architectural integrity (preventing the simulation core from becoming non-deterministic or non-headless), not security. The threat-model section of the PLAN.md says "No security-relevant code in this plan; this is static-analysis tooling."
## Known Stubs
- **`src/render/__firewall_target__.ts`** is a one-line export-stub for the firewall test ONLY. It is NOT part of the runtime render layer. `src/render/` is otherwise empty in Phase 1 (only `.gitkeep`). Phase 2 will populate `src/render/` with real Phaser scenes. If the firewall test is ever rewritten to point at a real render module, this stub should be removed. Documented in the file's header comment.
- **`src/sim/__test_violation__/violator.ts`** is intentionally a deliberate-violation fixture, lint-tested only. It is excluded from both the lint glob and the TS build. Documented in the file's header comment.
These are intentional, plan-anticipated stubs. They exist because the test infrastructure for an architectural rule MUST stress-test the rule end-to-end, and that requires a real (not synthetic) sim → render edge in code.
## Next Plan Readiness
- **Plan 03 (save layer):** Unaffected. The `boundaries` rule does not restrict imports into `src/save/`; Plan 03 can populate `src/save/` freely. The TS resolver and the test-file exclusion in `tsconfig.app.json` will benefit Plan 03's IDB tests too — they should add their `src/save/**/*.test.ts` files and they'll be picked up by Vitest while excluded from `tsc -b`.
- **Plan 04 (content pipeline):** Unaffected. `src/content/` is a declared element type but has no `disallow` rule against it.
- **Plan 05 (asset provenance):** Unaffected — Plan 05 writes `scripts/validate-assets.mjs`, which lives outside `src/` and is therefore outside the boundaries rule's scope.
- **Plan 06 (doctrine docs):** Unaffected — pure markdown.
- **Plan 07 (CI workflow):** Ready. CI can compose `npm run lint && npm run test && npm run build` with high confidence — all three are green here, and the firewall rule has an automated test (not just "lint runs"), so a future regression that breaks the rule will be caught immediately.
## Self-Check
- [x] `eslint.config.js` exists at repo root — `test -f eslint.config.js` PASS.
- [x] `eslint.config.js` contains `boundaries/element-types` — PASS.
- [x] All 7 firewall element types declared (sim, render, ui, save, content, audio, store) — verified by individual `grep "type: '<name>'"` for each. PASS.
- [x] `disallow: ['render', 'ui']` from `sim` — PASS (line: `{ from: ['sim'], disallow: ['render', 'ui'] },`).
- [x] No legacy `.eslintrc.*` file remains — PASS (`ls .eslintrc.*` returns no matches).
- [x] `__test_violation__` is in the `ignores` block — PASS.
- [x] `npm run lint` exits 0 — PASS (0 errors, 0 warnings via JSON formatter).
- [x] `src/sim/__test_violation__/violator.ts` exists and imports from `'../../render/'` — PASS.
- [x] `src/sim/__test_violation__/lint-firewall.test.ts` exists, references `boundaries/element-types`, imports `ESLint` from `eslint`, and asserts both `sim` and `render|ui` — PASS (count of toMatch lines mentioning sim/render = 2).
- [x] `npx vitest run src/sim/__test_violation__/lint-firewall.test.ts` exits 0 — PASS.
- [x] `npx eslint --no-ignore src/sim/__test_violation__/violator.ts` exits non-zero — PASS (exit 1, expected error fires).
- [x] `npm run build` exits 0 — PASS.
- [x] `npm test` exits 0 with 2/2 passing — PASS.
- [x] Task 1 commit exists: `e9b742d` — verified in `git log`.
- [x] Task 2 commit exists: `8c1d839` — verified in `git log`.
**## Self-Check: PASSED**
---
*Phase: 01-foundations-and-doctrine*
*Plan: 02 of 7*
*Completed: 2026-05-09*