Repository navigation
refactor: named seams — one download path, cli-core atomic writes and lock, program.ts split - #141
Merged
Merged
Conversation
The transcript reader and the recording transfer each checked the Zoom download host and followed manual redirects on their own. Both now go through ZoomRemote.download, which checks every hop and returns the final response; status and body checks stay with each caller. The error words, codes and cancellation handling of each path are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Five hand-written temp-file-and-rename writes now call writeFileAtomic: the archive manifest, the verified-size cache, the schedule job file, the systemd timer units and the sync checkpoint. Each keeps its mode (0600), its overwrite rule and its last-moment check (beforePublish). The sync checkpoint's own lock (a .lock directory with owner.json and a .recovery directory) is replaced by acquireLock at a new path, <checkpoint>.locks, so a lock left by an older zm is not read as an unknown holder. Contention still answers configuration_error with the same words. Strictly safer: - the verified cache is now synced, and its folder too; - a checkpoint temp file is now removed when the lease check fails; - the checkpoint folder sync refuses a non-ordinary folder (the save resolves a symlinked archive folder first, as before it followed it). Changed: - two processes that ask for the checkpoint lock at the same moment can both be refused (never both granted); a retry succeeds. The archive writer lock already behaved this way. - an empty lock folder no longer blocks; a marker that cannot be read still does. output-file.ts stays hand-written: it reports a failure when cleanup after publishing fails, which its tests pin and writeFileAtomic does not do. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The store itself was already opened in one place (createStoreConnector). What was written twice was the step before it: the command path and the MCP path each checked the selected profile, built the connect request and phrased the same error. connectSelectedProfile does that once. The connection types move from program.ts to store-connection.ts, the module whose contract they are, which also removes its type import back into program.ts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
recording-archive.ts (560 lines on main) held two jobs: transferring recordings into an archive, and reading, writing and verifying the archive's manifests and verified-size cache. The second moves to recording-manifest.ts with inspectArchive and verifyArchive; the transfer stays. 314 and 214 lines. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
program.ts was 977 lines on main: the command tree and per-run state, plus five jobs wired inline. Each moves beside its commands, keeping zoom's flat *-commands.ts layout: - watch-service.ts: watch planning and one ingestion cycle; - setup-commands.ts: config, doctor, session status and login; - store-commands.ts: the commands over the shared store; - meeting-source.ts: the API or folder source the config names; - recording-schedule-setup.ts: saved jobs, systemd timers, credentials; - answer-policy.ts gains failure rendering (reportFailure). Per-run state that a parse resets stays in createProgram or behind a reset() the parse calls. Registration order, and so `commands --json`, is unchanged. program.ts is now 476 lines; the per-file cap drops from 1,000 to 500 lines, with the installed-package acceptance scripts exempt like the other tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
acquireLock creates its marker folder with the default mode (0775 under a usual umask). The old checkpoint lock folder was 0700; create <checkpoint>.locks private before taking the lock and set the mode again once held, so a folder another holder recreated is private too. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
leemour
force-pushed
the
refactor/named-seams
branch
from
October 17, 2026 00:33
6bbe60d to
94cd707
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Step 7 of the architecture re-review (2026-10-14), zoom's items. Each claim was checked against
origin/mainat9062f17before acting; the review is three days older than that.1. Store opening — claim false as stated, one small duplicate fixed
createStoreConnectorinsrc/store-connection.ts). There were no eight copies. What was written twice was the step before it:program.tsandbin/main.ts(the MCP path) each checked the selected profile, built the{ profile, externalId, intent }request and phrased the same error.connectSelectedProfileinstore-connection.tsdoes that once.StoreConnectionandStoreConnectRequestmove fromprogram.tstostore-connection.ts(the contract they describe);index.tsstill exports both. The MCP path still reads the profile once;program.tsstill wraps non-CliErrorfailures.2. Hand-written atomic writes and lock — claim true
.lock/owner.jsonwith a.lock.recoverydirectory).writeFileAtomic, each keeping mode 0600, its overwrite rule (overwrite: falsefor units,!!existingfor schedule jobs) and its last-moment check inbeforePublish(the schedule file's inode/size/mtime check, the checkpoint lease check, cancellation).signalis never passed, so cancellation still raises zoom's owntimeout/cancellederrors.acquireLockat a new path<checkpoint>.locks, so an old.lock/owner.jsonis not read as an unknown holder. Contention still answersconfiguration_errorwith the same words; release twice still refuses.src/output-file.ts. It reports a failure when cleanup after publishing fails (its tests pin that);writeFileAtomicswallows that cleanup error.Strictly safer:
<checkpoint>.locksstays 0700 like the old.lock(created private beforeacquireLock, mode set again once held; tested);Not strictly the same — please review:
.lockdoes not exclude the new.locks(no users yet).3. Download paths — claim true in substance, wrong place named
ZoomRemoteitself had no download; the host check and the manual-redirect loop were written twice, inapi.ts(transcript) andrecording-archive.ts(recording).ZoomRemote.download(url, { accessToken, signal, what, headers, unreachable }): checks the host on every hop, follows at most 5 redirects, cancels redirect bodies, returns the final response. Status and body checks stay with each caller. The transcript path keepsprovider_errorand no cancellation check on its private 30 s deadline; the recording path keepsnetwork_errorand its cancellation check — through theunreachablecallback.4. Large files — split
src/program.tssrc/recording-archive.tssrc/recording-manifest.ts(new)src/watch-service.ts(new)src/store-commands.ts(new)src/setup-commands.ts(new)src/recording-schedule-setup.ts(new)src/meeting-source.ts(new)src/answer-policy.tsprogram.tskeeps the command tree, output and per-run state; per-run state a parse resets stays there or behind areset()the parse calls. Command registration order is unchanged.*-commands.tsfiles, not drive'ssrc/commands/— moving every existing group would be a much larger diff for no seam gained.src/mcp-server.ts(476) stays: it is one job (the MCP transport and its tools) and under 500.biome.json: per-file cap 1,000 → 500. The installed-package acceptance scripts (scripts/installed-*.mjs, the largest 710 lines) join the tests that are exempt.watch-service.ts; the manifest code isrecording-manifest.ts; the lock is now cli-core's.Contract
commands --json: captured from main and from this branch with~/.cache/step7/capture.mjs; diff withoutrun_id/duration_msis empty.tools/listofzm serve --mcp(plain, and with--unified-search --meeting-context --meeting-preparation) in a throwaway config: identical (8 / 14 tools). A--semanticrun listed the same 8 as plain, so it did not exercise a semantic tool.Tests changed
recording-sync-store.test.ts: lock tests plant cli-core markers (<pid>-<nonce>.jsonin.locks) instead of.lock/owner.json; the.lock.recoverycase becomes "a live holder refuses"; the simultaneous-acquire test asserts at most one winner, then a retry wins; new test for a symlinked archive folder (it fails with ENOTDIR without therealpath).recording-schedule-store-race.test.ts: the injected race now matches the helper's temp name (.tmpsuffix) instead of.zoom-schedule-; it still asserts the race happened.Not done: effects beside each command
command-effects.tsstill decides effects centrally: some commands are mounted by cli-core, and the list wins over per-command annotations. It already disagrees with them (watchisFETCHin the list,LOCAL_WRITEinwatch-commands.ts; same forrecordings schedule run), so moving effects would change whatcommands --jsonreports. Left as debt for its own change.Checks (local)
The PR runs only
changelog / guardandstandards; "Full validation" (ci.yml) isworkflow_calland did not run here. Run locally:typecheck, lint, docs:lint, docs:check, standards:check, contracts:check, contracts:upgrade, security:no-leak, generate:check, lint:commands, full tests with coverage, test:matrix:check.
🤖 Generated with Claude Code