Archilyzer · Source

archilyzer

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

commit 4a519d575571eb5b134b2689bd9c895e660130a0
parent 5cef97da786ba04872c24c9e8f13713231df0e3f
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu, 17 Sep 2026 14:07:30 -0400

relocate: name the early stop in the summary, and the retry in the done line

Review fixes for S1.

The auto runner comment was wrong about its own cost: the `finally` only calls
markCompleted on a BUCKET lane, so an operation lane retires nothing. It says
so now, and says what actually defends the channel — GUARD 2 in
buildChannelWork drops an in-transition channel from the next tick, which
leaves this guard covering the pick-to-run window alone.

An early stop that only appears mid-log reads as a quiet, complete run in the
summary line, which is the line an operator actually reads.
`stoppedForRelocation` rides beside `spendCapped` and both lane summaries
append it, the same way the cap is consumed.

And the verify retry now reaches the operator too: the relocation job says a
directory timestamp was settled by a second pass, because the person reading
that line has watched this exact drift refuse a 131 GB move.

Plus two tidies: one copy of the verify paragraph rather than two, and `||=`
in moveBack to match moveOut.

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

Diffstat:
Mcommon/controller/autoRunner.ts | 20++++++++++++++------
Mcommon/controller/operationBatch.ts | 6++++++
Mcommon/controller/operationBatchRelocation.test.ts | 4++++
Mcommon/controller/operationJobs.ts | 6++++++
Mcommon/controller/relocateChannelMedia.ts | 22++++++++++------------
Meditor/app/channels/lib/relocationJob.ts | 8+++++++-
6 files changed, 47 insertions(+), 19 deletions(-)

