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>
This commit is contained in:
@@ -0,0 +1,268 @@
|
|||||||
|
---
|
||||||
|
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 (03–06) 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*
|
||||||
Reference in New Issue
Block a user