DimitriGilbert / DimitriGilbert/fuck-chat-control

Test & quality alignment plan

Open
#8 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
0
Forks
0
PR merge metrics
No merged PRs in 30d

Description

# Test & Quality Alignment Plan — fck-chat-control

_Revised 2026-09-05: CI removed by owner decision — quality gates are local commands run by the owner/agents; no GitHub Actions._

- **Project**: `fuck-eu-chat-control` (E2E-encrypted P2P chat; the fleet's crypto-critical repo)
- **Date**: 2026-09-05
- **Generated by the fleet test-alignment initiative** (audit + synthesis of 28 repos)
- **Audit report**: [`../.test-alignment-audit/reports/fck-chat-control.md`](../.test-alignment-audit/reports/fck-chat-control.md)
- **Fleet synthesis**: [`../.test-alignment-audit/synthesis-decisions.md`](../.test-alignment-audit/synthesis-decisions.md)
- **Wave**: 2 (strong suite, missing plumbing — mechanical DoD work)

**Posture**: this repo already has an outstanding suite (131 files, ~1,076 cases, NIST KAT crypto, race/glare regression culture, three-tier split). The plan preserves all of it untouched in behavior and adds the rails around it: local gate commands, coverage, dependency hygiene, quality metrics, and a scoped mutation tier. Nothing here rewrites tests or changes what they assert.

---

## 1. Adopted decisions (synthesis D1–D7, adapted to this stack)

| Decision | Applies? | How it lands here |
|---|---|---|
| **D1 — runner & layout** | Yes | Vitest 4 already in use (on the fleet-standard v4 line) — stays. Three-tier layout (`tests/unit`, `tests/integration`, `tests/e2e`) is exemplary and unchanged. `jest-expo` (mobile) and `cargo test` (desktop) stay per the synthesis delta. No mass renames; new tests colocate. Root headless entry already exists (`pnpm test:unit`); the gate chain uses the unit tier, not `pnpm test` (which drags in Playwright). |
| **D2 — coverage** | Yes — **the P1 gap** | Provider `@vitest/coverage-v8` (must match vitest ^4.1.x). State-2 policy: measure once, set `lines` threshold at measured−5, commit the baseline into AGENTS.md, ratchet +5 when green, never lower. Scope: `packages/chat-runtime` (crypto core, mandatory) and `apps/web` unit tier (same phase, parallel). |
| **D3 — lint & format** | Yes, **with the vp exception** | **Explicit exception per D3: this repo keeps Vite+ (`vp lint` / `vp fmt` via `vite-plus` 0.2.2). No ESLint/Prettier migration.** Adaptation: current config sets `typeAware: false, typeCheck: false` (`vite.config.ts`) — investigate enabling type-aware linting report-only as a follow-up ratchet, not a blocker. `vp lint` gets enforced in the local gate (Phase 1). No turbo exists here (tasks run via `vp run -r`), so the fleet's "dead turbo `lint` task" item is N/A. |
| **D4 — quality metrics** | Yes | `jscpd` + `knip` report-only (Phase 4). **Stryker mutation testing, scoped to `packages/chat-runtime/src/crypto/**`** — this is one of the five fleet-nominated cores. Report-only, on demand only, never in the default gate (Phase 7). CRAP/complexity deferred until coverage exists (Phase 2 first); note the crap-score-vs-v8-json caveat in Risks. The mirrored `_helpers.ts` duplication web↔chat-runtime is real duplication → extract into `packages/test-utils` (Phase 5), per the synthesis delta. In-repo package only — the fakes themselves stay domain-specific (audit §7), they are not a fleet-level centralization candidate. |
| **D5 — no CI by design (owner decision 2026-09-05)** | Yes | **No GitHub Actions, no org-repo caller, no local workflow fallback.** The earlier "thin caller to `DimitriGilbert/.github` `ts-ci.yml` (with a repo-local fallback workflow)" plan is dropped entirely. Enforcement = the root-level headless verification entrypoint (`pnpm verify`: lint + format + workspace types → unit tests → mobile jest, composed in Phase 1) run by the owner and by agents; the plan's phase validation gates are run by implementer/validator agents before any phase is declared done. Heavy tiers (integration, Playwright e2e, `cargo test`) stay on-demand local commands (Phase 6). |
| **D6 — shared config (`@dg/config`)** | Deferred | No lint/tsconfig/vitest rewrite is happening here (vp stays; vitest configs already deduplicate via the self-link alias convention). Adoption is "when a repo touches config anyway" — the vitest preset factory becomes relevant only if the alias-boilerplate (`~40 lines` repeated in 4 configs) gets refactored; optional P3, not scheduled. |
| **D7 — hooks** | Yes, adapted | The local gate commands run by the owner/agents are the only authoritative gate. This repo already has a committed `vp staged` hook config (`vite.config.ts`) with **no hook installed** — synthesis delta: "either install the Vite+ staged hook or delete the config." Decision: keep vp-native hooks (working alternative stays, same spirit as D3), run `pnpm hooks:setup` once to install, and document in AGENTS.md that hooks are opt-in local convenience, never load-bearing. vp-native instead of lefthook is the deliberate adaptation (D7 names lefthook for repos that want fast feedback; this repo already has a native mechanism). |

---

## 2. Definition of Done — current status per item

Fleet DoD (synthesis §3) with this repo's status as of 2026-09-05:

| # | Item | Status | Where fixed |
|---|---|---|---|
| 1 | Root-level headless test command, documented in AGENTS.md | **Partial** — `pnpm test:unit` exists and AGENTS.md documents test commands; needs coverage/tier updates as phases land | Phase 0, 8 |
| 2 | Headless verification entrypoint running typecheck + test + lint in one local command; agent records green before a phase is done | **Missing** — no root aggregate command; the ~1,076-case suite runs only when someone remembers to | Phase 1 |
| 3 | Coverage measured + reported; baseline recorded; thresholds per D2; never lowered | **Missing** — zero coverage tooling anywhere | Phase 2 |
| 4 | Language-appropriate lint, zero errors, wired into the headless entrypoint | **Partial** — `vp lint` works (D3 exception), not type-aware, not wired into any aggregate gate | Phase 1 (+ type-aware follow-up) |
| 5 | AGENTS.md "Testing" section matches reality | **Partial** — commands are accurate; no testing-section map of tiers/coverage; no hooks-are-convenience statement | Phase 0, 8 |
| 6 | Dead scaffold test deps removed or used | **Satisfied** — `jsdom` + `@testing-library/*` in `apps/web` are used by real `tests/unit/ui/*` and `tests/unit/components/*` tests (per-file jsdom environments); mobile's `react-test-renderer` is used by jest-expo suite | — (verified 2026-09-05) |
| 7 | Renovate one-liner; no `"latest"` deps | **Missing renovate**; **no `"latest"` pins found** (all caret/catalog-pinned). Special care: patched `nitro@3.0.260610-beta` | Phase 3 |
| 8 | Quality-report script (knip + jscpd, + CRAP where coverage exists) runnable locally, report-only | **Missing** | Phase 4, 7 |
| 9 | No fake passes | **Partial** — one *honest* `.skip` with a written reason (`apps/web/tests/e2e/settings.spec.ts:139`, no controllable broker; acceptable, track it). One violation: `apps/web` `test:integration` runs `--passWithNoTests` over an **empty** tier (`apps/web/tests/` has no `integration/` dir) | Phase 6 |

---

## 3. Execution model (per `subagent-orchestration` skill)

The orchestrator dispatches subagents; it never writes code itself.

- **Per phase**: 1 **implementer** (gets the complete phase requirements + NO-SLOP policy; runs gatekeeping commands itself before reporting done) → 1 **validator** (a different agent; must actually read the created/modified files line-by-line, verify every requirement, enforce NO-SLOP, and run the phase's validation gate) → fix loop (one **fixer** fixing all validator findings at once, then re-validate) up to 3 attempts, then halt and report.
- **Phases marked parallel** have per-sub-phase implementer+validator pairs, then a mandatory phase-wide validator checking integration/coherence.
- **NO-SLOP policy (pasted verbatim into every implementer/fixer dispatch, enforced by every validator)**: no `any`/`as any`/`: any`; no placeholder code, no `TODO`/`FIXME`; no unused imports/variables; no console.log hacks; `import type` for type-only imports (`verbatimModuleSyntax` is on); external imports first, blank line, then local; production quality only.
- **Repo conventions every dispatch must repeat**: pnpm only (never another package manager/lockfile); commands from repo root; `catalog:` references when a dep exists in `pnpm-workspace.yaml` catalog; check `pnpm run check-types` before reporting done; never touch `patches/nitro@3.0.260610-beta.patch`; no commits/pushes unless separately asked (the orchestrator or user handles commits between phases so each phase lands as a reviewable unit).

Effort legend: **S** ≤ half day · **M** ~1 day · **L** ≥ 2 days (of implementer+validator time).

---

## Phase 0 — Guardrails: hook wired, gate documented (effort S)

**Goal**: resolve the "hook configured but never installed" P2 and state the quality-gate policy in AGENTS.md before the gate lands.

**Role**: implementer → validator (sequential).

**Tasks**:
1. Install the existing Vite+ staged hook: run `pnpm hooks:setup` (the repo's own documented command, root `package.json`). Verify a hook now exists in `.git/hooks/` and that `pnpm staged` (→ `vp staged`) runs cleanly on a sample staged file (stage nothing permanently; use `git status` after to confirm a clean tree).
2. If `vp hooks:setup` does not produce a working pre-commit hook (vite-plus 0.2.2 is early), **delete** the `staged` block from `vite.config.ts` and the `staged`/`hooks:setup` scripts from root `package.json` instead — per the synthesis delta, a dead hook config is worse than none. Record which path was taken in AGENTS.md.
3. Update `/home/didi/workspace/fck-chat-control/AGENTS.md`: add a short **Testing** section stating (a) the three tiers (unit / integration / Playwright e2e) and their commands (`pnpm test:unit`, per-package `test:integration`, `pnpm test:e2e`), (b) mobile/desktop tiers (`pnpm test:mobile`, `pnpm test:desktop`), (c) "the local headless gate commands run by the owner/agents are the quality gate; the `vp staged` hook is optional local convenience" (D7 language), (d) placeholder for the coverage baseline (filled in Phase 2). Keep it consistent with existing AGENTS.md style; no phantom commands.

**Files**: `AGENTS.md`, `vite.config.ts` (only if deleting staged block), root `package.json` (only if deleting scripts).

**Validation gate (validator runs all)**:
- `pnpm check` passes (lint + format + workspace types).
- `.git/hooks/pre-commit` exists (or the staged block is provably deleted — exactly one of the two outcomes).
- Validator reads AGENTS.md and confirms every documented command exists in some `package.json` and every tier matches reality.
- `git status` clean afterwards (no stray staged/modified files left by hook testing).

---

## Phase 1 — Local gate composition (effort M) — **the P1**

**Goal**: one headless root command runs typecheck + lint + unit tests + mobile jest; the ~1,076-case suite is gated by a single command any agent or the owner can run; green run recorded before the phase is declared done.

**Role**: implementer → validator. **Type**: sequential (script composition + possible small fixes).

**Tasks**:
1. Root `package.json`: add `"verify": "pnpm check && pnpm test:unit && pnpm test:mobile"` — the headless verification entrypoint (D5; `pnpm check` already chains lint + format + workspace types). Phases 2–7 extend the toolkit around it without lengthening the fast path.
2. Scope: **the gate = unit tier + types + lint + format + mobile jest**. `pnpm test:unit` (`vp run -r test:unit`) covers `packages/chat-runtime` + `apps/web` unit suites. Mobile jest (`pnpm test:mobile`) is cheap (~41 cases, node-env) and catches RN-shell regressions. Integration, Playwright e2e, and `cargo test` are on-demand tiers (Phase 6), per D5's "never in the default gate" rule for heavy tiers; the integration tier is excluded from `test:unit` by config already.
3. Fix whatever the first real run exposes (frozen-lockfile mismatches, browserless test assumptions — unit suites are node-env by design; web unit tests use per-file jsdom environments which need no browser download).
4. Do **not** add engines/nvmrc churn in this phase; AGENTS.md documents the expected Node 22 toolchain explicitly, which is sufficient.

**Files**: root `package.json` (the `verify` script; the only file likely needed beyond small fixes) + AGENTS.md gate note.

**Validation gate**:
- The implementer runs `pnpm verify` locally; it exits 0 with every sub-command resolving in a `package.json`.
- Fail-fast proven once: the validator introduces a failing assertion in a scratch copy and confirms `pnpm verify` exits non-zero at the test step (never on the real tree).
- The green output is recorded in the phase-execution report — gates are local commands, and the implementer/validator agents run the phase's validation gate before it is declared done (D5).

---

## Phase 2 — Coverage measurement + baseline thresholds (effort M)

**Goal**: coverage is measured on the crypto core and the web unit tier; baselines committed; ratchet policy active. Closes the second P1.

**Role**: **parallel sub-phases** — implementer A (`packages/chat-runtime`) + implementer B (`apps/web` unit tier), each with its own validator, then a **phase-wide validator** (AGENTS.md baseline coherence, threshold policy consistency, suite behavior unchanged).

**Tasks (sub-phase A — chat-runtime, mandatory)**:
1. Add to `pnpm-workspace.yaml` catalog: `"@vitest/coverage-v8": "^4.1.10"` (must track the vitest ^4.1 line exactly — v8 provider minor must match vitest minor).
2. `pnpm --filter @fuck-eu-chat-control/chat-runtime add -D "@vitest/coverage-v8@catalog:"`.
3. Extend `packages/chat-runtime/vitest.config.ts` with a `coverage` block: `provider: "v8"`, `reportsDirectory: "coverage"`, `reporter: ["text", "json", "html"]`, `include: ["src/**/*.ts"]`, `exclude`: wasm glue (`src/**/wasm/**` or wherever the SPAKE2 wasm loader lives — validator confirms generated/bindgen output is excluded), type-only barrels. **Do not** touch the `include`/`exclude` of the test globs or the alias block.
4. Add script to `packages/chat-runtime/package.json`: `"test:coverage": "vitest run --coverage"`.
5. Run it once; read the measured `lines` %; set `coverage.thresholds.lines` to **measured − 5** (floor at the measured value; never lower later). If measured ≥ 95, set thresholds to 95 and say so in AGENTS.md.
6. Update the AGENTS.md Testing-section coverage placeholder with: provider, command, measured baseline, threshold, ratchet rule (+5 when green, on any PR touching the package; never lower).

**Tasks (sub-phase B — web unit tier, same pattern)**:
1. `pnpm --filter web add -D "@vitest/coverage-v8@catalog:"`.
2. Same coverage block in `apps/web/vitest.config.ts` (`include: ["src/**/*.ts", "src/**/*.tsx"]`, exclude `src/routeTree.gen.ts` and any generated/paraglide-style output — `routeTree.gen.ts` exclusion is the fleet-baked rule).
3. `"test:coverage"` script; root aggregator `"test:coverage": "vp run -r test:coverage"` in root `package.json` (symmetry with `test:unit`).
4. Thresholds + AGENTS.md baseline as in sub-phase A.

**Crypto-don't-break constraint (both sub-phases)**: the coverage run must be behavior-identical to the plain run — same test globs, same environments, same alias resolution. If any crypto/race test fails **only under `--coverage`**, that is a phase failure (instrumentation perturbation), to be reported, not papered over with excludes of test files.

**Validation gate**:
- `pnpm --filter @fuck-eu-chat-control/chat-runtime test:coverage` and `pnpm --filter web test:coverage` both produce text+HTML reports and pass thresholds.
- `pnpm test:unit` (plain) still green — instrumentation off = behavior identical.
- `pnpm run check-types` green.
- Phase-wide validator: reads both configs + AGENTS.md; confirms thresholds equal measured−5, `routeTree.gen.ts` and wasm glue excluded, no test-file excludes, baseline numbers in AGENTS.md match the actual reports, and ratchet rule is stated.

---

## Phase 3 — Renovate + nitro-patch update policy (effort S)

**Goal**: dependency updates become automated and reviewed instead of absent; the patched nitro dep gets an explicit update protocol.

**Role**: implementer → validator (sequential).

**Tasks**:
1. Create `/home/didi/workspace/fck-chat-control/renovate.json`: `{"$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["local>DimitriGilbert"]}` (the D4/D6 org preset).
2. **If the org preset does not exist yet** (checked 2026-09-05): use a minimal self-contained config instead (pnpm manager, `rangeStrategy: "replace"` defaults, lockFileMaintenance on, `schedule: weekly`), with a comment to converge onto `local>DimitriGilbert` later. Note the deviation. (Renovate presets are a non-workflow artifact, unaffected by the no-CI decision.)
3. Add a `packageRules` entry for `nitro`: label it and mark it so its PRs are reviewed carefully — its update will invalidate `patches/nitro@3.0.260610-beta.patch` (Renovate will surface this as a failed artifact, which is the desired loud failure, but the PR must not be auto-merged).
4. Document in AGENTS.md (one sentence, near the existing "nitro dependency is patched" note): nitro updates require re-deriving the ws `maxPayload` patch (R3/F1: wire-level 32 KiB cap preventing pre-app-layer OOM) against the new version, and a `pnpm test:integration` run on `packages/chat-runtime` (the broker boots through the patched path) before merging.
5. Note for the owner (not automatable here): enable the Renovate GitHub App on the repo.

**Validation gate**:
- `renovate.json` parses as JSON and validates against the schema (`npx --yes renovate-config-validator` or equivalent — validator may skip the network check and do a JSON-parse + key review if offline).
- Validator reads the config: preset/local fallback present, nitro packageRule present, no `assignees`/`reviewers` noise, no automerge on nitro or on the workspace catalog.
- AGENTS.md sentence present and accurate.

---

## Phase 4 — jscpd + knip, report-only (effort S/M)

**Goal**: duplication and dead-export signals exist and are recorded; zero gates (D4: nothing gated on a number it has never seen).

**Role**: implementer → validator (sequential).

**Tasks**:
1. `pnpm add -Dw jscpd` and `pnpm add -Dw knip` (root workspace deps).
2. Create `/home/didi/workspace/fck-chat-control/jscpd.config.json`: fleet-canonical settings — `minLines: 8`, `minTokens: 80`, `reporters: ["json", "consoleFull"]`, `output: "./reports/jscpd"`, `absolute: true`, and `ignore` covering `**/node_modules/**`, `**/dist/**`, `**/coverage/**`, `**/target/**`, `apps/web/src/routeTree.gen.ts`, `packages/chat-runtime/wasm/**`, and the desktop `src-tauri/gen/**`. **Do not set `threshold`**; keep exit code 0 (report-only).
3. Expected finding, recorded up front: the mirrored `_helpers.ts` trees between `apps/web/tests/unit/{runtime,signaling,orchestrator,store}` and `packages/chat-runtime/tests/unit/` will dominate the report — that is the Phase 5 target, so Phase 4 only *measures* it.
4. Create `/home/didi/workspace/fck-chat-control/knip.json`: entry points per workspace (`apps/web` vite/Start entry, `packages/chat-runtime` `exports` map, `packages/ui`, `packages/env`), `ignoreExportsUsedInFile: true`. **Known noise source**: `packages/chat-runtime` wildcard `exports` (`"./*": "./src/*.ts"`) makes every src file an entry — configure knip's `entry` patterns to match that reality and accept a noisy-but-real first report; record the baseline counts in AGENTS.md.
5. Add root scripts: `"quality:duplicates": "jscpd"`, `"quality:dead": "knip"` (report-only; nothing wires them into `pnpm check` or `pnpm test`).

**Validation gate**:
- `pnpm quality:duplicates` and `pnpm quality:dead` both run to completion, exit 0, and write reports.
- `pnpm run check-types` still green (new devDeps don't leak into build).
- Validator reads both configs against the fleet-canonical settings above and confirms no gate/threshold was smuggled in.

---

## Phase 5 — Shared test-utils extraction (effort M)

**Goal**: de-duplicate the mirrored `_helpers.ts` fakes (web ↔ chat-runtime) into `packages/test-utils`, preserving suite behavior exactly. Sequenced after Phase 2 so the coverage ratchet shows any accidental behavior change.

**Role**: implementer → validator (sequential), with a hard behavior-preservation contract.

**Tasks**:
1. Inventory first: diff each mirrored pair — `apps/web/tests/unit/{runtime,signaling,orchestrator,store}/_helpers.ts` vs `packages/chat-runtime/tests/unit//_helpers.ts`. Extract **only what is genuinely mirrored** (the synthesis delta's target). Divergent helpers stay put. Do not attempt to unify the crypto `_helpers.ts` or the e2e helpers — audit §7 marks those domain-specific.
2. Scaffold `packages/test-utils` (`@fuck-eu-chat-control/test-utils`, private, `type: module`, TS, extends `packages/config/tsconfig.base.json`, devDep on vitest types only as needed). Move shared fakes (e.g. `MockSignalingSocket`, in-memory storage fake, peer/connection factories) into per-domain modules with explicit exports.
3. Update the importing test files **by changing import paths only** — zero assertion, zero logic, zero fixture-value edits. Chat-runtime tests switch from the alias-relative `./_helpers` import to the workspace package (the self-link alias issue does not apply: chat-runtime's vitest alias maps the package name to `src`; add `@fuck-eu-chat-control/test-utils` as a real devDep so it resolves normally). Web tests import it via `workspace:*` devDep.
4. `pnpm-workspace.yaml` already globs `packages/*` — no workspace edit needed. Add the package to the AGENTS.md monorepo table.
5. Delete the absorbed `_helpers.ts` content from both trees (keep any non-mirrored remainder as a slimmed local helper).

**Crypto-don't-break constraint**: the validator must `git diff` every touched test file and confirm changes are import lines only. KAT literals, timing/race structures, and fixture seeds are byte-identical. Any test that behaves differently under the new imports fails the phase.

**Validation gate**:
- `pnpm --filter @fuck-eu-chat-control/test-utils check-types` (new package wired into workspace typecheck), `pnpm run check-types`, `pnpm test:unit`, `pnpm test:mobile` all green.
- Coverage thresholds from Phase 2 still pass with no meaningful delta (validator compares the text-report line % before/after; >1 pt drop = fail).
- `pnpm quality:duplicates` report shows the mirrored-helper clones gone (or reduced to the deliberately-kept remainder).
- Validator's line-by-line review: no assertion edits, no `any`, no dead leftover helper exports.

---

## Phase 6 — Slow tiers on demand: integration + e2e + desktop (effort M)

**Goal**: the real-server integration tier, the two-browser Playwright tier, and `cargo test` are documented as on-demand local commands with proven-green runs — never part of the default gate (D5). Also retires the repo's one fake-pass vector (DoD 9).

**Role**: implementer → validator (sequential).

**Tasks**:
1. Document the slow tiers as on-demand local commands and prove each once (implementer runs them; validator witnesses):
- **integration**: `pnpm --filter @fuck-eu-chat-control/chat-runtime test:integration` (boots a real dev server; dev baseline needs no STUN/TURN — `/ice-config` returns empty ICE servers by design, so no coturn/docker is needed).
- **e2e**: `pnpm test:e2e` after `pnpm --filter web exec playwright install --with-deps chromium firefox` (Playwright config: chromium + firefox, `fullyParallel`, retain-on-failure artifacts stay local under `playwright-report`/`test-results`, both git-ignored). **Prerequisite check**: verify whether `pnpm dev` (the Playwright `webServer`) requires the SPAKE2 wasm artifact (`pnpm --filter web build:wasm:spake2`, needs `wasm-pack`). If yes, document the wasm-pack prerequisite in AGENTS.md's Testing section; if no, record that finding.
- **desktop**: `pnpm test:desktop` (`cargo test` on `apps/desktop/src-tauri`; 2 tests today — cheap, just needs the local Rust toolchain).
- Root convenience script `"test:slow"` chaining the three is allowed but stays out of `pnpm verify`.
2. **Retire `--passWithNoTests`** (DoD 9): `apps/web` has no `tests/integration/` directory — the web integration config (`apps/web/vitest.integration.config.ts`) + its `test:integration` script + the `--passWithNoTests` flag guard an empty tier. Delete the config file, the script, and the `&& pnpm run test:integration` leg of `apps/web`'s `test` script; note in the AGENTS.md Testing section that web integration coverage re-enters via `packages/chat-runtime`'s tier (where the broker integration tests actually live) or a future `apps/web/tests/integration/`. If the implementer instead finds a reason to keep the tier, the alternative is writing the first real web integration test — deletion is the default.
3. Track the honest skip: file a short note (in the AGENTS.md Testing section or a GitHub issue opened by the orchestrator) referencing `apps/web/tests/e2e/settings.spec.ts:139` (broker-drop scenario, skipped pending a controllable broker) so the skip has an owner and a path back.

**Validation gate**:
- All three slow-tier commands complete green locally (implementer runs, validator witnesses; the e2e browser install may be done once on the validator's machine).
- `pnpm --filter web test` (post-edit) = unit + e2e only, no empty tier; `pnpm run check-types` green.
- Validator confirms no workflow file exists anywhere (D5) and the slow tiers are not wired into `pnpm verify`.

---

## Phase 7 — Stryker mutation tier on the crypto core (effort M)

**Goal**: measure whether the crypto suite kills mutants (D4's fleet-nominated scope for this repo). Report-only; on demand only.

**Role**: implementer → validator (sequential).

**Tasks**:
1. `pnpm --filter @fuck-eu-chat-control/chat-runtime add -D @stryker-mutator/core @stryker-mutator/vitest-runner` (latest major with vitest-4 support; pin exact in the package's devDeps — Renovate keeps it current).
2. Create `packages/chat-runtime/stryker.config.json`: `mutate: ["src/crypto/**/*.ts"]` (the fleet-nominated core; deliberately **not** the whole package — runtime/race suites are too slow per D4's cost note), `testRunner: "vitest"` pointed at `vitest.config.ts` (unit tier only), `reporters: ["clear-text", "html", "json"]`, `htmlReporter` output to `reports/stryker`, `tempDirName: "stryker-tmp"` (gitignored), `concurrency: 2` (conservative — hash-wasm/WASM + WebCrypto sandboxes), `timeouts` per Stryker defaults, `disableTypeChecks` for helpers as needed. **No `thresholds.break`** — report-only.
3. Root script: `"mutate:crypto": "pnpm --filter @fuck-eu-chat-control/chat-runtime exec stryker run"`.
4. Run on demand only (`pnpm mutate:crypto`); HTML/JSON reports are written locally to `reports/stryker` (git-ignored). Never in `pnpm verify` or any default gate.
5. Run it once (implementer, before reporting done); record the mutation **score baseline** in the AGENTS.md Testing section next to the coverage baseline, with the same ratchet spirit: never lower, revisit scope (e.g. adding `src/protocol/**` or `src/framing/**`) once the crypto score is understood.
6. Add `stryker-tmp/`, `reports/` to `.gitignore`.

**Validation gate**:
- `pnpm mutate:crypto` completes and writes reports (expect tens of minutes — that is why it is not in the default gate; if it exceeds ~45 min, narrow `mutate` globs and record why).
- Score baseline present in AGENTS.md and consistent with the JSON report.
- `pnpm run check-types`, `pnpm test:unit` still green; config reviewed line-by-line by the validator: no thresholds/gates, crypto-scope only, unit-tier runner only.

---

## Phase 8 — Close-out: AGENTS.md truth pass + DoD audit (effort S)

**Goal**: AGENTS.md documents the final state with zero phantom docs; every DoD item is verifiably done or explicitly deferred.

**Role**: validator-led (a single validator with the fix loop available if docs drift is found).

**Tasks**:
1. Final AGENTS.md **Testing** section: tiers + commands, coverage provider/baseline/threshold/ratchet, mutation score baseline, quality-report commands (`quality:duplicates`, `quality:dead`, `mutate:crypto`), the gate description (what `pnpm verify` gates vs the on-demand tiers), hooks-as-convenience statement, nitro-patch update protocol.
2. Walk the DoD table (section 2) item by item against reality; mark each done/deferred with evidence paths.
3. Confirm nothing regressed: `pnpm check`, `pnpm test:unit`, `pnpm test:mobile` green; `pnpm test:coverage` thresholds hold; `pnpm verify` green on latest `main`.

**Validation gate**: the walk itself — every DoD row has a current status and evidence; all listed commands run green from repo root.

---

## 4. Risks & repo-specific notes

1. **Patched nitro dep (`nitro@3.0.260610-beta`)**: the patch (`patches/nitro@3.0.260610-beta.patch`) is security-load-bearing — it forwards a wire-level `ws` `maxPayload` (default 32 KiB, env-overridable, fails closed) because upstream exposes no config key; without it a few sockets can OOM the broker pre-app-layer. Any version bump invalidates the patch; Renovate (Phase 3) must never auto-merge it, and each bump requires re-deriving the patch + re-running the broker integration tier. Longer term: consider upstreaming the `NITRO_WS_MAX_PAYLOAD` behavior so the patch can die.
2. **Crypto-don't-break guarantees**: the KAT suites use published NIST/CAVP literals — never regenerate expectations from the project's own code (the tests themselves assert this). Phase 5's extraction is import-path-only by contract; Phases 2/7 must not change test environments, timers, or parallelism of the race/glare suites (`resume-glare-role`, `handshake-teardown-races`, `chat-controller-lifecycle-races`). Any test behaving differently under instrumentation = phase failure.
3. **Tauri/Expo in gate scope — deliberate narrowness**: mobile jest is in `pnpm verify` (fast, node-env); `cargo test` is on-demand only (Rust toolchain cost for 2 tests today); **no** `expo prebuild`, no Tauri bundle build, no mobile e2e in v1 — those are native-toolchain tiers with low current test value. Revisit when the native shells grow suites.
4. **SPAKE2 wasm prerequisite**: Playwright's `webServer: pnpm dev` may require `build:wasm:spake2` (wasm-pack) to have run — Phase 6 verifies and, if needed, documents the wasm-pack prerequisite in AGENTS.md. Integration tier (node-side) must not silently depend on a developer-machine wasm artifact.
5. **`DimitriGilbert/.github` does not exist (checked 2026-09-05)** — and under the no-CI decision it no longer matters for this plan: no caller and no fallback workflow exist (D5). Only the Renovate preset fallback (Phase 3) remains convergence-relevant: when the org preset lands, the standalone config swaps to `local>DimitriGilbert` in a one-line change.
6. **CRAP-score caveat**: the fleet CRAP pipeline consumes istanbul's `coverage-final.json`; this repo uses the v8 provider (D2 default). v8 also emits `coverage-final.json`, but its function-hit semantics differ — when CRAP tooling is added (after Phase 2), verify it parses v8 output or switch that one report to istanbul; either way CRAP stays report-only. Not scheduled in v1.
7. **vite-plus maturity**: `vite-plus` 0.2.2 is early; `vp staged` hook installation may not work (Phase 0 has the delete-fallback). Type-aware linting (`typeAware`/`typeCheck`) is off — treat enabling it as an opt-in follow-up ratchet, not a DoD blocker (D3 exception covers the stack, not the config quality).
8. **Honest skip stays honest**: `settings.spec.ts:139` is a documented skip, not a fake pass — Phase 6 gives it a tracking reference rather than deleting or fake-fixing it.
9. **`pnpm test` at root runs unit + e2e** (Playwright needs browsers + dev server) — humans and agents must use `pnpm test:unit` for the fast headless gate; AGENTS.md states this explicitly so nobody wires `pnpm test` into the gate.

---

## 5. Effort summary

| Phase | Focus | Effort |
|---|---|---|
| 0 | Hook wired + gate documented | S |
| 1 | Local gate composition (types + lint + unit + mobile) | M |
| 2 | Coverage + thresholds (chat-runtime, web) | M |
| 3 | Renovate + nitro update protocol | S |
| 4 | jscpd + knip report-only | S/M |
| 5 | Shared test-utils extraction | M |
| 6 | Slow tiers on demand (integration, e2e, cargo) + kill `--passWithNoTests` | M |
| 7 | Stryker on `src/crypto` (on demand, report-only) | M |
| 8 | AGENTS.md truth pass + DoD audit | S |

Total: ~6–8 focused days of implementer+validator time; every phase independently shippable, ordered so the two P1s (local gate, coverage) land first and nothing touches test behavior until Phase 5.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reading AGENTS.md, the root package.json, and vite.config.ts, then run the existing pnpm check and test commands named in Phase 0. Work through the plan in phase order, beginning with the hook and documentation decision; done means the listed local gates, coverage, quality reports, and tier documentation are implemented and validated without changing existing test behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
build-system, testing, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.