Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 30f0378ec99dccc061b9b9365f547195ca6b8414
parent 9c96c35f1aae9009bd43de239d23d8be77e92416
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Fri,  2 Oct 2026 00:09:15 -0400

plans: slice U2's record (the deliverables switch) and FACTS "umtool's deliverables switch"

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Mplans/FACTS.md | 21+++++++++++++++++++++
Mplans/release-17.md | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 161 insertions(+), 0 deletions(-)

diff --git a/plans/FACTS.md b/plans/FACTS.md @@ -8307,3 +8307,24 @@ phase deletes from the destination. `test-transcripts/` (`common/social/__fixtures__/firefoxCookieStore.ts`, imported by relative path) and points `cookiesFromBrowser` at it as `firefox:<abs path>` — a profile PATH in the spec, so no home directory is searched. + +## umtool's deliverables switch (verified 2026-10-01, branch `r17/umtool-deliverables`) + +- **`clips/` and `share-*/` of a report project may be links** into `UMTOOL_MEDIA_DIR` — per project, + when `video.manifest.json` says `"storage": { "deliverables": "media" }` (absent = local). `out/` is + the other directory that may be a link, and it follows `UMTOOL_MEDIA_DIR` on its own (slice U1). + Readers open `clips/<id>.mp4` by path through the link; a dirent `isDirectory()` filter over a + project hides a moved one (`listBatches` follows links for that reason). +- **One writer for the value:** `updateStorage` in `umtool/lib/report/manifest.mjs`, called only by + `moveDeliverables` (`umtool/lib/report/storage.mjs`) after every directory has moved. A no-op is not + rewritten. +- **A missing deliverable directory is made by `deliverableDir(project, name)`** — never a bare + `mkdir` (`cut.mjs`, `buildShareBatch`). Under `"media"` it is a link to a new mirror directory; the + media root is never created; a dangling link, a cut move's leftover (`<name>.moved-*`, + `<name>.incoming`), a bad value, or `"media"` in a process without `UMTOOL_MEDIA_DIR` refuse. +- **`umtool storage deliverables <project> --to media|local`** is the move (the bench's "Move + deliverables" runs it as a job). `umtool check` reports `storage-unreachable` (blocking: an `out`, + `clips` or `share-*` link whose target is gone) and `storage-mismatch` (open). +- The e2e app server has no media root (`UMTOOL_MEDIA_DIR=` in `playwright.config.ts`); specs give + their CLI one (`<fixture>-media`), so `deliverables.spec.ts` shows an app cutting through a link it + did not make and refusing a new batch under `"media"`. diff --git a/plans/release-17.md b/plans/release-17.md @@ -563,6 +563,146 @@ media tier; a dangling link still reads as "no build" there (`umtool check` lear mix render into it lands on the media root through the link (the write check is lexical; that matches the semantics). +### Slice U2, as shipped — umtool's deliverables switch (2026-10-01) + +Branch `r17/umtool-deliverables` off `main` `bb877f93` (slice U1 merged), `main` `1d5c33bf` (slice +XP) merged in before the gates, worktree `~/Projects/r12-source-mirror` (editor 5101, test 5111, +export 5110), one Opus implementer. Scratch files `U2-*` in the job's `tmp`. The ruling is the plan's: +deliverables (`clips/`, `share-*/`, final mp4s) move per project by a switch "like channels"; +manifests, `revisions/`, the caches and the cue cache stay put; final mp4s travel with `out/` (U1). + +**What it does.** +- **The switch.** `video.manifest.json` gains `"storage": { "deliverables": "local" | "media" }` + (absent = local). Its one writer is `updateStorage` in `lib/report/manifest.mjs` (the manifest lock, + tmp + rename, the mtime token; any other key under `storage` kept; a no-op is not rewritten, so no + open bench page's token goes stale). Its one caller is `moveDeliverables`, after every directory has + moved. +- **`lib/report/storage.mjs`.** `ensureOutDir`'s body is generalised to a name (`ensureProjectDir`; + `ensureOutDir` keeps its behaviour and sentences). New: `DELIVERABLES_MODES`, `isDeliverableName` + (`clips` or `share-<x>`, never a move's leftover), `deliverablesModeOf` / `deliverablesMode` (a plain + JSON read, so the module stays free of the writer's imports), **`deliverableDir(project, name)`** — + an existing directory or link is used as it is; absent and local → a directory; absent and media → + `mkdir -p <mirror>/<name>` + an absolute link, the media root stat'd and never created; refused, + creating nothing, on a dangling link, a cut move's leftover (the sentence names `umtool storage + deliverables <project> --to …`), a bad value, a project outside the reports root, or `"media"` in a + process with no `UMTOOL_MEDIA_DIR` (never silently local: a batch on the wrong drive is a split + nobody chose); `deliverableNames` (clips first, every `share-*` dir or link, and a name present only + as a leftover so a move finishes it); `deliverablesState`; `deliverablesProblems(state, name)` (the + sentences that stop a write: a dangling link, a leftover, and for a directory not there yet a switch + this process cannot honour); **`moveDeliverables(project, to, { writeMode })`** — U1's movers over + each name, then the switch only when none failed; idempotent; a cut move resumes; `--dry-run` + measures. `finishCommand` names the right command in the leftover sentence (`out` keeps U1's). +- **The writers.** `cut.mjs` makes `clips/` through `deliverableDir` (a refusal is `reason: "storage"`, + before any ffmpeg; tmp and final still share the directory). `buildShareBatch` makes `share-<name>/` + through it, and first refuses on `deliverablesProblems` — a batch that cannot read an earlier batch + would ship its clips again. A batch name shaped like a leftover (`x.incoming`, `x.moved-…`) is + refused by `share-batch.mjs` and the route. +- **The readers.** `listBatches` lists a `share-*` link like a directory, a dangling one as + `dangling` with no ids, and no leftover; `sharedIdsIn`'s walk follows a link to a directory (six + levels at most). `deliverStateOf` carries `storage` (the state, `cutBlocked`, `shareBlocked`). The + mix picker (`lib/media.ts`) follows `clips` and `share-*` links into the media root as it does `out`. + `kinds.mjs` needed nothing: U1's `SKIP_DIRS`/`SKIP_PREFIXES` already keep the walk out of both. +- **`umtool check`** (through `reportDecisions`): `storage-unreachable`, **blocking** — an `out`, + `clips` or `share-*` link whose target is gone ("is the media drive mounted?"); `storage-mismatch`, + open — a cut move's leftover (with the command that finishes it), a directory while the switch says + media, a link while it says local, or media with no `UMTOOL_MEDIA_DIR` in this process; a value + that is neither is `manifest-invalid` on `storage.deliverables`, blocking. +- **CLI.** `umtool storage deliverables <project> --to media|local [--dry-run] [--json]`: one line per + directory and the switch's before → after; exit 1 on any failure (the switch then stays). Refused + while a pipeline process works in the project — U1's `/proc` scan of the report scripts, now also the + report's own `apply-manifest.py` and `build.py` found by their working directory (they name no + path). Its blind spots, said in the code and `docs/folders.md`: the app's in-process work (the bench + runs the move as a job, so the app's one-job-at-a-time rule keeps cuts and batches out), a hand-run + command, another machine. `umtool storage [<project>]` lists each report's deliverables and switch. +- **Bench.** The deliver panel says where the deliverables are (`data-deliverables` = `local` | + `media` | `invalid`; one `data-deliverable="<name>"` with `data-deliverable-state` per directory) and + gains **"Move deliverables to media|local"** (`data-action="deliver-move"`, `data-move-to`), the + `move` action of `/api/report/deliver`, a job (`driver.mjs` `moveDeliverablesSteps` runs the CLI). + It is disabled, the reason in `data-move-reason`, while a job of the project runs ("… is running — + move when it has finished"), while a deliverable link dangles, and toward media when the app has no + `UMTOOL_MEDIA_DIR`. Cut and share are disabled with `deliverablesProblems`' sentences + (`data-deliver-blocked`), and the route refuses them with 409. +- **e2e.** `deliverables-fixture` (d01/d02 cuttable from a cached window; d03 cut and shipped in + `share-first`) and `deliverables.spec.ts` (5, serial): the CLI move (dry run first; links; the switch + set; again → already, not rewritten; `check` silent); the app (no media root) cuts THROUGH the + `clips/` link while the move button is disabled with "is running"; the app refuses a new batch + (button disabled with the reason, route 409, nothing made) and the CLI with the media root makes + `share-second` as a link, reading `share-first` through its own (d03 not shipped again); the root + unplugged → `check` exits 1 with `storage-unreachable` on all three links, the panel and the route + refuse, nothing made, the root not recreated; the bench's button brings everything home (the app, + untiered, leaves the media side and says "left in place"), the switch reads local, and the button + then offers media, disabled, naming `UMTOOL_MEDIA_DIR`. + +**Commits** + +| Commit | What | +|---|---| +| `a61b60e2` | `umtool:` the switch — `updateStorage`, `deliverableDir` and the deliverables helpers in `storage.mjs`, `cut.mjs`/`buildShareBatch` through it, `listBatches`/`sharedIdsIn` follow links, `umtool check`'s storage decisions, `umtool storage deliverables`, the busy scan's `apply-manifest.py`/`build.py`, the picker; `deliverables.test.mjs` (14) | +| `ec7c84f3` | `umtool:` "Move deliverables" on the bench, the route's `move` action and refusals, `deliverables-fixture` + `deliverables.spec.ts`, `docs/folders.md` + `docs/cli.md`, the `[Unreleased]` bullet | +| `eb9bb64e` | merge of `main` `1d5c33bf` (slice XP) — clean, nothing under `umtool/` | +| `875dbff1` | `umtool:` the spec's batch count includes the id its LIST.md says was already shared (run 1's one failure) | +| this commit | `plans:` this section; FACTS "umtool's deliverables switch" | + +#### Gates (logs `$T/U2-*`) + +- **tsc** (all workspaces) clean at `ec7c84f3` and on the merged tree. +- **common:** 2,501/2,501 on the merged tree (no common code touched). **Editor unit:** skipped — no + editor code touched (only `editor/CHANGELOG.md`). +- **test:scripts:** 408 tests: 406 passed, 1 skipped (LIVE), 1 failed — `queue-lock.test.mjs`'s + "QUEUE_LOCK_HELD passes straight through" (waited 1,065 ms at a load average of 16–22, the timing + case U1 met); `node --test scripts/queue-lock.test.mjs` alone right after: **11/11**. + `deliverables.test.mjs` **14/14**, `storage.test.mjs` 24/24, `next-build-trace.test.mjs` **10/10** + against the fresh build below. +- **The capped umtool build with the corpus linked** (worktree `transcripts/` set aside, `ln -sT`, + 77 channels visible, `systemd-run --scope -p MemoryMax=5G -p MemorySwapMax=0`, `timeout -s KILL + 240`, the link removed and the directory put back), on the merged tree: **exit 0, 32 s, 0.82 GB**; + with `UMTOOL_MEDIA_DIR` set to a scratch directory: **exit 0, 35 s, 0.82 GB**. `.nft.json` entries + 39,809 each, **identical** (`diff` empty), none naming `transcripts`, the scratch media root or + `.e2e-song`. +- **Numbers tool:** none. +- **umtool e2e** (`SONG_DIR=~/reports/quartering-uh-song/data`, `UMTOOL_MEDIA_DIR=` in the shell, + `pnpm --filter umtool run e2e …` from the worktree root, queued; lists in `$T/U2-specs*.txt`): + + | Run | At | Specs | Result | + |---|---|---|---| + | 1 | `eb9bb64e` | `storage`, `deliverables`, `deliver`, `clip-bench`, `report-fetch-via-editor`, `projects` | 91 passed, 1 failed, 2 did not run (serial), 7.7 min of tests (21 min with the queue and the fixture build) — `deliverables:143` expected the batch count `(2)`; the panel said `(3)`, which is right (fixed in `875dbff1`) | + | 2 | `875dbff1` | `deliverables` | **5 passed**, 0 failed, 2.1 min of tests (21 min with the queue) | + + `projects.spec` is the one extra beyond the prompt's list: it reads the decisions inbox, which gained + two kinds. + +#### Found and left + +- **The app's decisions index is signed without the deliverables** (`reportSignature`: the manifest, + `out`, `availability.json`, `revisions`): a drive unmounted under an unchanged manifest leaves a + cached inbox row set stale until something else changes. `umtool check` computes afresh. Signing + `clips`/`share-*` would stat through the links on every index read — on a stalled drive, the + hang U1 left for `out`. Left with U1's note. +- **A dangling `clips/` still reads as "nothing cut" to `deliverStateOf`'s counts** (the panel's + `need-cut` and per-section numbers); the panel now says why beside them and every write refuses. +- **The app moves home without a media root of its own** (as in the e2e): the media copy is left in + place and named, as U1's L3 rule says. The real app runs with `UMTOOL_MEDIA_DIR` set, where the + project's own mirror is removed. +- **U1's two items for this slice** are done: `listBatches`/`sharedIdsIn` follow a `share-*` link; + `umtool check` reports a dangling `out/` (and `clips/`, `share-*`). + +#### Deviations from the plan + +- The plan's files `report-to-video/{cut,deliver,check}.mjs` are `lib/report/cut.mjs`, + `lib/report/deliver.mjs` and `bin/umtool.mjs`'s `check` (through `lib/projects/report.mjs`'s + `reportDecisions`, so `umtool check` and the decisions inbox say the same). +- The movers' loop and the switch are one function in `storage.mjs` (`moveDeliverables`) taking the + writer as `writeMode`, so `storage.mjs` — imported by the app's routes — does not import the + manifest writer's dependencies. +- The bench move is the deliver route's fifth action and a job, not a route of its own: the job + registry is what keeps a cut or a batch from running under it. +- `lib/media.ts` (the mix picker) and `driver.mjs` (the step builder) are touched beyond the owned + list, one function each. +- A FACTS section was added ("umtool's deliverables switch"); no existing fact changed. + +`[Unreleased]` (`editor/CHANGELOG.md`): "umtool can move a report's cut clips and share batches to +the media drive, one project at a time." + ### Slice XP, as shipped — X posts are private (2026-10-01) Branch `r17/x-posts-private` off `main` `90bd8384`, worktree `~/Projects/r13-lows-export` (editor 5501,