diff --git a/docs/superpowers/plans/2026-07-25-coverage-integrity-runtime-boundaries.md b/docs/superpowers/plans/2026-07-25-coverage-integrity-runtime-boundaries.md new file mode 100644 index 000000000..d1224d9f8 --- /dev/null +++ b/docs/superpowers/plans/2026-07-25-coverage-integrity-runtime-boundaries.md @@ -0,0 +1,1964 @@ +# Coverage Integrity And Electron Runtime Boundaries Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make Tier A coverage fail closed and add meaningful regression coverage for the HTTP server, remote control, settings, and download-file Electron boundaries. + +**Architecture:** A shared pure Node module classifies runtime-owning TypeScript, validates per-project Istanbul reports, scans child output for collection failures, and evaluates aggregate/per-file ratchets. Existing Electron modules keep their public behavior; tests use real loopback/filesystem behavior where practical and mock only process, persistence, or native-shell boundaries. + +**Tech Stack:** Node.js ESM and `node:test`, TypeScript compiler API, Istanbul coverage maps, Jest/ts-jest, Electron main-process IPC, Node HTTP streams, Angular Jest configuration, Nx, Playwright. + +--- + +## Scope And File Map + +Create: + +- `tools/coverage/coverage-integrity.mjs` — shared source classification, + report validation, output scanning, and ratchet evaluation. +- `tools/coverage/coverage-integrity.test.mjs` — deterministic Node tests using + temporary source/report fixtures. +- `apps/electron-backend/src/app/server/http-server.spec.ts` — real loopback + and temporary-directory HTTP server contracts. +- `apps/electron-backend/src/app/events/remote-control.events.spec.ts` — + registered HTTP handler, IPC, status, validation, and size-limit contracts. + +Modify: + +- `tools/coverage/run-tier-a-coverage.mjs` — stream child output, detect + collection failures, require complete project reports. +- `tools/coverage/merge-coverage.mjs` — reject missing/invalid Tier A inputs + before touching merged output. +- `tools/coverage/coverage-health.mjs` — independently validate completeness + and ratchets. +- `tools/coverage/coverage-policy.json` — store achieved aggregate and + critical-file ratchets. +- `package.json` — run coverage-tool unit tests inside `coverage:ci`. +- `libs/m3u-state/tsconfig.spec.json` — use bundler resolution and include the + shared Electron window declaration so `effects.ts` instruments. +- `apps/electron-backend/src/app/server/http-server.ts` — optional static-root + and server-factory seam; accept port `0` for loopback tests. +- `apps/electron-backend/src/app/events/remote-control.events.ts` — export the + class for isolated instances while preserving the default singleton. +- `apps/electron-backend/src/app/events/settings.events.spec.ts` — persistence, + normalization, remote-control reconciliation, and redaction contracts. +- `apps/electron-backend/src/app/events/database/downloads.events.spec.ts` — + managed-path checks before native shell access. +- `docs/architecture/validation-map.md` — canonical fail-closed and ratchet + maintenance workflow. +- `docs/superpowers/specs/2026-07-25-coverage-integrity-runtime-boundaries-design.md` + — retain the corrected Istanbul-rounded baseline values. + +Do not modify: + +- `apps/electron-backend/src/app/workers/database.worker.ts`; +- workspace dashboard or Stalker search production code; +- type-only/shared-interface source for percentage gain; +- `AGENTS.md` or `CLAUDE.md`, unless execution discovers that the implemented + commands contradict text already present there. + +### Task 0: Rebase And Bootstrap Immediately Before Implementation + +**Files:** + +- Verify only: `package.json` +- Verify only: `pnpm-lock.yaml` +- Verify only: `tools/coverage/coverage-policy.json` + +- [ ] **Step 1: Load the execution skills** + +Read completely before code changes: + +```text +using-superpowers +executing-plans +test-driven-development +electron-pro +typescript +angular-testing +iptvnator-nx-architecture +``` + +Use `systematic-debugging` before changing code in response to any unexpected +failure, and `verification-before-completion`, `release-notes`, and +`github-pr` during the final tasks. + +- [ ] **Step 2: Confirm a clean branch and rebase onto fresh master** + +Run: + +```bash +git status --short --branch +git fetch origin master --prune +git rebase origin/master +git status --short --branch +git log -2 --oneline --decorate +``` + +Expected: the branch is `agent/coverage-integrity-runtime-boundaries`, the +rebase exits 0, and only the committed design/plan are ahead of the latest +`origin/master`. If master changed a scoped source, spec, Jest config, or +coverage tool, re-run the baseline checks below and update this plan's numeric +seed before implementation rather than resolving by assumption. + +- [ ] **Step 3: Refresh locked dependencies and Nx discovery** + +Run: + +```bash +pnpm install --frozen-lockfile +pnpm nx show projects +pnpm nx show projects --withTarget test +``` + +Expected: all commands exit 0; the project list contains +`electron-backend`, `electron-backend-e2e`, and `m3u-state`; the lockfile stays +unchanged. + +- [ ] **Step 4: Reconfirm the fresh baseline** + +Run even when Step 2 reports no new master commits: + +```bash +NX_SKIP_NX_CACHE=true pnpm run coverage:ci +``` + +Expected on unchanged `4e5132cb`: the command exits 0 while printing +`Failed to collect coverage` for `libs/m3u-state/src/lib/effects.ts`, and the +aggregate remains 68.86/58.66/67.19/69.20. Record the fresh aggregate and +selected-file values. If master changed them, replace the numeric ratchet seed +in Task 3; do not lower a baseline already present on newer master. + +### Task 1: Build And Unit-Test The Shared Coverage Integrity Module + +**Files:** + +- Create: `tools/coverage/coverage-integrity.test.mjs` +- Create: `tools/coverage/coverage-integrity.mjs` + +- [ ] **Step 1: Write the failing source-classification and output-scanner tests** + +Create `tools/coverage/coverage-integrity.test.mjs` with Node built-ins and +these contracts: + +```javascript +import assert from 'node:assert/strict'; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { afterEach, describe, it } from 'node:test'; + +import { + createCoverageOutputScanner, + evaluateCoverageRatchets, + hasRuntimeOwnedStatement, + validateProjectCoverage, + validateRequiredProjectReports, +} from './coverage-integrity.mjs'; + +const temporaryRoots = []; + +afterEach(() => { + for (const root of temporaryRoots.splice(0)) { + rmSync(root, { recursive: true, force: true }); + } +}); + +describe('hasRuntimeOwnedStatement', () => { + it('excludes type-only, import-only, and pure re-export source', () => { + assert.equal( + hasRuntimeOwnedStatement( + 'import type { Settings } from "./settings";' + ), + false + ); + assert.equal( + hasRuntimeOwnedStatement('interface Settings { enabled: boolean }'), + false + ); + assert.equal( + hasRuntimeOwnedStatement('export type Mode = "live" | "vod";'), + false + ); + assert.equal( + hasRuntimeOwnedStatement('export { value } from "./value";'), + false + ); + assert.equal( + hasRuntimeOwnedStatement('declare const injected: string;'), + false + ); + }); + + it('includes emitted declarations and executable statements', () => { + assert.equal( + hasRuntimeOwnedStatement('export const enabled = true;'), + true + ); + assert.equal( + hasRuntimeOwnedStatement( + 'export class RuntimeBoundary { start() {} }' + ), + true + ); + assert.equal(hasRuntimeOwnedStatement('console.log("runtime");'), true); + }); +}); + +describe('createCoverageOutputScanner', () => { + it('detects an ANSI-colored marker split across chunks', () => { + const scanner = createCoverageOutputScanner(128); + + scanner.push('\u001b[31mFailed to collect'); + scanner.push(' coverage from /workspace/effects.ts\u001b[39m'); + + assert.equal(scanner.collectionFailed, true); + }); + + it('keeps only a bounded rolling tail', () => { + const scanner = createCoverageOutputScanner(32); + + scanner.push('x'.repeat(256)); + + assert.equal(scanner.tail.length, 32); + assert.equal(scanner.collectionFailed, false); + }); +}); +``` + +- [ ] **Step 2: Add failing temporary-report tests** + +In the same file, add helpers that write one runtime source plus a synthetic +Istanbul entry: + +```javascript +function makeProjectFixture() { + const workspaceRoot = mkdtempSync( + path.join(tmpdir(), 'iptvnator-coverage-integrity-') + ); + temporaryRoots.push(workspaceRoot); + const sourceRoot = 'libs/example/src'; + const projectRoot = 'libs/example'; + const sourcePath = path.join(workspaceRoot, sourceRoot, 'runtime.ts'); + const reportPath = path.join( + workspaceRoot, + 'coverage', + projectRoot, + 'coverage-final.json' + ); + + mkdirSync(path.dirname(sourcePath), { recursive: true }); + mkdirSync(path.dirname(reportPath), { recursive: true }); + writeFileSync(sourcePath, 'export const runtime = true;\n'); + + const project = { + name: 'example', + root: projectRoot, + sourceRoot, + }; + + return { project, reportPath, sourcePath, workspaceRoot }; +} + +function coverageEntry(filePath, hits = [0]) { + const statementMap = Object.fromEntries( + hits.map((_, index) => [ + index, + { + start: { line: index + 1, column: 0 }, + end: { line: index + 1, column: 28 }, + }, + ]) + ); + const statementHits = Object.fromEntries( + hits.map((hit, index) => [index, hit]) + ); + + return { + path: filePath, + statementMap, + fnMap: {}, + branchMap: {}, + s: statementHits, + f: {}, + b: {}, + }; +} + +describe('validateProjectCoverage', () => { + it('accepts a report containing every runtime-owning file', () => { + const fixture = makeProjectFixture(); + writeFileSync( + fixture.reportPath, + JSON.stringify({ + [fixture.sourcePath]: coverageEntry(fixture.sourcePath), + }) + ); + + const result = validateProjectCoverage(fixture); + + assert.deepEqual(result.errors, []); + assert.equal(result.report?.project.name, 'example'); + }); + + it('reports a runtime-owning source omitted from a valid report', () => { + const fixture = makeProjectFixture(); + writeFileSync(fixture.reportPath, '{}'); + + const result = validateProjectCoverage(fixture); + + assert.equal(result.errors.length, 1); + assert.match(result.errors[0], /runtime\.ts/); + }); + + it('reports missing and invalid project reports', () => { + const missing = makeProjectFixture(); + const invalid = makeProjectFixture(); + writeFileSync(invalid.reportPath, '{ invalid json'); + + assert.match( + validateProjectCoverage(missing).errors[0], + /did not produce/ + ); + assert.match( + validateProjectCoverage(invalid).errors[0], + /invalid JSON/ + ); + }); + + it('requires every configured Tier A report', () => { + const fixture = makeProjectFixture(); + + const result = validateRequiredProjectReports({ + projects: [fixture.project], + workspaceRoot: fixture.workspaceRoot, + }); + + assert.equal(result.reports.length, 0); + assert.match(result.errors[0], /example/); + }); +}); +``` + +- [ ] **Step 3: Add failing aggregate and critical-file ratchet tests** + +Add: + +```javascript +describe('evaluateCoverageRatchets', () => { + it('reports aggregate percentage regression', () => { + const errors = evaluateCoverageRatchets({ + coverageData: {}, + mergedSummary: { + statements: { covered: 68, total: 100, pct: 68 }, + branches: { covered: 57, total: 100, pct: 57 }, + functions: { covered: 67, total: 100, pct: 67 }, + lines: { covered: 69, total: 100, pct: 69 }, + }, + ratchet: { + merged: { + statements: 68.86, + branches: 58.66, + functions: 67.19, + lines: 69.2, + }, + criticalFiles: [], + }, + workspaceRoot: '/workspace', + }); + + assert.equal(errors.length, 4); + assert.match(errors[0], /statements.*68.*68\.86/); + }); + + it('requires both covered statements and percentage for a critical file', () => { + const workspaceRoot = '/workspace'; + const filePath = path.join(workspaceRoot, 'apps/runtime.ts'); + const errors = evaluateCoverageRatchets({ + coverageData: { + [filePath]: coverageEntry(filePath, [1, 0]), + }, + mergedSummary: { + statements: { covered: 1, total: 1, pct: 100 }, + branches: { covered: 0, total: 0, pct: 100 }, + functions: { covered: 0, total: 0, pct: 100 }, + lines: { covered: 1, total: 1, pct: 100 }, + }, + ratchet: { + merged: { + statements: 0, + branches: 0, + functions: 0, + lines: 0, + }, + criticalFiles: [ + { + path: 'apps/runtime.ts', + statements: { + minimumCovered: 2, + minimumPercent: 75, + }, + }, + ], + }, + workspaceRoot, + }); + + assert.equal(errors.length, 2); + assert.match(errors[0], /apps\/runtime\.ts/); + }); + + it('reports a critical file missing from merged coverage', () => { + const errors = evaluateCoverageRatchets({ + coverageData: {}, + mergedSummary: { + statements: { covered: 0, total: 0, pct: 100 }, + branches: { covered: 0, total: 0, pct: 100 }, + functions: { covered: 0, total: 0, pct: 100 }, + lines: { covered: 0, total: 0, pct: 100 }, + }, + ratchet: { + merged: { + statements: 0, + branches: 0, + functions: 0, + lines: 0, + }, + criticalFiles: [ + { + path: 'apps/missing.ts', + statements: { + minimumCovered: 1, + minimumPercent: 0, + }, + }, + ], + }, + workspaceRoot: '/workspace', + }); + + assert.match(errors[0], /missing from merged coverage/); + }); +}); +``` + +- [ ] **Step 4: Run the Node tests to verify RED** + +Run: + +```bash +node --test tools/coverage/coverage-integrity.test.mjs +``` + +Expected: FAIL with `ERR_MODULE_NOT_FOUND` for +`tools/coverage/coverage-integrity.mjs`. + +- [ ] **Step 5: Implement source classification and the bounded scanner** + +Create `tools/coverage/coverage-integrity.mjs`. Use `unknown`-equivalent +runtime validation rather than trusting parsed JSON shapes. The core +classification and scanner must follow: + +```javascript +import { createRequire } from 'node:module'; +import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'; +import path from 'node:path'; +import ts from 'typescript'; + +const require = createRequire(import.meta.url); +const { createCoverageMap } = require('istanbul-lib-coverage'); + +export const COVERAGE_COLLECTION_FAILURE = 'Failed to collect coverage'; +const ANSI_ESCAPE = /\u001b\[[0-?]*[ -/]*[@-~]/g; + +function toPosix(filePath) { + return filePath.split(path.sep).join('/'); +} + +function hasDeclareModifier(statement) { + const modifiers = ts.canHaveModifiers(statement) + ? (ts.getModifiers(statement) ?? []) + : []; + return modifiers.some( + (modifier) => modifier.kind === ts.SyntaxKind.DeclareKeyword + ); +} + +export function hasRuntimeOwnedStatement(sourceText, fileName = 'source.ts') { + const source = ts.createSourceFile( + fileName, + sourceText, + ts.ScriptTarget.Latest, + true + ); + + return source.statements.some((statement) => { + if (hasDeclareModifier(statement)) { + return false; + } + return !( + ts.isImportDeclaration(statement) || + ts.isImportEqualsDeclaration(statement) || + ts.isInterfaceDeclaration(statement) || + ts.isTypeAliasDeclaration(statement) || + ts.isExportDeclaration(statement) || + ts.isEmptyStatement(statement) + ); + }); +} + +export function createCoverageOutputScanner(maxCharacters = 4096) { + let tail = ''; + let collectionFailed = false; + + return { + get collectionFailed() { + return collectionFailed; + }, + get tail() { + return tail; + }, + push(chunk) { + tail = `${tail}${String(chunk).replace(ANSI_ESCAPE, '')}`.slice( + -maxCharacters + ); + if (tail.includes(COVERAGE_COLLECTION_FAILURE)) { + collectionFailed = true; + } + }, + }; +} +``` + +Add recursive enumeration with exactly these exclusions: + +```javascript +function isExcludedSource(filePath) { + const file = toPosix(filePath); + return ( + /\.(spec|test)\.ts$/.test(file) || + file.endsWith('.d.ts') || + file.endsWith('/test-setup.ts') || + file.includes('/test-stubs/') || + /\.generated\./.test(file) || + file.includes('/environments/') || + file.endsWith('/index.ts') + ); +} +``` + +`runtimeOwningSourceFiles(workspaceRoot, sourceRoot)` must recursively read +only `.ts` files, apply `isExcludedSource`, parse the file, and return sorted +absolute paths whose source has a runtime-owned statement. + +- [ ] **Step 6: Implement report validation** + +Implement these stable return shapes: + +```javascript +// validateProjectCoverage(...) +{ + errors: string[], + report: undefined | { + data: Record, + path: string, + project: object, + }, +} + +// validateRequiredProjectReports(...) +{ + errors: string[], + reports: Array<{ + data: Record, + path: string, + project: object, + }>, +} +``` + +`validateProjectCoverage({ workspaceRoot, project })` must: + +1. resolve `coverage//coverage-final.json`; +2. report a missing file using the project name and relative report path; +3. parse JSON and reject arrays, `null`, and invalid JSON; +4. normalize report keys with `path.resolve`; +5. compare them to `runtimeOwningSourceFiles`; and +6. emit one diagnostic per missing source, including project and relative + source path. + +`validateRequiredProjectReports` must call it for every project and preserve +policy order in `reports`. + +- [ ] **Step 7: Implement ratchet evaluation** + +`evaluateCoverageRatchets` must: + +- compare each merged metric with its numeric minimum; +- build an Istanbul map from `coverageData`; +- require every critical path to exist; +- obtain `fileCoverage.toSummary().toJSON().statements`; +- compare both `covered` and `pct`; and +- return diagnostics rather than exiting. + +Use messages of the form: + +```text +Merged statements coverage regressed: observed 68%, required at least 68.86%. +Critical coverage apps/runtime.ts statements covered regressed: observed 1, required at least 2. +Critical coverage apps/runtime.ts statements percent regressed: observed 50%, required at least 75%. +``` + +- [ ] **Step 8: Run the module tests to verify GREEN** + +Run: + +```bash +node --test tools/coverage/coverage-integrity.test.mjs +``` + +Expected: all classification, marker, report, and ratchet tests pass. + +- [ ] **Step 9: Commit the pure integrity module** + +Run: + +```bash +git add \ + tools/coverage/coverage-integrity.mjs \ + tools/coverage/coverage-integrity.test.mjs +git commit -m "test(coverage): add integrity primitives" +``` + +Expected: one commit containing only the pure module and its Node tests. + +### Task 2: Wire The Gate And Fix The Real `m3u-state` Instrumentation Failure + +**Files:** + +- Modify: `tools/coverage/run-tier-a-coverage.mjs` +- Modify: `tools/coverage/merge-coverage.mjs` +- Modify: `tools/coverage/coverage-health.mjs` +- Modify: `package.json` +- Modify: `libs/m3u-state/tsconfig.spec.json` + +- [ ] **Step 1: Replace synchronous inherited child output with streamed scanning** + +In `run-tier-a-coverage.mjs`, replace `spawnSync` with `spawn`, import +`createCoverageOutputScanner` and `validateProjectCoverage`, and add: + +```javascript +async function runCoverageProject(project) { + const args = buildNxArgs(project); + console.log(`\n==> Collecting coverage for ${project.name}`); + console.log(`pnpm ${args.join(' ')}`); + + const scanner = createCoverageOutputScanner(); + const child = spawn('pnpm', args, { + cwd: workspaceRoot, + env: { + ...process.env, + CI: process.env.CI ?? 'true', + NX_TASKS_RUNNER_DYNAMIC_OUTPUT: 'false', + }, + stdio: ['inherit', 'pipe', 'pipe'], + }); + + for (const [stream, destination] of [ + [child.stdout, process.stdout], + [child.stderr, process.stderr], + ]) { + stream.on('data', (chunk) => { + scanner.push(chunk); + destination.write(chunk); + }); + } + + const result = await new Promise((resolve, reject) => { + child.once('error', reject); + child.once('close', (code, signal) => resolve({ code, signal })); + }); + + if (result.code !== 0 || result.signal) { + return result.code ?? 1; + } + if (scanner.collectionFailed) { + console.error( + `Coverage collection failed while testing ${project.name}.` + ); + return 1; + } + + const validation = validateProjectCoverage({ + project, + workspaceRoot, + }); + for (const error of validation.errors) { + console.error(`Error: ${error}`); + } + return validation.errors.length === 0 ? 0 : 1; +} +``` + +Replace the final `spawnSync` loop with top-level awaited sequential calls. +Exit immediately after a nonzero project result so later projects cannot hide +the failed owner. + +- [ ] **Step 2: Make merge input fail closed before output mutation** + +In `merge-coverage.mjs`, remove the `.filter(existsSync)` construction. Call: + +```javascript +const validation = validateRequiredProjectReports({ + projects: policy.unitCoverage.tierA, + workspaceRoot, +}); + +if (validation.errors.length > 0) { + for (const error of validation.errors) { + console.error(`Error: ${error}`); + } + process.exit(1); +} + +const coverageInputs = validation.reports; +``` + +Perform this before `rmSync(outputDir, ...)`. Merge each `input.data` instead +of reopening its path. Log `coverageInputs.length`, which must be exactly 30 +on the current policy. + +- [ ] **Step 3: Add independent health validation** + +In `coverage-health.mjs`: + +- import `evaluateCoverageRatchets`, `validateProjectCoverage`, and + `validateRequiredProjectReports`; +- under `--require-report`, validate every Tier A report and append all + diagnostics to `errors`; +- without `--require-report`, validate only report files that actually exist, + treating invalid JSON or missing runtime source in a present report as an + error rather than printing 30 missing-report warnings; +- after loading merged `coverage-summary.json`, load merged + `coverage-final.json` and evaluate `policy.reporting.coverageRatchet` when + the policy has one. + +Do not change existing source-root/spec ownership checks, E2E tag warnings, or +changed-file warnings. + +- [ ] **Step 4: Run targeted `m3u-state` coverage to verify the real RED** + +Run: + +```bash +node --test tools/coverage/coverage-integrity.test.mjs +pnpm run coverage:unit:ci -- --projects=m3u-state +``` + +Expected: + +- Node tests PASS. +- Targeted coverage exits nonzero after relaying the existing TypeScript + diagnostics and reports `Coverage collection failed while testing +m3u-state`. + +This is the real regression proof: before the runner wiring, the same Jest +failure exited 0. + +- [ ] **Step 5: Align the `m3u-state` spec compiler configuration** + +Replace `libs/m3u-state/tsconfig.spec.json` with: + +```json +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "preserve", + "target": "es2016", + "types": ["jest", "node"], + "moduleResolution": "bundler" + }, + "files": ["src/test-setup.ts", "../../global.d.ts"], + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} +``` + +This matches the working Angular services test compiler mode, lets TypeScript +honor Angular Material package exports, and includes the shared +`Window.electron` contract. Do not add a local fake Material declaration to +`m3u-state`. + +- [ ] **Step 6: Add coverage-tool tests to `coverage:ci`** + +In `package.json`, add: + +```json +"coverage:tools:test": "node --test tools/coverage/coverage-integrity.test.mjs" +``` + +Change `coverage:ci` so it starts with: + +```json +"coverage:ci": "pnpm run coverage:tools:test && pnpm run coverage:policy:check && pnpm run coverage:unit:ci && pnpm run coverage:merge && node tools/coverage/coverage-health.mjs --require-report" +``` + +- [ ] **Step 7: Verify targeted instrumentation is GREEN and complete** + +Run: + +```bash +pnpm run coverage:tools:test +pnpm nx test m3u-state --skip-nx-cache --runInBand +pnpm run coverage:unit:ci -- --projects=m3u-state +node --input-type=module -e 'import fs from "node:fs"; import path from "node:path"; const report=JSON.parse(fs.readFileSync("coverage/libs/m3u-state/coverage-final.json","utf8")); const target=path.resolve("libs/m3u-state/src/lib/effects.ts"); if (!report[target]) throw new Error(`${target} missing`); console.log("effects.ts present");' +``` + +Expected: all commands exit 0, no `Failed to collect coverage` appears, and +the final command prints `effects.ts present`. + +- [ ] **Step 8: Commit the fail-closed runner and instrumentation fix** + +Run: + +```bash +git add \ + package.json \ + tools/coverage/run-tier-a-coverage.mjs \ + tools/coverage/merge-coverage.mjs \ + tools/coverage/coverage-health.mjs \ + libs/m3u-state/tsconfig.spec.json +git commit -m "fix(coverage): fail on incomplete instrumentation" +``` + +Expected: one commit with the gate wiring and the real `effects.ts` fix. + +### Task 3: Establish A Real Failing Critical-File Ratchet + +**Files:** + +- Modify but do not yet commit: + `tools/coverage/coverage-policy.json` + +- [ ] **Step 1: Add the provisional ratchet** + +Under `reporting`, add: + +```json +"coverageRatchet": { + "merged": { + "statements": 68.86, + "branches": 58.66, + "functions": 67.19, + "lines": 69.2 + }, + "criticalFiles": [ + { + "path": "apps/electron-backend/src/app/server/http-server.ts", + "statements": { + "minimumCovered": 1, + "minimumPercent": 0 + } + }, + { + "path": "apps/electron-backend/src/app/events/remote-control.events.ts", + "statements": { + "minimumCovered": 1, + "minimumPercent": 0 + } + }, + { + "path": "apps/electron-backend/src/app/events/settings.events.ts", + "statements": { + "minimumCovered": 17, + "minimumPercent": 59.25 + } + }, + { + "path": "apps/electron-backend/src/app/events/database/downloads.events.ts", + "statements": { + "minimumCovered": 70, + "minimumPercent": 46.93 + } + } + ] +} +``` + +The existing files use their fresh master percentage and require one +additional covered statement. The two 0% files require one covered statement. +These are minimal progress probes, not final thresholds. + +- [ ] **Step 2: Verify the critical acceptance test is RED** + +Use the fresh baseline reports from Task 0: + +```bash +pnpm run coverage:merge +node tools/coverage/coverage-health.mjs --require-report +``` + +Expected: merge succeeds with exactly 30 reports; health exits nonzero and +names all selected files still below the provisional ratchet. In particular, +`http-server.ts` and `remote-control.events.ts` report zero covered +statements. + +- [ ] **Step 3: Keep the provisional policy change unstaged** + +Run: + +```bash +git status --short +``` + +Expected: only `tools/coverage/coverage-policy.json` is intentionally dirty. +Do not stage it in Tasks 4–7; it remains the real red acceptance test until +the behavioral suites raise the observed values. + +### Task 4: Cover The HTTP Server Through Real Loopback And Filesystem Contracts + +**Files:** + +- Create: `apps/electron-backend/src/app/server/http-server.spec.ts` +- Modify: `apps/electron-backend/src/app/server/http-server.ts` + +- [ ] **Step 1: Write the complete HTTP contract suite before the seam exists** + +The spec must: + +- create a temporary static directory with `index.html`, `app.js`, + `styles.css`, `data.json`, and an unknown-extension asset; +- inject a real `http.createServer` wrapper that captures the Node server; +- call `start(0)`, await `listening`, and request + `127.0.0.1:`; +- stop and remove the temporary tree in `afterEach`. + +Use these test helpers: + +```typescript +interface HttpResponseSnapshot { + body: Buffer; + contentType: string | undefined; + statusCode: number; +} + +function request( + port: number, + requestPath: string, + method = 'GET' +): Promise { + return new Promise((resolve, reject) => { + const req = http.request( + { + host: '127.0.0.1', + method, + path: requestPath, + port, + }, + (res) => { + const chunks: Buffer[] = []; + res.on('data', (chunk: Buffer) => chunks.push(chunk)); + res.on('end', () => + resolve({ + body: Buffer.concat(chunks), + contentType: res.headers['content-type'], + statusCode: res.statusCode ?? 0, + }) + ); + } + ); + req.once('error', reject); + req.end(); + }); +} +``` + +Add assertions for: + +1. `/` returns the exact index body as `text/html`; +2. `.js`, `.css`, `.json`, and an unknown extension return the expected MIME; +3. a client route falls back to the exact index body; +4. missing index returns plain-text 404; +5. a registered API handler receives the request and bypasses static serving; +6. unknown `/api/remote-control/...` returns JSON 404; +7. `/../../outside-secret.txt` never returns an outside file; +8. duplicate `start` creates/listens once; +9. disable stops the server; and +10. an enabled port change stops then starts once with the new port. + +- [ ] **Step 2: Run the focused spec to verify RED** + +Run: + +```bash +pnpm nx test electron-backend \ + --testPathPattern=http-server.spec.ts \ + --runInBand +``` + +Expected: FAIL at TypeScript compilation because `HttpServer` does not yet +accept the injected `distPath` and `createServer` options, and `start(0)` +currently ignores port zero. + +- [ ] **Step 3: Add the minimal constructor seam** + +In `http-server.ts`, add flat types: + +```typescript +type HttpServerFactory = (requestListener: http.RequestListener) => http.Server; + +interface HttpServerOptions { + createServer?: HttpServerFactory; + distPath?: string; +} +``` + +Add a factory field and constructor: + +```typescript +private readonly createServer: HttpServerFactory; + +constructor(options: HttpServerOptions = {}) { + this.createServer = + options.createServer ?? + ((requestListener) => http.createServer(requestListener)); + this.distPath = options.distPath ?? null; +} +``` + +Replace: + +```typescript +if (port) { +``` + +with: + +```typescript +if (port !== undefined) { +``` + +and replace the direct `http.createServer` call with `this.createServer`. +Leave the production singleton as: + +```typescript +export const httpServer = new HttpServer(); +``` + +No production caller passes port zero or constructor options, so normal +runtime behavior stays unchanged. + +- [ ] **Step 4: Run HTTP tests and Electron coverage** + +Run: + +```bash +pnpm nx test electron-backend \ + --testPathPattern=http-server.spec.ts \ + --runInBand +pnpm run coverage:unit:ci -- --projects=electron-backend +pnpm run coverage:merge +node tools/coverage/coverage-health.mjs --require-report +``` + +Expected: + +- the focused suite PASSes; +- `http-server.ts` is nonzero; +- health remains RED only for the not-yet-expanded selected boundaries and any + aggregate dip caused by adding `effects.ts`. + +- [ ] **Step 5: Commit only the HTTP slice** + +Run: + +```bash +git add \ + apps/electron-backend/src/app/server/http-server.ts \ + apps/electron-backend/src/app/server/http-server.spec.ts +git commit -m "test(electron): cover remote HTTP server" +``` + +Expected: the provisional policy remains unstaged. + +### Task 5: Cover Remote-Control HTTP, IPC, And Renderer Dispatch + +**Files:** + +- Create: + `apps/electron-backend/src/app/events/remote-control.events.spec.ts` +- Modify: + `apps/electron-backend/src/app/events/remote-control.events.ts` + +- [ ] **Step 1: Write the isolated remote-control suite** + +Mock only: + +- `BrowserWindow.getAllWindows`; +- `ipcMain.handle` and `ipcMain.on`; +- `httpServer.registerRemoteControlHandler` and `httpServer.start`; and +- `store.get`. + +Import a named `RemoteControlEvents` class and instantiate it in each test. +Capture registered handlers in maps. Use a `PassThrough` request for JSON body +tests and a response recorder with typed `writeHead`/`end` methods. + +Use this complete exchange helper: + +```typescript +interface ResponseSnapshot { + body: string; + headers: Record; + statusCode: number; +} + +function createExchange(method: string) { + const requestStream = new PassThrough(); + Object.defineProperty(requestStream, 'method', { value: method }); + + let resolveResponse!: (snapshot: ResponseSnapshot) => void; + const snapshot: ResponseSnapshot = { + body: '', + headers: {}, + statusCode: 0, + }; + const completed = new Promise((resolve) => { + resolveResponse = resolve; + }); + const response = { + writeHead(statusCode: number, headers: Record) { + snapshot.statusCode = statusCode; + snapshot.headers = headers; + return response; + }, + end(body?: string) { + snapshot.body = body ?? ''; + resolveResponse(snapshot); + return response; + }, + }; + + return { + completed, + request: requestStream as unknown as http.IncomingMessage, + requestStream, + response: response as unknown as http.ServerResponse, + }; +} + +function getHttpHandler(path: string) { + const handler = mockRegisteredHttpHandlers.get(path); + if (!handler) { + throw new Error(`Expected remote-control HTTP handler for ${path}`); + } + return handler; +} +``` + +Add exact contract cases: + +```typescript +it.each([ + ['/api/remote-control/channel/up', 'CHANNEL_CHANGE', { direction: 'up' }], + [ + '/api/remote-control/channel/down', + 'CHANNEL_CHANGE', + { direction: 'down' }, + ], + [ + '/api/remote-control/volume/up', + 'REMOTE_CONTROL_COMMAND', + { type: 'volume-up' }, + ], + [ + '/api/remote-control/volume/down', + 'REMOTE_CONTROL_COMMAND', + { type: 'volume-down' }, + ], + [ + '/api/remote-control/volume/toggle-mute', + 'REMOTE_CONTROL_COMMAND', + { type: 'volume-toggle-mute' }, + ], +] as const)( + 'dispatches POST %s to the first renderer', + async (path, channel, payload) => { + const exchange = createExchange('POST'); + + getHttpHandler(path)(exchange.request, exchange.response); + exchange.requestStream.end(); + const response = await exchange.completed; + + expect(response.statusCode).toBe(200); + expect(response.body).toBe(JSON.stringify({ success: true })); + expect(mockWebContentsSend).toHaveBeenCalledWith(channel, payload); + } +); +``` + +Also test: + +- all seven endpoint paths are registered; +- stored enabled/port starts HTTP once; disabled does not; +- GET on POST-only handlers and POST on status return 405 without dispatch; +- select-number accepts `7.9` and sends integer `7`; +- missing, zero, negative, `Infinity`, and nonnumeric values return 400; +- malformed JSON returns 400 and sends nothing; +- a 10,241-byte body returns 413, destroys the request, and sends nothing; +- partial status IPC updates merge with previous state and refresh + `updatedAt`; +- GET status returns that merged state; and +- no BrowserWindow logs a warning but does not throw. + +- [ ] **Step 2: Run the focused spec to verify RED** + +Run: + +```bash +pnpm nx test electron-backend \ + --testPathPattern=remote-control.events.spec.ts \ + --runInBand +``` + +Expected: FAIL because `RemoteControlEvents` is not exported as a named class. + +- [ ] **Step 3: Export the existing class without changing its singleton** + +Change: + +```typescript +class RemoteControlEvents { +``` + +to: + +```typescript +export class RemoteControlEvents { +``` + +Keep: + +```typescript +export default new RemoteControlEvents(); +``` + +Do not change endpoint names, methods, payloads, body limit, stored defaults, +or renderer channels unless a correct regression assertion exposes an actual +defect. If that happens, invoke `systematic-debugging`, keep the failing test, +and document the separately justified behavior fix. + +- [ ] **Step 4: Verify remote-control behavior and coverage** + +Run: + +```bash +pnpm nx test electron-backend \ + --testPathPattern=remote-control.events.spec.ts \ + --runInBand +pnpm run coverage:unit:ci -- --projects=electron-backend +pnpm run coverage:merge +node tools/coverage/coverage-health.mjs --require-report +``` + +Expected: the focused suite PASSes, `remote-control.events.ts` is nonzero, and +health no longer reports either formerly 0% selected file. + +- [ ] **Step 5: Commit only the remote-control slice** + +Run: + +```bash +git add \ + apps/electron-backend/src/app/events/remote-control.events.ts \ + apps/electron-backend/src/app/events/remote-control.events.spec.ts +git commit -m "test(electron): cover remote control events" +``` + +Expected: the provisional policy remains unstaged. + +### Task 6: Expand Settings Runtime Reconciliation Coverage + +**Files:** + +- Modify: + `apps/electron-backend/src/app/events/settings.events.spec.ts` +- Verify only: + `apps/electron-backend/src/app/events/settings.events.ts` + +- [ ] **Step 1: Make the existing test harness deterministic** + +Keep real `normalizeExternalPlayerArguments`. Add stable mocks: + +```typescript +type SettingsUpdateHandler = ( + event: unknown, + settings: Record +) => void; + +const mockStoreGet = jest.fn(); +const mockStoreSet = jest.fn(); +const mockUpdateSettings = jest.fn(); +let handler: SettingsUpdateHandler; +``` + +Include every imported key in the store mock: + +```typescript +EMBEDDED_MPV_FRAME_COPY: 'embeddedMpvFrameCopy', +MPV_PLAYER_ARGUMENTS: 'mpvPlayerArguments', +MPV_REUSE_INSTANCE: 'mpvReuseInstance', +VLC_PLAYER_ARGUMENTS: 'vlcPlayerArguments', +VLC_REUSE_INSTANCE: 'vlcReuseInstance', +store: { + get: mockStoreGet, + set: mockStoreSet, +}, +``` + +In `beforeEach`, clear the handler map, reset modules and mocks, silence +`console.log`, import `./settings.events`, and retrieve the registered +`SETTINGS_UPDATE` handler. Preserve both existing credential-redaction tests. + +- [ ] **Step 2: Confirm the provisional settings ratchet is RED before new cases** + +Run: + +```bash +node tools/coverage/coverage-health.mjs --require-report +``` + +Expected: settings reports fewer than the provisional 17 covered statements or +less than its retained 59.25% floor. + +- [ ] **Step 3: Add normalization and defined-only persistence assertions** + +Add: + +```typescript +it('normalizes player arguments and preserves explicit false reuse flags', () => { + handler( + {}, + { + mpvPlayerArguments: [' --screen=1 ', '', ' --hwdec=auto-safe '], + mpvReuseInstance: false, + vlcPlayerArguments: ' --fullscreen \n\n --no-video-title-show ', + vlcReuseInstance: false, + } + ); + + expect(mockStoreSet).toHaveBeenCalledWith( + 'mpvPlayerArguments', + '--screen=1\n--hwdec=auto-safe' + ); + expect(mockStoreSet).toHaveBeenCalledWith('mpvReuseInstance', false); + expect(mockStoreSet).toHaveBeenCalledWith( + 'vlcPlayerArguments', + '--fullscreen\n--no-video-title-show' + ); + expect(mockStoreSet).toHaveBeenCalledWith('vlcReuseInstance', false); +}); + +it('does not persist undefined settings', () => { + handler({}, {}); + + expect(mockStoreSet).not.toHaveBeenCalled(); + expect(mockUpdateSettings).not.toHaveBeenCalled(); +}); + +it('coerces the frame-copy startup preference to boolean', () => { + handler({}, { embeddedMpvFrameCopy: 1 }); + + expect(mockStoreSet).toHaveBeenCalledWith('embeddedMpvFrameCopy', true); +}); +``` + +- [ ] **Step 4: Add partial remote-control reconciliation assertions** + +Add two tests: + +```typescript +it('uses the stored port when only remote-control enabled changes', () => { + mockStoreGet.mockImplementation((key: string) => + key === 'remoteControlPort' ? 9988 : undefined + ); + + handler({}, { remoteControl: true }); + + expect(mockStoreSet).toHaveBeenCalledWith('remoteControl', true); + expect(mockStoreSet).not.toHaveBeenCalledWith( + 'remoteControlPort', + expect.anything() + ); + expect(mockUpdateSettings).toHaveBeenCalledWith(true, 9988); +}); + +it('uses stored enabled state when only the remote-control port changes', () => { + mockStoreGet.mockImplementation((key: string) => + key === 'remoteControl' ? true : undefined + ); + + handler({}, { remoteControlPort: 9123 }); + + expect(mockStoreSet).toHaveBeenCalledWith('remoteControlPort', 9123); + expect(mockStoreSet).not.toHaveBeenCalledWith( + 'remoteControl', + expect.anything() + ); + expect(mockUpdateSettings).toHaveBeenCalledWith(true, 9123); +}); +``` + +- [ ] **Step 5: Run settings tests and refresh Electron coverage** + +Run: + +```bash +pnpm nx test electron-backend \ + --testPathPattern=settings.events.spec.ts \ + --runInBand +pnpm run coverage:unit:ci -- --projects=electron-backend +pnpm run coverage:merge +node tools/coverage/coverage-health.mjs --require-report +``` + +Expected: all settings tests PASS; settings covered statements and percentage +are above the fresh master baseline. These are characterization tests for +unchanged behavior, so no production edit is expected. + +- [ ] **Step 6: Commit only the settings spec** + +Run: + +```bash +git add apps/electron-backend/src/app/events/settings.events.spec.ts +git commit -m "test(electron): cover settings runtime updates" +``` + +Expected: the provisional policy remains unstaged. + +### Task 7: Expand Download Managed-Path Security Coverage + +**Files:** + +- Modify: + `apps/electron-backend/src/app/events/database/downloads.events.spec.ts` +- Verify only: + `apps/electron-backend/src/app/events/database/downloads.events.ts` + +- [ ] **Step 1: Add filesystem and shell spies to the existing harness** + +Add: + +```typescript +const mockExistsSync = jest.fn(); +const mockOpenPath = jest.fn(); +const mockShowItemInFolder = jest.fn(); +``` + +Return the two shell spies from the existing Electron mock. Reset them in +`beforeEach`; make `mockOpenPath` resolve an empty string by default. +Before importing `downloads.events`, register: + +```typescript +jest.doMock('node:fs', () => ({ + existsSync: mockExistsSync, +})); +``` + +Add a database helper whose `limit(1)` returns either `[{ id: 42 }]`, `[]`, or +rejects: + +```typescript +function mockManagedPath(result: 'managed' | 'unmanaged' | 'error') { + const limit = + result === 'error' + ? jest.fn().mockRejectedValue(new Error('database unavailable')) + : jest + .fn() + .mockResolvedValue(result === 'managed' ? [{ id: 42 }] : []); + mockGetDatabase.mockResolvedValue({ + select: jest.fn(() => ({ + from: jest.fn(() => ({ + where: jest.fn(() => ({ limit })), + })), + })), + }); +} +``` + +- [ ] **Step 2: Confirm the provisional downloads ratchet is RED** + +Run: + +```bash +node tools/coverage/coverage-health.mjs --require-report +``` + +Expected: downloads is below the provisional 70-covered-statement target or +its retained 46.93% floor. + +- [ ] **Step 3: Add the managed-path rejection cases** + +For both `DOWNLOADS_REVEAL_FILE` and `DOWNLOADS_PLAY_FILE`, assert: + +```typescript +it.each(['DOWNLOADS_REVEAL_FILE', 'DOWNLOADS_PLAY_FILE'])( + '%s rejects a path not owned by a download row', + async (channel) => { + mockManagedPath('unmanaged'); + mockExistsSync.mockReturnValue(true); + + await expect( + getHandler(channel)(null, '/tmp/unmanaged.mp4') + ).resolves.toEqual({ + error: 'File not found', + success: false, + }); + + expect(mockShowItemInFolder).not.toHaveBeenCalled(); + expect(mockOpenPath).not.toHaveBeenCalled(); + } +); + +it.each(['DOWNLOADS_REVEAL_FILE', 'DOWNLOADS_PLAY_FILE'])( + '%s rejects a managed path missing on disk', + async (channel) => { + mockManagedPath('managed'); + mockExistsSync.mockReturnValue(false); + + await expect( + getHandler(channel)(null, '/downloads/missing.mp4') + ).resolves.toEqual({ + error: 'File not found', + success: false, + }); + + expect(mockShowItemInFolder).not.toHaveBeenCalled(); + expect(mockOpenPath).not.toHaveBeenCalled(); + } +); +``` + +- [ ] **Step 4: Add the authorized shell and database-failure cases** + +Add one reveal success, one play success, and a parameterized database failure: + +```typescript +it('reveals only a managed file that exists', async () => { + mockManagedPath('managed'); + mockExistsSync.mockReturnValue(true); + + await expect( + getHandler('DOWNLOADS_REVEAL_FILE')(null, '/downloads/movie.mp4') + ).resolves.toEqual({ success: true }); + + expect(mockShowItemInFolder).toHaveBeenCalledWith('/downloads/movie.mp4'); +}); + +it('opens only a managed file that exists', async () => { + mockManagedPath('managed'); + mockExistsSync.mockReturnValue(true); + + await expect( + getHandler('DOWNLOADS_PLAY_FILE')(null, '/downloads/movie.mp4') + ).resolves.toEqual({ success: true }); + + expect(mockOpenPath).toHaveBeenCalledWith('/downloads/movie.mp4'); +}); +``` + +For the database-error cases, silence `console.error`, use +`mockManagedPath('error')`, expect the existing structured not-found response, +and assert neither shell spy ran. + +- [ ] **Step 5: Run download tests and refresh Electron coverage** + +Run: + +```bash +pnpm nx test electron-backend \ + --testPathPattern=downloads.events.spec.ts \ + --runInBand +pnpm run coverage:unit:ci -- --projects=electron-backend +pnpm run coverage:merge +node tools/coverage/coverage-health.mjs --require-report +``` + +Expected: all existing destructive cleanup/pause/resume tests and all new +managed-path tests PASS. Downloads covered statements and percentage exceed +the fresh master values. + +- [ ] **Step 6: Commit only the downloads spec** + +Run: + +```bash +git add \ + apps/electron-backend/src/app/events/database/downloads.events.spec.ts +git commit -m "test(electron): cover managed download paths" +``` + +Expected: the provisional policy remains the only unstaged source change. + +### Task 8: Record Achieved Ratchets And Document The Integrity Contract + +**Files:** + +- Modify: + `tools/coverage/coverage-policy.json` +- Modify: + `docs/architecture/validation-map.md` +- Modify: + `docs/superpowers/specs/2026-07-25-coverage-integrity-runtime-boundaries-design.md` + +- [ ] **Step 1: Generate a complete fresh candidate report** + +Run: + +```bash +NX_SKIP_NX_CACHE=true pnpm run coverage:ci +``` + +Expected: 30 Tier A reports, no `Failed to collect coverage`, no missing +runtime-owning files, and all four aggregate metrics at or above the fresh +master baseline. If any metric is lower, do not lower the baseline. Inspect +the approved boundary cases for a missing meaningful assertion, add that +failing assertion, and rerun the directly affected suite before repeating this +command. + +- [ ] **Step 2: Print exact achieved aggregate and per-file values** + +Run: + +```bash +node --input-type=module <<'NODE' +import { createRequire } from 'node:module'; +import fs from 'node:fs'; +import path from 'node:path'; + +const require = createRequire(import.meta.url); +const { createCoverageMap } = require('istanbul-lib-coverage'); +const data = JSON.parse( + fs.readFileSync('coverage/merged/coverage-final.json', 'utf8') +); +const map = createCoverageMap(data); +const targets = [ + 'apps/electron-backend/src/app/server/http-server.ts', + 'apps/electron-backend/src/app/events/remote-control.events.ts', + 'apps/electron-backend/src/app/events/settings.events.ts', + 'apps/electron-backend/src/app/events/database/downloads.events.ts', +]; +const criticalFiles = targets.map((file) => { + const statements = map + .fileCoverageFor(path.resolve(file)) + .toSummary() + .toJSON().statements; + return { + path: file, + statements: { + minimumCovered: statements.covered, + minimumPercent: statements.pct, + }, + }; +}); +const summary = map.getCoverageSummary().toJSON(); +console.log( + JSON.stringify( + { + merged: { + statements: summary.statements.pct, + branches: summary.branches.pct, + functions: summary.functions.pct, + lines: summary.lines.pct, + }, + criticalFiles, + }, + null, + 4 + ) +); +NODE +``` + +Expected: a complete `coverageRatchet` value with nonzero HTTP/remote values +and settings/download values no lower than their baseline. Copy this exact +JSON object over the provisional `reporting.coverageRatchet`; do not round +manually or retain the provisional one-statement probes. + +- [ ] **Step 3: Verify the achieved ratchet passes and rejects regressions** + +Run: + +```bash +node tools/coverage/coverage-health.mjs --require-report +node --test tools/coverage/coverage-integrity.test.mjs +``` + +Expected: both exit 0. The synthetic Node tests still prove below-ratchet +metrics, missing critical files, missing reports, and collection markers fail. + +- [ ] **Step 4: Update the canonical validation map** + +Under `## Coverage Tiers` in `docs/architecture/validation-map.md`, add: + +```markdown +Tier A coverage is fail-closed. `coverage:unit:ci` relays Jest output but exits +nonzero on `Failed to collect coverage`, a missing/invalid project report, or +a runtime-owning production TypeScript file absent from that report. +`coverage:merge` requires every configured Tier A report before replacing the +merged output, and `coverage:health --require-report` repeats completeness plus +aggregate and critical-file ratchets. + +Runtime-owning files are discovered from the TypeScript AST. Specs, declarations, +test setup/stubs, generated/environment files, `index.ts`, type-only files, and +pure re-export shims do not count as executable coverage inputs. + +Ratchets live under `reporting.coverageRatchet` in +`tools/coverage/coverage-policy.json`. Update them only from a fresh full +`coverage:ci` report when every value stays level or rises; never lower a +ratchet to accept a regression. +``` + +Also include `pnpm run coverage:tools:test` in the local coverage command block. + +- [ ] **Step 5: Confirm documentation and release-note decisions** + +Run: + +```bash +pnpm exec prettier --check \ + docs/architecture/validation-map.md \ + docs/superpowers/specs/2026-07-25-coverage-integrity-runtime-boundaries-design.md +pnpm run release:notes:validate +``` + +Expected: both pass. Do not add `.changes/*.md`: this PR changes tests, CI +integrity, and behavior-preserving testability seams, not user-visible +behavior. Because it touches runtime `.ts`, apply `no-release-note` to the PR +in Task 10. + +- [ ] **Step 6: Commit ratchets and docs** + +Run: + +```bash +git add \ + tools/coverage/coverage-policy.json \ + docs/architecture/validation-map.md \ + docs/superpowers/specs/2026-07-25-coverage-integrity-runtime-boundaries-design.md +git commit -m "ci(coverage): ratchet runtime boundaries" +``` + +Expected: the provisional policy is replaced by achieved values and the +worktree becomes clean. + +### Task 9: Run Fresh Validation And Audit The Final Diff + +**Files:** + +- Verify only: all changed files +- Verify only: + `apps/electron-backend-e2e/src/downloads.e2e.ts` +- Verify only: + `apps/electron-backend-e2e/src/remote-control.e2e.ts` +- Verify only: + `apps/electron-backend-e2e/src/settings.e2e.ts` + +- [ ] **Step 1: Run targeted unit, lint, and type checks** + +Run: + +```bash +pnpm run coverage:tools:test +pnpm nx test electron-backend --skip-nx-cache --runInBand +pnpm nx test m3u-state --skip-nx-cache --runInBand +pnpm nx lint electron-backend --skip-nx-cache +pnpm nx lint m3u-state --skip-nx-cache +pnpm run typecheck:backend +``` + +Expected: every command exits 0; no Jest suite, lint rule, or backend type +check fails. + +- [ ] **Step 2: Run the three existing Electron E2E contracts** + +Run: + +```bash +pnpm nx run electron-backend-e2e:e2e-ci--src/downloads.e2e.ts --skip-nx-cache +pnpm nx run electron-backend-e2e:e2e-ci--src/remote-control.e2e.ts --skip-nx-cache +pnpm nx run electron-backend-e2e:e2e-ci--src/settings.e2e.ts --skip-nx-cache +``` + +Expected: all targets PASS. These prove the real packaged-style Electron IPC, +native folder/download flow, remote loopback server, renderer command, and +settings persistence contracts beyond unit mocks. + +- [ ] **Step 3: Build the production Electron target** + +Run: + +```bash +pnpm nx build electron-backend \ + --configuration=production \ + --skip-nx-cache +``` + +Expected: exit 0, including worker, web, and remote-control-web dependencies. + +- [ ] **Step 4: Run final full coverage from scratch** + +Run: + +```bash +NX_SKIP_NX_CACHE=true pnpm run coverage:ci +``` + +Expected: + +- all coverage-tool tests PASS; +- policy sees 30 Tier A projects; +- all 30 project reports are merged; +- `effects.ts` is present; +- no collection marker or runtime-owning source omission is reported; +- achieved aggregate and critical-file ratchets PASS. + +- [ ] **Step 5: Inspect exact metrics and scoped diff** + +Run: + +```bash +git diff origin/master...HEAD --check +git diff --stat origin/master...HEAD +git diff --name-only origin/master...HEAD +git status --short --branch +``` + +Expected: + +- no whitespace errors; +- only the files in this plan appear; +- no database worker, dashboard, Stalker search, type-only interface, lockfile, + AGENTS, or CLAUDE changes appear; +- the worktree is clean. + +- [ ] **Step 6: Apply verification-before-completion** + +Read and follow `verification-before-completion`. Record, without paraphrasing +away failures: + +- exact unit suite/test counts; +- exact E2E results; +- lint/typecheck/build exit results; +- 30/30 Tier A report integrity; +- aggregate before/after; +- all selected-file before/after values; +- docs and release-note decisions. + +Do not claim completion from cached or earlier output. + +### Task 10: Recheck Master, Push, And Open The Draft PR + +**Files:** + +- Verify only: current branch history and GitHub PR metadata + +- [ ] **Step 1: Recheck master before publication** + +Run: + +```bash +git fetch origin master --prune +git log --oneline HEAD..origin/master +``` + +Expected: no output. If master advanced, rebase, rerun dependency bootstrap and +all of Task 9, then refresh ratchets only upward before publication. + +- [ ] **Step 2: Review conventional history** + +Run: + +```bash +git log --oneline origin/master..HEAD +git status --short --branch +``` + +Expected: concise conventional commits, including the design and plan docs, +with a clean worktree. + +- [ ] **Step 3: Push the agent branch** + +Run: + +```bash +git push -u origin agent/coverage-integrity-runtime-boundaries +``` + +Expected: push succeeds and sets the upstream. + +- [ ] **Step 4: Create a draft PR** + +Read and follow `github-pr`. Create a draft targeting `master` with title: + +```text +test(electron): harden runtime coverage integrity +``` + +The body must contain: + +- fresh baseline commit and aggregate metrics; +- exact after metrics; +- selected-file before/after table; +- behavioral contracts for HTTP, remote control, settings, and downloads; +- the two behavior-preserving production seams; +- the `effects.ts` compiler/instrumentation correction; +- runner, merge, health, and ratchet behavior; +- exact validation commands/results; +- docs update; +- no release note because behavior is unchanged; and +- deferred `database.worker.ts`, dashboard rails, and Stalker search. + +Use: + +```bash +gh pr create \ + --draft \ + --base master \ + --head agent/coverage-integrity-runtime-boundaries \ + --title "test(electron): harden runtime coverage integrity" \ + --body-file - +``` + +Provide the fully populated body on standard input; do not leave metric or +validation placeholders. + +- [ ] **Step 5: Apply the internal-only release-note label** + +Run: + +```bash +gh pr edit --add-label no-release-note +gh pr view --json url,isDraft,headRefName,baseRefName,labels,commits,statusCheckRollup +``` + +Expected: the PR is draft, head/base are correct, `no-release-note` is present, +and the command prints the PR URL. + +- [ ] **Step 6: Return the exact handoff** + +Return: + +- draft PR URL; +- branch; +- final commit SHA; +- aggregate and selected-file before/after metrics; +- exact command results and any explicit environment-only skip; +- docs updated (`docs/architecture/validation-map.md`); +- release note skipped with `no-release-note`; +- deferred zero-coverage follow-ups. diff --git a/docs/superpowers/specs/2026-07-25-coverage-integrity-runtime-boundaries-design.md b/docs/superpowers/specs/2026-07-25-coverage-integrity-runtime-boundaries-design.md index 2d0de8fe5..1357b2049 100644 --- a/docs/superpowers/specs/2026-07-25-coverage-integrity-runtime-boundaries-design.md +++ b/docs/superpowers/specs/2026-07-25-coverage-integrity-runtime-boundaries-design.md @@ -45,8 +45,8 @@ The audit file status on the same baseline is: | `apps/electron-backend/src/app/workers/database.worker.ts` | 0 / 205 (0%) | | `apps/electron-backend/src/app/events/remote-control.events.ts` | 0 / 115 (0%) | | `apps/electron-backend/src/app/server/http-server.ts` | 0 / 73 (0%) | -| `apps/electron-backend/src/app/events/database/downloads.events.ts` | 69 / 147 (46.94%) | -| `apps/electron-backend/src/app/events/settings.events.ts` | 16 / 27 (59.26%) | +| `apps/electron-backend/src/app/events/database/downloads.events.ts` | 69 / 147 (46.93%) | +| `apps/electron-backend/src/app/events/settings.events.ts` | 16 / 27 (59.25%) | | `libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.ts` | 0 / 206 (0%) | | `libs/portal/stalker/feature/src/lib/stalker-search/stalker-search.component.ts` | 0 / 166 (0%) | | `libs/m3u-state/src/lib/effects.ts` | absent |