diff --git a/common/controller/autoRunner.ts b/common/controller/autoRunner.ts @@ -1656,12 +1656,20 @@ async function runLoop( // left the verify refusing a 131 GB transfer. A marker is the signal, the // same one every other guard already honours. // - // TRADE-OFF, ACCEPTED: the `finally` below marks the video completed for - // the session, so this unit is not offered again until the runner - // restarts or the snapshot regenerates. The window is one unit per - // channel per session, and the relocation finishing re-enables the - // channel on the next sweep — which is a far cheaper loss than a copy - // refused at its last step. + // THIS IS THE BACKSTOP, NOT THE DEFENCE. GUARD 2 in buildChannelWork + // already drops the channel from the NEXT tick's pending list — any + // marker makes inspectChannelMedia report `in-transition`, which is + // neither `ok` nor `in-place` — so the only window this closes is + // pick→run, and it is measured in milliseconds. + // + // WHAT THE `finally` DOES WITH IT DIFFERS BY LANE, and neither answer + // costs anything. On a BUCKET lane it calls markCompleted, so this video + // is retired for the session and not offered again until the runner + // restarts or the snapshot regenerates: one unit per channel, accepted, + // and far cheaper than a copy refused at its last step. On an OPERATION + // lane it retires nothing — runOperationPick owns the (operation, video) + // keys and never ran — which is equally fine, because guard 2 has taken + // the channel off the list before the next tick could offer it again. const marker = await readRelocationMarker(paths, channelSlug); if (marker) { onLog( diff --git a/common/controller/operationBatch.ts b/common/controller/operationBatch.ts @@ -1517,6 +1517,10 @@ export type OperationBatchResult = { warnings: number; // True when the run stopped early because the spend cap was reached. spendCapped: boolean; + // True when the run ended early because a relocation of this channel's media + // started while it was running. Not a failure: the work that is left is still + // there, and the next sweep picks it up once the move is done. + stoppedForRelocation: boolean; }; export function emptyOperationBatchResult(): OperationBatchResult { @@ -1540,6 +1544,7 @@ export function emptyOperationBatchResult(): OperationBatchResult { costUsd: 0, warnings: 0, spendCapped: false, + stoppedForRelocation: false, }; } @@ -1722,6 +1727,7 @@ export async function runOperationBatch( `to ${marker.target} is in flight — the rest of this batch is left ` + `for the next pass.`, ); + result.stoppedForRelocation = true; return null; } // RE-DERIVED FROM DISK, every pull. A restart, a concurrent lane, or a diff --git a/common/controller/operationBatchRelocation.test.ts b/common/controller/operationBatchRelocation.test.ts @@ -86,6 +86,7 @@ test("every candidate is classified when no relocation is in flight", async () = // The control for the case below: both videos are reached and counted. assert.equal(result.missingInput, 2); assert.equal(result.attempted, 0); + assert.equal(result.stoppedForRelocation, false); assert.ok(!lines.some((l) => l.includes("Stopping: a relocation"))); }); @@ -126,6 +127,9 @@ test("a marker written mid-run ends the batch instead of racing the copy", async // than working its way through the rest of the channel while rsync copied it. assert.equal(result.missingInput, 1); assert.equal(result.attempted, 0); + // The flag is how the job's summary line says why it stopped — an early end + // that the counters alone would render as a quiet, complete run. + assert.equal(result.stoppedForRelocation, true); const stop = lines.find((l) => l.includes("Stopping: a relocation")); assert.ok(stop, `expected a stop line, got:\n${lines.join("\n")}`); assert.match(stop as string, /\(out\) of omni to \/platter\/omni\/data/); diff --git a/common/controller/operationJobs.ts b/common/controller/operationJobs.ts @@ -144,6 +144,9 @@ export async function runBackfillChannelJob( : "") : "") + (batch.diskFloorHit ? " (stopped re-acquiring at the disk floor)" : "") + + (batch.stoppedForRelocation + ? " (stopped: a relocation is in flight)" + : "") + ".", ); requestChannelSnapshot(paths, channelSlug); @@ -266,6 +269,9 @@ export async function runDigestChannelJob( `${batch.engineCalls} model call(s), ${batch.warnings} warning(s)` + (batch.costUsd > 0 ? `, $${batch.costUsd.toFixed(4)}` : "") + (batch.spendCapped ? " (stopped at the spend cap)" : "") + + (batch.stoppedForRelocation + ? " (stopped: a relocation is in flight)" + : "") + ".", ); // ONCE, at job end — the digest kinds are in NO_REGEN_KINDS precisely so diff --git a/common/controller/relocateChannelMedia.ts b/common/controller/relocateChannelMedia.ts @@ -61,7 +61,9 @@ export type RelocateChannelMediaResult = { // starting from scratch. resumed: boolean; // True when the verify found ONLY directory-mtime drift and one extra - // `rsync -a` pass settled it. Content drift is still a refusal, never this. + // `rsync -a` pass settled it — worth saying on the job's done line, because + // an operator who has seen this refuse a copy should be told it did not this + // time. Content drift is still a refusal, never this. retried: boolean; }; @@ -333,10 +335,6 @@ async function rsyncTree(opts: { return { exitCode: result.exitCode ?? 1, output }; } -// What a copy has to clear before the swap: rsync itself agrees there is -// nothing left to send, AND the two trees measure the same. The dry run alone -// would accept a target that is byte-identical for the wrong reason; the counts -// alone would accept two trees of equal size with different contents. // A DIRECTORY MTIME IS NOT CONTENT. `.d..t` is rsync's itemization for "this is // a directory and only its modification time differs" — nothing to send, and no // byte of the copy is in question. It is what the omnimirror move hit @@ -682,9 +680,9 @@ async function moveOut(args: { if (exitCode !== 0) throw new Error(`rsync failed (exit ${exitCode})`); log("Verifying the copy…"); - verifyRetried = - (await verifyCopy({ paths, src: dataDir, dest: target, log, signal })) - .retried || verifyRetried; + verifyRetried ||= ( + await verifyCopy({ paths, src: dataDir, dest: target, log, signal }) + ).retried; phase = "swap"; } @@ -712,9 +710,9 @@ async function moveOut(args: { state.kind === "real-dir" ? dataDir : (parked[0] ?? null); if (verifySrc) { log("Verifying the copy…"); - verifyRetried = - (await verifyCopy({ paths, src: verifySrc, dest: target, log, signal })) - .retried || verifyRetried; + verifyRetried ||= ( + await verifyCopy({ paths, src: verifySrc, dest: target, log, signal }) + ).retried; } if (state.kind === "real-dir") { @@ -895,7 +893,7 @@ async function moveBack(args: { } if (exitCode !== 0) throw new Error(`rsync failed (exit ${exitCode})`); log("Verifying the copy…"); - verifyRetried = ( + verifyRetried ||= ( await verifyCopy({ paths, src: target, dest: incoming, log, signal }) ).retried; phase = "swap"; diff --git a/editor/app/channels/lib/relocationJob.ts b/editor/app/channels/lib/relocationJob.ts @@ -56,7 +56,13 @@ export async function enqueueRelocation(opts: { onLog( `${direction === "out" ? "Moved" : "Moved back"} ${result.files} file(s) / ` + `${formatBytes(result.bytes)} — ${result.target}` + - (result.resumed ? " (resumed an interrupted move)" : ""), + (result.resumed ? " (resumed an interrupted move)" : "") + + // Said out loud because the operator has watched this refuse a move: + // a directory timestamp left by a sidecar written mid-copy used to + // fail the verify with 131 GB correctly on the far side. + (result.retried + ? " (one directory timestamp settled by a second pass)" + : ""), ); // No snapshot regen — deliberately, and `relocate-channel-media` is in // NO_REGEN_KINDS so the central hook does not arm one either. The move