Archilyzer · Source

archilyzer

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

commit 129de94dcad57c0c2c16d5a84cbcf294a58e4453
parent 3d9f74bc7ae47926084bfb3867e210701ad8d797
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Tue, 22 Sep 2026 16:15:25 -0400

common: the write side is asserted on disk, and two comments name the key

The rest of S0-pause, folded in.

`lib/settingsWrite.test.ts` is the round trip a pure test cannot do:
`writeSettings` writes `getPaths().settingsFile`, which memoizes at module
scope, so the temp-dir seam has to be set before the module is imported. It
saves an object spelling all four retired fields — each the OPPOSITE of the lane
defaults — reads the file back, and asserts the four are absent and the four
`held` keys are there. A second case saves a held lane and reads it back through
`isGateHeld`, so "dropped the field" cannot pass by dropping the gate with it.
`pauseGates.test.ts`'s write-side case now says what it actually asserts: the
PARTS writeSettings is built from, not the save.

`videoOperations.test.ts` seeded `backfill: {enabled: true, weight: 1}` — two
deleted fields, which as written produced a file reading as HELD while the
literal said "running".

`operationBatchRelocation.test.ts` postdates the old branch and had the same
seed. With the gate defaulting shut it did not fail, it HUNG: the batch held and
the two tests were cancelled by the event loop. It spells the lane's gate now,
which is what the retired field was always saying.

Two comments name the key rather than the deleted field: the download idle
reason and `defaultHeldFor`'s window.

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

Diffstat:
Mcommon/controller/autoRunner.ts | 2+-
Mcommon/controller/operationBatchRelocation.test.ts | 9++++++++-
Mcommon/controller/videoOperations.test.ts | 13++++++++-----
Mcommon/jobs/autoQueuePolicy.ts | 4++--
Mcommon/lib/pauseGates.test.ts | 20++++++++++++++------
Acommon/lib/settingsWrite.test.ts | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 127 insertions(+), 15 deletions(-)

