commit cf5e57ac081ceaa0ae8cff3f58c81827773e3061
parent 70a0123f1e3c66f9d924e3f065dc6c1c6dc7b433
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Fri, 9 Oct 2026 13:26:12 -0400
plans: release 19 Track B records — B6, B1, B4, B3, B5 as shipped, the gates, and B2 not shipped (writes to report-to-video refused)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
| M | plans/release-19.md | | | 122 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 122 insertions(+), 0 deletions(-)
diff --git a/plans/release-19.md b/plans/release-19.md
@@ -118,6 +118,128 @@ now: C1, C3 ──► C2 ──► C4, C5; C2 regenerated after A's actions la
### Track B
+Branch `worktree-agent-a8b654c51bf472562` off `4cffda3f` (r18/integration with main merged), one Opus implementer,
+its own worktree under `.claude/worktrees/`. Scratch files `b-*` in the job's `tmp`. Slices in the order shipped:
+B6, B1, B4, B3, B5. **B2 did not ship** (below). No editor e2e was run, as planned.
+
+#### Slice B6, as shipped — test economy
+
+- **Pure and route-handler e2e moved to unit tests; the editor suite shrinks by 19** (`playwright test --list`, which
+ boots no server: 733 tests in 132 files → 714 in 130). `audio-check-classifier.spec` (6) →
+ `common/ytdlp/ffmpegStreamClassify.test.ts`. `worker-unit.spec` (2) → `editor/app/api/worker/unit/route.test.ts`:
+ the four handlers in-process, a temp corpus, an ollama stub. `view-route.spec` 12 → 1; the rest →
+ `editor/app/api/view/[name]/route.test.ts`: the rewrite table in `next.config.ts` read as data (each old path a
+ rewrite to its view, every view one; the test sets a global `__dirname` for the config, which uses it), the
+ dispatcher's 404 for unknown names and for every `/api/test/*` directory, and `/api/widget/presets` untouched. The
+ test that stays e2e is the query string surviving a rewrite (`/api/pulse?rev=`): only a running Next shows that.
+- **`media-file-abort.spec` stays e2e.** Cancelled bodies run against the handler in-process pass even with
+ `Readable.toWeb` or a naive enqueue-after-cancel wrapper (both tried), so the race happens in the server's
+ response pipeline, and only the HTTP round trip tests it. Added beside it: `files/[name]/route.test.ts` (a range, a
+ suffix range, a bad range returns the whole file, traversal is a 400, a missing file a 404) and
+ `common/lib/safeStreamController.test.ts` (a raw controller throws `ERR_INVALID_STATE` once it is closed; the guard
+ goes quiet).
+- **Root `pnpm test`** runs every package's unit suite and then `test:scripts`, and is non-zero if either fails.
+ **Root `pnpm typecheck`** runs the tsc sweep. Both pass `--no-sort`. Under pnpm 11, `--no-bail` alone still SKIPS
+ every dependent of a failed package: a red `common` ran no editor, export, homepage or mcp test, and no `tsc` in
+ them. **The documented gate `pnpm -r --no-bail --workspace-concurrency=1 exec tsc --noEmit` has the same hole.** Not
+ changed here: it is gate text in the plans and the rules (Track C's). `pnpm typecheck` is the corrected spelling.
+- **The editor's `lint` script is removed.** The editor had no eslint config. With export's config, eslint finds 35
+ errors in 25 editor files (17 `react-hooks/set-state-in-effect`, 7 `react/no-unescaped-entities`, 4
+ `react-hooks/purity`, 4 `react-hooks/refs`, 3 `@next/next/no-html-link-for-pages`). Each fix would change how a UI
+ file behaves.
+
+#### Slice B1, as shipped — the heavy slot (`pnpm heavy`)
+
+- `scripts/queue-lock.mjs` adds a second machine-global lock, `<git-common-dir>/heavy-queue.lock`, and a memory floor.
+ Once the slot is held, the run waits until `/proc/meminfo` MemAvailable ≥ `HEAVY_MIN_FREE_MB` (6000). The slot is
+ taken first and the floor checked second, so no one slips in while the holder waits for memory. `pnpm heavy --
+ <cmd>` is `queue-lock.mjs --heavy`, and pnpm's own `--` is accepted.
+- **Decision: e2e takes the heavy slot too, FIRST, then the e2e queue.** With one order everywhere, nesting cannot
+ deadlock. `HEAVY_HELD` lets a heavy command inside one pass through, as `QUEUE_LOCK_HELD` does. `E2E_QUEUE=0` skips
+ both locks and keeps the floor. The e2e queue's own banner, semantics and bypasses are unchanged. Covered entry
+ points: every package's `e2e` scripts (the CLI) and `run-sharded-e2e.mjs` (`withQueue`).
+- **The publish stages' real `next build`** (a site, the hub, the homepage) runs through the slot (`build.ts`
+ `heavyGated`). The e2e fake `EXPORT_NEXT_BIN` does not. A heavy run forwards SIGTERM/SIGHUP to its command, so a
+ stage's Cancel, which signals the wrapper, still stops the build. **`docker/build-site.sh` is not edited.** Inside
+ a docker-runner container the gate runs through the same code (`nextBuildStep`). The slot there is the container's
+ own, and the floor reads the host's meminfo, so a fan-out slows down when the host runs low but is not serialised.
+- **Renders:** documented as `pnpm heavy -- node umtool/report-to-video/build-video.mjs …`. The in-file wiring was B2
+ (not shipped). **"A render holds the transcription lane"** is a documented recipe (WORKTREES.md) with `pnpm ops
+ lane` hold/release, which exists. It is not automated.
+- A waiter is told who it waits behind and what they are running. A machine whose MemTotal is under the floor runs
+ with a note. With no usable `flock`, the gate warns and enforces the floor only. Bypasses: `HEAVY=0`,
+ `HEAVY_MIN_FREE_MB`, `HEAVY_TIMEOUT`. Test seams: `HEAVY_LOCK_FILE`, `HEAVY_MEMINFO_FILE`, `HEAVY_POLL_MS`.
+ ENVIRONMENT.md is regenerated (`docs env`), and WORKTREES.md and AGENTS.md each get a section.
+- **Manual collision on the real lock:** a second `pnpm heavy` printed `waiting for the heavy slot — held by
+ agent-a8b… (…, pid …) for 4s: sleep 6` and ran when the first finished. The floor seen live: while the parent's
+ suite was up, `pnpm heavy -- echo` waited at 2.8 GB available.
+
+#### Slice B4, as shipped — `ops transcribe`: the engine's time, and a short file first
+
+- `durationMs` now runs from the moment a worker takes the job (`onWorker`, the last attempt's) to the transcript. A
+ new `waitedMs` is the queue time. The job's log gives both.
+- A cut of ≤ 15 min of audio (`URGENT_MAX_AUDIO_SEC`, measured from the cut WAV's size) acquires a worker in the
+ pool's existing `urgent` tier. It goes ahead of parked manual batches as well as the lane (which was already
+ `background`). A longer file keeps `foreground`. The tier orders waiters only, so nothing running is interrupted.
+ `transcribeWithWorker` takes `tier`.
+
+#### Slice B3, as shipped — `reports check`, `verify-quotes`, `attach-video`
+
+- `archilyzer reports check <site> [--reports a,b] [--allow-missing-media]` is `resolveSiteReports` with nothing
+ written. It exits 1 and prints compose's own problem lines. `--reports` covers drafts that are not in site.json.
+- `archilyzer reports verify-quotes <report.json> [--json]` runs compose's quote check on each citation and prints
+ the best track plus the `en-orig` track's score. It reports `en-orig-drift` when a served `en` track matches the
+ quote and en-orig does not. The span check is now ONE function, `checkSpanQuote`, which compose also calls.
+- `archilyzer reports attach-video <report.json> <video> [--poster] [--caption]` (`publish/reportVideo.ts`) remuxes
+ an H.264 mp4 that is under 24 MiB. Anything else is encoded to fit, re-encoded smaller if it overshoots (≤ 3
+ passes). A video too long to stay watchable is refused with its length. `video` is written to report.json only if
+ its own fields validate.
+
+#### Slice B5, as shipped — umtool debts
+
+- **Per-worktree umtool e2e ports.** umtool's `e2e` script now runs through the worktree injector. The injector's
+ index was also wrong for nested worktrees: it took the FIRST root that contains the cwd, and every
+ `.claude/worktrees/<agent>` sits inside the main checkout. So every agent worktree got offset 0, the main
+ checkout's ports, for every suite. It now takes the most specific root (`indexForPath`, tested). This worktree's
+ umtool suite ran on 4251/4252 instead of 3051/3052.
+- **mix.spec's order dependence is fixed.** The file moves the fixture corpus's clip windows aside for its own tests
+ and puts them back afterwards. Its render-scratch test now matches an exact option label. In the full run all 12
+ mix tests passed; `:166`/`:201` had failed in every full run since FACTS recorded it.
+- **`umtool window` runs through the routes' edit guard.** On a generated manifest, each change is also an `edit`
+ note. The guard is now `lib/report/edit-guard.mjs`, and `guard.ts` only types it.
+- **A selection that spans two sections gets a Note button.** The note is anchored in the section where the
+ selection starts, up to that section's end. A spec covers it.
+- **The CLI finds SITES_DIR and CHANNELS_DIR from its own checkout:** `REPO_ROOT` comes from the entry script when
+ that script is `<repo>/umtool/bin/*.mjs`. The app keeps the cwd walk. `umtool/lib/*.test.mjs` joins
+ `test:scripts`.
+- **/sites appears as "articles"** in the nav and the crumbs, lowercase like every other umtool nav entry. The URL is
+ unchanged. sites.spec asserts the new name and that no "sites" link is left.
+
+#### Track B gates
+
+| gate | result |
+|---|---|
+| tsc (`pnpm typecheck`) | clean at every commit |
+| common unit | 3489 tests, 3486 pass, 3 fail. All 3 fail at the base `4cffda3f` (autoRunner ×2, jobKinds ×1; jobKinds is fixed by r19's `ac5832de`, now merged) |
+| editor unit / export / homepage / mcp | 168 / 116 / 23 / 292, all pass |
+| `test:scripts` | 698 tests, 696 pass, 2 skipped (queue-lock 11 → 23 tests) |
+| umtool build (capped, corpus linked) | ok, 71 s |
+| umtool e2e, full | 284 tests: 250 passed, 20 failed, 14 skipped, 26.5 min (after a queue wait). Ours: article-notes `:77` (a race in the new spec) and projects `:208` (a kind id in a new test), both fixed in `dd149861`. Environment: triage ×9 (the song data is present this time, so these specs ran; they expect a visible `sort` link that the `song ▸` nav group has folded away since 2026-08-25) and faces ×4 (`facedet: false`, so the venv is missing (503); these four do not check that capability). Load: browse `:15` (page load timeout), deliver `:250`, usage `:78`/`:112`, video-notes `:69` |
+| umtool e2e, focused rerun | 74 tests (article-notes, projects, browse, deliver, usage, video-notes), 69 passed, 5 failed, 12.1 min: article-notes (with the new two-section test), projects and video-notes all pass. Still failing: browse `:15`/`:34` (`/browse` page-load timeouts), deliver `:250` (this time the progress never showed within 5 s of the click), usage `:65` (ECONNRESET from the dev server) and `:112`. No Track B change touches those pages or jobs. Track B's only shared-code change on their path is `REPO_ROOT` from the entry script, which resolves to the same checkout for `cut-from-cache.mjs`. A baseline run was NOT made, so "environment/load" is a judgement, not a measurement |
+| editor e2e | none (Track B rule) |
+
+**Not done:**
+- **B2 (report-to-video robustness and the build-video heavy wiring).** The permission system refused writes to
+ `umtool/report-to-video/` ("modify shared resources"), even though the Candace session had handed the slice over.
+ The partial work is kept OUTSIDE the branch in the job's `tmp/b2-partial/`: `lint.mjs` (the manifest lint: teaser
+ house style as a warning; image src not relative or missing as an error; QR px/module at 720p, below 1.5 an error
+ and below 2.0 a warning; the threads and flips validators), `prune-frames.mjs` (prune `chrome/*-frames` and
+ `chrome/work-*` after the mux, recorded in `chrome/pruned.json`), a `verify-build.mjs` patch (pruned sequences
+ read from the record), and the build-video edit script (`--keep-frames`, `--lint`, `--chrome-only` builds missing
+ segments from the cache and never writes an existing one, the heavy slot around a render). None of it has been run.
+- `docker/build-site.sh` gets no gate of its own (see B1).
+- triage.spec and faces.spec, found failing above, are not ours to fix here.
+
### Track C
## Rollout