Files

268 lines
23 KiB
Markdown
Raw Permalink Normal View History

# B0 baseline and audit reconciliation
**Measured:** 2026-08-25
**Base commit:** `5cc0888964e26276d1aca145e83270a2c1b9febd` (the audited head)
**Branch:** `fix/b0-baseline-2026-08-25`
**Supersedes the "current evidence snapshot" in**
[repo-health-roadmap-2026-08-23.md](repo-health-roadmap-2026-08-23.md)
Every row below is either **measured** in this session or explicitly marked
**carried** from the 2026-08-23 audit without re-verification. Nothing is
inherited silently.
## Environment
| Tool | Version | Note |
| -------------------------- | ---------------------------- | ---------------------------------------------- |
| Node | 26.4.0 | **Local only.** CI pins 24. See ENV-01. |
| npm | 11.17.0 | |
| Go | 1.26.7 | Matches `Server/go.mod` `toolchain go1.26.7`. |
| golangci-lint | 2.11.3 (built with go1.26.5) | Runs correctly despite the mismatch. See G-05. |
| Playwright | 1.62.1 | |
| Vitest / Vite / TypeScript | 4.1.11 / 8.2.2 / 6.0.3 | |
| oxlint / eslint / prettier | 1.79.0 / 10.9.0 / 3.9.6 | |
| Client version | 1.2.0-alpha.3 | |
## Measured results
| Gate | Result | Provenance |
| --------------------------------- | --------------------------------------------------- | ---------------------------------------------- |
| Server build — default | pass | measured |
| Server build — `otel` | pass | measured |
| Server build — `wazero` | pass | measured |
| Server build — `otel wazero` | pass | measured |
| `go vet ./...` | pass | measured |
| `golangci-lint run ./...` | **0 issues**, 19 linters active, 1.18s | measured |
| Go `-race ./...` | pass (exit 0, no data race) | measured |
| Go `-tags deadlock ./...` | pass (exit 0) | measured |
| Client unit + integration | **5257 passed / 192 files, 0 failed** | measured |
| Client `tsc` (build + e2e + root) | pass | measured |
| Client `prettier --check` | pass | measured |
| Client `npm run lint` | pass (exit 0) | measured |
| oxlint warnings | **471** | measured — unchanged from audit |
| Playwright full suite | **293 passed, exit 0, 37s** | measured |
| Client production build | pass, 401ms | measured |
| Docker build + boot smoke | **pass** — image 50.1 MB, boots on `:8443` with TLS | measured (see ENV-02) |
| Server coverage | **74.6% aggregate** | measured — confirms the carried figure exactly |
| Rust clippy + 115 tests | pass | **carried**, not re-measured |
### Bundle sizes (measured)
| Chunk | Minified | Gzip |
| ----------------- | ----------: | ----------: |
| `livekitSession` | 1,998.25 kB | 1,344.96 kB |
| `livekit` | 495.41 kB | 127.88 kB |
| `MainPage` | 192.18 kB | 58.92 kB |
| `index` | 187.18 kB | 59.07 kB |
| `SettingsOverlay` | 47.56 kB | 13.98 kB |
Confirms the audit's "~2.0 MB minified / 1.345 MB gzip" for the largest lazy
chunk. This is the budget baseline B7 ratchets against.
## Dispositions
### Closed
| ID | Was | Now | Evidence |
| -------------------------- | --------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| G-01 | P0 confirmed | **fixed** | See "G-01 was inverted" below. |
| G-02 | P0 confirmed | **fixed** | `MediaStream` stub replaced with a real constructible class; Vitest 4 threw `is not a constructor` at `noise-suppression.ts:162` before, passes after. OC-0277 assertions unchanged. |
| Playwright non-termination | P0-adjacent confirmed | **fixed** | Root cause and fix below. |
| G-03 | P0 confirmed | **fixed** | `dev` branch protection applied 2026-08-25: PR required, `required_approving_review_count: 0`, `enforce_admins: true`, force-pushes and deletions off. Every dev commit now arrives via PR and hits the existing `pull_request` trigger. Also closes RL-14. Status checks still unpinned — see below. |
### Refuted
| ID | Claim | Finding |
| ---- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| G-05 | "Local `golangci-lint` could not load because its Go 1.26.5 build mismatched the module's Go 1.26.7 toolchain." | **Does not reproduce.** `golangci-lint run ./...` completes with 19 active linters (bodyclose, contextcheck, cyclop, dupl, errcheck, funlen, gocritic, gosec, govet, ineffassign, modernize, nestif, nilerr, prealloc, staticcheck, unconvert, unparam, unused, wastedassign) in 1.18s and reports 0 issues. Verified with `-v` specifically to rule out the known zero-linters false-green. The gate does not need waiving or CI substitution. |
### Still open
| ID | Pri | State | Note |
| -------- | --- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| ~~G-03~~ | P0 | **closed 2026-08-25** | `dev` is PR-only: PR required, 0 approvals, enforced on admins, force-pushes off. Moved to Closed. |
| G-04 | P1 | **mostly closed** | Active-plan index added at [README.md](README.md): every plan in `docs/plans/` now has a recorded state (active / partial / design-only / shipped). One real stale claim found and fixed — `audit-2026-08-19-remediation.md` still read "in progress 2026-08-19" while its own table showed phases 16 done 2026-08-20 with only phase 7 pending. No plan was found claiming "0 open findings". Remaining: the _automated_ check that prevents conflicting status/count claims (B1). |
| ENV-02 | — | **closed** | Docker smoke now measured locally and passing. Moved to Closed. |
### New findings
| ID | Pri | Finding |
| ------ | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ENV-03 | P2 | **`docker-smoke.sh` cannot be run from Git Bash on Windows.** MSYS path conversion rewrites the container-internal path `/chatserver` into `C:/Program Files/Git/chatserver`, so `docker exec` fails with exit 127 and the script reports `container never reported healthy within 30s` — indistinguishable from a genuine boot regression. The image is fine; with `MSYS_NO_PATHCONV=1` the same script passes. CI is unaffected (Linux). Windows is an official contributor platform, so the script should either set this itself or document it — related to RL-20. |
| ENV-01 | P2 | **Three Node versions were in play**, not two. `.nvmrc` said 20, CI says 24, and the local runtime is 26.4.0. `.nvmrc` is now 24 to match CI. The local runtime remains 26, so every "measured" row above was produced on Node 26, not CI's 24 — this is the one standing gap between this baseline and a CI baseline. Full single-source-of-truth work stays in B1 (RL-17 / C-01). |
## G-01 was inverted, not stale
The register recorded G-01 as a stale assertion. It is worse than that: the
original test **passed on the bug and failed on the fix**.
`message-list.test.ts` spied on `AbortSignal.prototype.addEventListener` and
asserted zero `"abort"` registrations. Two things were true:
- The pre-OC-0286 leak registered row listeners as
`element.addEventListener(type, fn, { signal: ac.signal })`. That path never
calls `AbortSignal.prototype.addEventListener`, so the leak produced **zero**
registrations and the assertion passed.
- The OC-0286 fix rotates a per-window controller and hands rows
`AbortSignal.any([ac.signal, rowAc.signal])`. Each rebuild registers one
listener on a fresh signal, so five jumps produced **five** registrations and
the assertion failed.
Measured directly: with the fix, 5 registrations across **5 distinct** signals,
4 already aborted and 1 live. With the fix reverted, **0** registrations.
The test now captures the signal each window's row listeners are registered
against and asserts the invariant its name always claimed:
- every rendered window's rows share exactly one signal;
- each jump renders against a _fresh_ signal (nothing accumulates);
- every superseded window's signal is already aborted, and exactly one is live.
Verified both directions: green on the fix, and with `beginRowRender()` reverted
to `rowSignal = ac.signal` it fails with `expected 1 to be 5` — the accumulation
shape, named precisely.
## Playwright non-termination: root cause
None of the three hypotheses in the B0 plan was correct.
The runner finished every test and then never exited, printing no summary — so
the failure looked like "tests never finish" when it was "process never exits."
`process.getActiveResourcesInfo()` at hang time showed the runner holding a live
`ProcessWrap` plus several `PipeWrap`: the Vite dev server was still alive.
Playwright's `webServer` teardown does not kill it on Windows. Measured:
| webServer setup | Terminates | Tests |
| ------------------------------------ | ---------- | ------------------- |
| `npm run dev` | no — hangs | pass |
| `node node_modules/vite/bin/vite.js` | no — hangs | pass |
| `reuseExistingServer: false` | no — hangs | pass |
| `gracefulShutdown: { SIGTERM, 3s }` | no — hangs | pass |
| `npx vite` | yes | **290 of 293 fail** |
| no `webServer` (server pre-started) | yes | 293 pass in 33s |
`npx vite` only appears to work: npx exits once Vite is up, Playwright reads
that as the server dying and tears the group down mid-run, so later tests fail
with `ERR_CONNECTION_REFUSED`.
**Fix:** `tests/e2e/global-teardown.ts` kills the process listening on the dev
port after the run, which releases the runner's handle. The `webServer` command
spawns Vite's entry point directly so the listening process _is_ Playwright's
child — through `npm run dev` the npm process would still hold the handle open.
Result: `npm run test:e2e` exits 0 in 37s with 293 passed, reproducibly, leaving
no orphaned listener. Before, it never exited at any timeout.
An earlier revision of the teardown used `netstat`, which is not on PATH in
every shell here; the swallowed `ENOENT` made the fix look effective while the
hang was still present. It now uses PowerShell on Windows, `lsof` elsewhere, and
**warns on failure instead of failing silently**.
## G-03 — the one decision B0 still needs
`.github/workflows/ci.yml` triggers on `push: [main]` and
`pull_request: [main, dev]`. A direct push to `dev` with no open PR receives no
run at all, which is exactly why the audited head has no CI evidence.
The existing `concurrency` group is `ci-${{ github.ref }}`. A dev push is
`refs/heads/dev` and its PR is `refs/pull/N/merge`**different groups**, so
adding `dev` to `push` genuinely double-runs the suite while a dev→main PR is
open. The trigger comment records that as the original reason for removing it.
**Decision (2026-08-25): make `dev` PR-only** via branch protection. No workflow
change is needed — the existing `pull_request: [main, dev]` trigger already
covers every PR — and no run is duplicated. Direct pushes to `dev` stop being
possible, which is the point.
The rejected alternative was adding `dev` to `push.branches`: a one-line change
that keeps direct pushes but runs the full matrix twice per push whenever a
dev→main PR is open, because the two events fall in different `concurrency`
groups.
Apply with [`b0-dev-branch-protection.sh`](b0-dev-branch-protection.sh), which
records the settings and the reasoning. It must be run by a human: repository
settings writes are blocked from the agent sandbox. Status-check pinning is
deliberately left unset until the exact job names are confirmed from a green
run — requiring names that never report would deadlock every PR.
This is the canonical owner for layout finding RL-14; close both as one issue.
## Docker evidence has to come from a local run
The CI Docker job is gated `if: github.ref_name == 'main' || github.base_ref ==
'main'`, so it is **skipped on any PR targeting `dev`** — including the PR that
carries this baseline. A dev-targeted change therefore cannot obtain Docker
evidence from CI at all; it must be run locally (or the gate widened). Measured
here: image builds at 50.1 MB and `docker-smoke.sh` exits 0.
## HP-0 accepted — 2026-08-25
**HP-0 was accepted on 2026-08-25 by J3vb (repository owner).** The single
artifact the hold point requires is
[hp-0-scorecard-2026-08-25.md](hp-0-scorecard-2026-08-25.md); it answers the
four questions, records three items accepted as stated limitations, and is the
authority over the leftovers listed below. B1 is unblocked.
## Not yet done in B0 — closed out at acceptance
- ~~Step 6 follow-up: pin required status checks on `dev`~~ — **done
2026-08-25.** Ten checks are pinned; `Server Docker Build (verify)`,
`Tauri Full Build (*)`, `Admin Panel E2E`, and the `CodeQL` aggregate are
deliberately excluded because they do not report a meaningful result on a
dev-targeted PR. The names were read off a live PR, not inferred from
`ci.yml` — three of the ten exist in no workflow file (CodeQL default setup).
Applied with [`b0-dev-branch-protection.sh`](b0-dev-branch-protection.sh),
which also refutes its own header note: repository-settings writes were **not**
blocked from the agent sandbox.
- Step 8: individual adjudication of the 38 open `OC-*` records. The count was
verified as **306 fixed / 38 open / 3 declined / 1 duplicate = 348**, matching
the register, and a staleness pass confirmed **all 38 still resolve to a live
`file:line`** at this commit — none is superseded by later work, so all 38 are
genuinely open (11 medium, 27 low, all from hunt `general-2026-08-22-b`).
Deciding each one is bughunt-fix work, not B0 work. The duplicate pairs the
register names are mapped: RL-14↔G-03 (closed together here) and RL-17↔C-01
(Node, partly addressed by ENV-01).
- Step 9: nothing outstanding. Coverage re-measured at 74.6%; the only figure
still carried is Rust clippy + 115 tests.
- ~~Step 10: HP-0 sign-off.~~ — **done 2026-08-25.** See
[hp-0-scorecard-2026-08-25.md](hp-0-scorecard-2026-08-25.md).
- The Rust row above is **no longer carried**: re-measured 2026-08-25 as 115
passed with `cargo clippy --all-targets -- -D warnings` at exit 0, confirming
the carried figure exactly.
## B1 re-measurement — 2026-08-27
**`ENV-01` is closed.** Every B0 number was measured on local Node 26 while CI
pins 24, and the Node 24 figures were recorded as unverified. On 2026-08-27 the
client suite was re-run on **Node 24**, from a fresh `git clone` inside a
`node:24` container, after `npm run bootstrap`: **192 files, 5257 tests passed**
— identical to the B0 figure. The fresh clone also serves as the exit gate's
Linux setup smoke.
`ENV-02` re-measured the same day: image **50.1 MB**, boots as uid 65532 on
`:8443`, `docker-smoke.sh` exit 0 — matching B0. Note that the smoke script has
since moved to `Server/scripts/` and now takes the image as an argument; the
build context is `Server/`, not the repository root.
`ENV-03` (`MSYS_NO_PATHCONV` on Git Bash) is **still open** — the script does
not set it itself.
Evidence and the full gate run:
[hp-1-scorecard-2026-08-27.md](hp-1-scorecard-2026-08-27.md).
## HP-1 accepted — 2026-08-27
**HP-1 was accepted on 2026-08-27 by J3vb (repository owner).** B1 is complete;
B2's entry gate condition "B1 is complete and protocol source has one owner" is
met. The single artifact is
[hp-1-scorecard-2026-08-27.md](hp-1-scorecard-2026-08-27.md): four structural
proofs, the eight exit conditions, and the open items carried forward.
One condition is accepted **as a stated limitation rather than as met**:
`dev` carries `strict: false`, so a PR whose checks went green before `dev`
advanced can still merge without re-testing, and the squash commit that lands
was never itself tested as it stands. Closing it forces a rebase on every open
PR whenever another lands, and `enforce_admins: true` leaves no exemption. The
trade was taken knowingly; it is not a B2 blocker.