diff --git a/common/controller/autoRunner.ts b/common/controller/autoRunner.ts @@ -198,7 +198,7 @@ export type AutoRunnerIdleReason = | "disabled" // policy.snoozeUntil is in the future — idle on purpose, not stopped. | "snoozed" - // The global downloads pause (settings.downloadsPaused). Download only. + // The download lane's own gate (`autoQueue.download.held`). Download only. | "downloads-paused" // diskGate refused to start more work. Download only. | "disk-gate" diff --git a/common/controller/operationBatchRelocation.test.ts b/common/controller/operationBatchRelocation.test.ts @@ -36,7 +36,14 @@ process.env.SETTINGS_FILE = SETTINGS_FILE; writeFileSync( SETTINGS_FILE, JSON.stringify({ - backfill: { enabled: true, allowRedownload: false, concurrency: 1 }, + // THE LANE'S GATE, SPELLED. It used to be spelled by `backfill.enabled: + // true` — the inverted retired field, where `true` meant NOT held — + // and S0-pause deleted it. The backfill lane's gate DEFAULTS shut + // (`defaultHeldFor`, which is the reading that field always gave a file + // naming no gate), so a fixture that needs the batch to dispatch says so + // on the lane. Without it `runOperationBatch` holds and never resolves. + autoQueue: { backfill: { enabled: true, held: false } }, + backfill: { allowRedownload: false, concurrency: 1 }, diarization: { enabled: true, segModel: "/models/seg-1.onnx", diff --git a/common/controller/videoOperations.test.ts b/common/controller/videoOperations.test.ts @@ -235,11 +235,14 @@ test("shownOnVideoPage: off with nothing on disk hides; off with a sidecar shows // Lives here rather than in operationBatch.test.ts because countOperationWork // reads settings from disk and this file already owns the settings seam. test("countOperationWork sizes an ids-scoped run to those ids alone", async () => { - writeSettings( - settingsOn({ - backfill: { enabled: true, weight: 1, concurrency: 1 }, - }), - ); + // `enabled` and `weight` used to live on this block and are BOTH deleted — + // `weight` by slice 1.3, `enabled` (the lane's inverted pause) by S0-pause. + // Left in, they would have read as nothing at all while looking like the + // switch that makes the lane run. What arms the lane's OPERATIONS is + // `settingsOn`'s diarization/attribution above; counting never asks the + // lane's gate, so there is no `autoQueue.backfill.held` to spell here either + // (a test that did need the lane running would say exactly that). + writeSettings(settingsOn({ backfill: { concurrency: 1 } })); const idA = "vid-count-a"; const idB = "vid-count-b"; await seed(idA); diff --git a/common/jobs/autoQueuePolicy.ts b/common/jobs/autoQueuePolicy.ts @@ -675,8 +675,8 @@ function defaultOrderFor(lane: AutoQueueKind): AutoQueueOrder { // `downloadsPaused` and `digest.digestsPaused` all defaulted false (free); // `backfill.enabled` defaulted FALSE and was INVERTED, so the backfill lane has // shipped HELD since it existed. S0-pause deleted the fields, which is what -// makes defaulting this key correct — and required, because until slice 1.4 an -// absent `held` had somewhere else to ask and now it has not. +// makes defaulting this key correct — and required, because from slice 1.4 until +// S0-pause an absent `held` had somewhere else to ask, and now it has not. // // The backfill lane is therefore off twice over on a fresh install: unarmed // (`enabled: false`) and held. That is gate B — never enable the backfill lane diff --git a/common/lib/pauseGates.test.ts b/common/lib/pauseGates.test.ts @@ -97,12 +97,20 @@ test("a settings.json still spelling a retired pause field cannot hold a lane", ); }); -test("no sanitizer carries a retired pause field back onto disk", () => { - // THE DELETION, FROM THE WRITE SIDE. `writeSettings` builds its output from - // ONLY the known operational fields — `sanitizeDigest`, `sanitizeBackfill` - // and an explicit top-level literal — so a field no sanitizer names cannot - // survive a save. That is what stops a stale file resurrecting a pause on the - // next write, and it is the half a read-side test cannot see. +test("no sanitizer names a retired pause field", () => { + // THE PARTS `writeSettings` IS BUILT FROM. Its output is ONLY the known + // operational fields — `sanitizeDigest`, `sanitizeBackfill` and an explicit + // top-level literal — so a field no sanitizer names cannot survive a save. + // + // THIS IS NOT THE SAVE ITSELF, and the distinction matters: `writeSettings` + // writes `getPaths().settingsFile`, which memoizes at module scope, so a + // round trip needs an env seam this pure file deliberately does not have. + // `lib/settingsWrite.test.ts` is that round trip — it saves an object + // spelling all four retired fields into a temp dir and reads the file back — + // and the e2e "a retired pause field holds nothing, and does not survive a + // write" proves the same thing through a real editor saving a real file from + // a page. What is asserted HERE is the pieces: nothing that composes the + // output knows those names. const digest = sanitizeDigest({ ...defaultDigest(), digestsPaused: true, diff --git a/common/lib/settingsWrite.test.ts b/common/lib/settingsWrite.test.ts @@ -0,0 +1,94 @@ +// WHAT A SAVE PUTS ON DISK. +// +// Run with: node_modules/.bin/tsx --test common/lib/settingsWrite.test.ts +// +// Its own file because it needs a SETTINGS SEAM: `writeSettings` writes +// `getPaths().settingsFile`, and `getPaths()` memoizes its first answer at +// module scope — so SETTINGS_FILE has to be set before anything can import the +// module under test. Same arrangement as controller/videoOperations.test.ts, +// and the reason the module is imported dynamically below. +// +// THE CLAIM IT PINS is the write half of S0-pause: the four retired pause +// fields (`transcriptionsPaused`, `downloadsPaused`, `digest.digestsPaused`, +// the inverted `backfill.enabled`) cannot survive a save, because +// `writeSettings` builds its output from ONLY the known operational fields and +// none of them is one any more. A read-side test can say the fields are +// ignored; only this can say they are GONE, which is what stops a stale file +// resurrecting a pause an operator has lifted. + +import { mkdtempSync, readFileSync } from "node:fs"; +import { rm } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { test, after } from "node:test"; +import assert from "node:assert/strict"; + +const ROOT = mkdtempSync(path.join(os.tmpdir(), "settings-write-")); +process.env.TRANSCRIPTS_DIR = ROOT; +process.env.SETTINGS_FILE = path.join(ROOT, "settings.json"); + +// Type-only, so it is erased and cannot execute the module before the env above +// is set; the VALUES come through the dynamic import below. +import type { SiteSettings } from "./settings"; + +const { defaultSiteSettings, writeSettings } = await import("./settings"); +const { isGateHeld } = await import("./pauseGates"); + +after(() => rm(ROOT, { recursive: true, force: true })); + +type Stale = Record<string, unknown>; + +test("a save drops every retired pause field and keeps the lane's key", async () => { + // The pre-1.4 spelling, all four, each saying the OPPOSITE of the lane + // defaults: held/held/held/free against free/free/free/held. So a save that + // carried any of them through would be visible in both directions. + const base = defaultSiteSettings(); + const stale = { + ...base, + transcriptionsPaused: true, + downloadsPaused: true, + digest: { ...base.digest, digestsPaused: true }, + backfill: { ...base.backfill, enabled: true }, + } as unknown as SiteSettings; + + await writeSettings(stale); + + const onDisk = JSON.parse( + readFileSync(process.env.SETTINGS_FILE!, "utf8"), + ) as Stale; + assert.equal("transcriptionsPaused" in onDisk, false); + assert.equal("downloadsPaused" in onDisk, false); + assert.equal("digestsPaused" in (onDisk.digest as Stale), false); + assert.equal("enabled" in (onDisk.backfill as Stale), false); + + // And what DID land is the gate, one key per lane, at the lane defaults — + // so the next reader gets the same answer this object gave. + const autoQueue = onDisk.autoQueue as Record<string, { held?: boolean }>; + assert.deepEqual( + ["transcription", "download", "digest", "backfill"].map( + (lane) => autoQueue[lane]?.held, + ), + [false, false, false, true], + ); +}); + +test("a held lane survives its own round trip", async () => { + // The other direction, because "the field is dropped" would also be true of a + // save that dropped the gate with it. + const base = defaultSiteSettings(); + const held: SiteSettings = { + ...base, + autoQueue: { + ...base.autoQueue, + digest: { ...base.autoQueue.digest, held: true }, + backfill: { ...base.autoQueue.backfill, held: false }, + }, + }; + await writeSettings(held); + const onDisk = JSON.parse( + readFileSync(process.env.SETTINGS_FILE!, "utf8"), + ) as Stale; + const back = { ...base, autoQueue: onDisk.autoQueue } as SiteSettings; + assert.equal(isGateHeld(back, "digest"), true); + assert.equal(isGateHeld(back, "backfill"), false); +});