omp-conductor 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@ tiers: first to an orchestrator session that can re-brief the worker, then to yo
8
8
 
9
9
  You label an issue. Within one tick the conductor claims it on the tracker, cuts a
10
10
  worktree, hands one omp worker a self-contained brief, and watches it to a green
11
- PR. The worker then stops: it never merges, tags, publishes or deploys.
11
+ PR. The worker then stops: it does not merge, tag, publish or deploy.
12
12
 
13
13
  ### Scope
14
14
 
@@ -2028,7 +2028,7 @@ dispatcher. The brief is explicit about the boundary:
2028
2028
  | Add or update tests for behaviour it introduced. | Suppress a warning, delete an assertion, or special-case an input to make a check pass. |
2029
2029
  | Run the repo's configured cheap gates, each from its listed `cwd`, over the whole tree. | Run docker or image builds, production builds, browser/e2e suites, or the full test suite on the shared host — CI owns the heavy gates. |
2030
2030
  | Review its whole diff, then commit and publish once with `conductor_push`. One corrective push if CI is red. | Force-push, `git add -f`, or add AI/co-author attribution. There is no force path to reach: `conductor_push` publishes that run's branch fast-forward only and takes no other ref. Red twice means stop and report, not push a third time. |
2031
- | Open a PR with `conductor_pr_create`, and poll CI to a verdict with `conductor_pr_status`. | Run `gh pr merge` — or reach `conductor_pr_merge`, which refuses a worker session mechanically. **A worker never merges** absolute, whoever else holds the authority so PRs land one at a time with a freshness re-check; two workers merging concurrently is how agent PRs clobber each other. Who *may* merge is the [`authority`](#configuration) answer, and it is never the worker. |
2031
+ | Open a PR with `conductor_pr_create`, and poll CI to a verdict with `conductor_pr_status`. | Run `gh pr merge` — or reach `conductor_pr_merge`, which refuses a worker session mechanically. **A worker is never authorised to merge**, whoever else holds the authority, so PRs land one at a time with a freshness re-check; two workers merging concurrently is how agent PRs clobber each other. Who *may* merge is the [`authority`](#configuration) answer, and it is never the worker. The verb refusal is mechanical; reaching for `gh` instead is a prohibition, and one that shows up as a merge absent from the [ledger](#the-ledger). |
2032
2032
  | Escalate: ambiguity, a cross-repo contract, a needed credential, a product or data-migration decision, a blocking existing test, CI red twice, or most of the wall-clock budget burned. | Cut a release, push a tag, publish to npm, edit a deployment pin, deploy, or touch infrastructure or secrets. `conductor_release` and `conductor_label` refuse a worker whatever `releasePolicy` says, because the check compares the caller against the configured holder rather than ruling one value out. The in-session tripwire still blocks recognised release/deploy tool calls early and audits the attempt, but it is [defence in depth](#the-mediated-verbs-126), not the gate. |
2033
2033
 
2034
2034
  The worker ends with a seven-line evidence report (issue, PR, observed head SHA,
@@ -2150,8 +2150,9 @@ every release shape under the most permissive config there is.
2150
2150
 
2151
2151
  Identity is never an argument. `project`, `run`, `issue` and the caller's role
2152
2152
  come from **which socket the call arrived on** plus the verified peer uid, and a
2153
- request carrying any of those field names is refused outright, named. A worker
2154
- on run X asking to merge run Y's PR is not merely wrong, it is unexpressible.
2153
+ request carrying any of those field names is refused outright, named. So a worker
2154
+ on run X cannot *ask* to merge run Y's PR: on its own channel that request is
2155
+ unexpressible, and a merge is refused for a worker session on any channel.
2155
2156
 
2156
2157
  ```
2157
2158
  <state dir>/verbs/ daemon-owned, mode 0711
@@ -2294,9 +2295,12 @@ Known and deliberate in this version:
2294
2295
  than a silently dropped page. `delivered` means Telegram accepted an attempt,
2295
2296
  not that exactly one message exists. See
2296
2297
  [Report delivery](#report-delivery-the-outbox).
2297
- - **Workers stop at green PRs.** They never merge, release or deploy. Those
2298
- actions default to a human, but setup may grant either to the orchestrator;
2299
- `authority` never grants them to a worker or the dispatch daemon.
2298
+ - **Workers stop at green PRs.** They are never authorised to merge, release or
2299
+ deploy: those actions default to a human, and while setup may grant either to
2300
+ the orchestrator, `authority` never grants them to a worker or the dispatch
2301
+ daemon. The verbs refuse a worker mechanically. A session that shells out
2302
+ instead is violating its brief rather than defeating a gate — sessions inherit
2303
+ the daemon's credentials — and the missing ledger entry is the evidence.
2300
2304
  - **The worker gate is partial, and the orchestrator has none.** A worker's
2301
2305
  structured `write` / `edit` / `read` / `grep` / `glob` calls are gated to its
2302
2306
  worktree by an inline harness extension; `bash` is not, so a shell one-liner
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omp-conductor",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "A 24/7 dispatcher that takes ready GitHub issues to green, mergeable PRs using omp coding sessions, with tiered escalation first to an orchestrator session and then to a human.",
package/src/config.ts CHANGED
@@ -816,7 +816,7 @@ function normalizeRepos(parsed: unknown, label: string, problems: string[]): Rec
816
816
  const credentialInUrl = cloneUrlCredentialProblem(cloneUrl);
817
817
  if (credentialInUrl !== undefined) {
818
818
  problems.push(
819
- `${label}: routing.repos.${key}.cloneUrl ${credentialInUrl} (#125). Use an SSH URL, or an https URL ` +
819
+ `${label}: routing.repos.${key}.cloneUrl ${credentialInUrl}. Use an SSH URL, or an https URL ` +
820
820
  `backed by the daemon's own credential helper.`,
821
821
  );
822
822
  continue;
package/src/daemon.ts CHANGED
@@ -140,7 +140,9 @@ interface Deps {
140
140
  * Reads the connecting uid off a verb socket (#126). Resolved once at startup
141
141
  * so the mechanism is logged before the first socket exists; absent on a host
142
142
  * exposing no peer-credential call, where the `0600` socket under the
143
- * daemon-owned `0711` parent is the whole boundary and `status` says so.
143
+ * daemon-owned `0711` parent is all that keeps other local accounts out, and
144
+ * `status` says so. It never told two sessions apart — they share the daemon's
145
+ * uid.
144
146
  */
145
147
  verbPeerReader?: PeerReader;
146
148
  /**
@@ -564,7 +566,7 @@ export async function settleWorktree(
564
566
  branch: string;
565
567
  /**
566
568
  * Publishes the run branch on the privileged side. Required rather than
567
- * optional: since #125 the run's commits live in a repository of its own,
569
+ * optional: the run's commits live in a repository of its own,
568
570
  * so a removal that did not publish first would delete the only copy —
569
571
  * which is #121's data loss with one extra step. `undefined` is a visible
570
572
  * decision at the call site, never an omission.
package/src/gitops.ts CHANGED
@@ -1,17 +1,20 @@
1
1
  /**
2
2
  * The daemon's own git and `gh` operations.
3
3
  *
4
- * Everything here runs as the daemon, with the operator's credentials, and it
5
- * is the only place in this package that spawns a credentialed process. That is
6
- * the whole organising idea: "what can reach the operator's `gh` credential" is
7
- * answered by this file's call sites rather than by auditing every `Bun.spawn`
8
- * in the tree.
4
+ * Everything here runs as the daemon, with the operator's credentials, and it is
5
+ * the only place this package **constructs** credential material for a git or
6
+ * `gh` call of its own. That is the organising idea: "what does the conductor
7
+ * itself do with the operator's credential" is answered by this file's call sites
8
+ * rather than by auditing every `Bun.spawn` in the tree.
9
9
  *
10
- * Sessions are not gated at the OS level a worker is a child process of the
11
- * daemon and inherits its environment. What keeps a run's publication honest is
12
- * that the dispatcher performs it: a push or a pull request the daemon did not
13
- * make is a run it cannot account for. The mediated verbs route a worker's
14
- * intent through here so the settlement record and the branch cannot disagree.
10
+ * It is emphatically **not** a claim that nothing else can reach that credential.
11
+ * Sessions are child processes of the daemon and inherit its environment, so a
12
+ * session can reach whatever the daemon can `omp.ts` spawns them and does not
13
+ * scrub anything. What keeps a run's publication accountable is that the
14
+ * dispatcher performs it: a push or a pull request the daemon did not make is a
15
+ * run it cannot account for, and is absent from the verb ledger. The mediated
16
+ * verbs route a worker's intent through here so the settlement record and the
17
+ * branch cannot disagree.
15
18
  */
16
19
 
17
20
  import { join } from "node:path";
package/src/omp.ts CHANGED
@@ -111,12 +111,13 @@ const disposers = new WeakMap<AgentSessionLike, () => Promise<void>>();
111
111
  * Start one omp coding session **in this process**, rooted at `cwd`, with a
112
112
  * private agent registry and a file-backed transcript of its own.
113
113
  *
114
- * This is the far side of the boundary, not the dispatcher's entry point.
115
114
  * Production callers want {@link createSession}, which runs this same function
116
- * inside a child process under the run's own OS principal (#125); the only
117
- * caller of this one is `session-host.ts`, plus the tests that pin what it
118
- * builds. Calling it directly from the daemon puts the session back in the
119
- * credential-holding process, which is the thing #125 exists to stop.
115
+ * inside a **child process**; the only caller of this one is `session-host.ts`,
116
+ * plus the tests that pin what it builds. Calling it directly from the daemon
117
+ * puts the harness back inside the dispatcher, where a crash, a hang or an
118
+ * exhausted heap takes the whole fleet down with it and no `MemoryMax` bounds
119
+ * it — that is what the child process buys. It is not a security boundary: the
120
+ * child runs as the daemon's own user.
120
121
  *
121
122
  * `sessionDir` chooses the directory the harness writes that transcript into;
122
123
  * omitted, the harness picks its default location for `cwd`. Either way the
@@ -84,13 +84,7 @@ export interface OrchestratorOpts {
84
84
  model?: string;
85
85
  releaseGrants?: ResolvedGrants;
86
86
  onReleaseBlocked?: (shape: ReleaseShape) => void;
87
- /**
88
- * The OS principal this session runs as (#125). Its own, distinct from every
89
- * worker slot: the orchestrator reads the state directory and its briefs, and
90
- * must have no read or write access to any run checkout — a property the
91
- * adversarial probe asserts rather than assumes.
92
- */
93
- /** Control socket for the session child. See {@link OrchestratorOpts.boundary}. */
87
+ /** Control socket for the session child, beside its own working directory. */
94
88
  socketPath?: string;
95
89
  /**
96
90
  * The orchestrator's own verb socket (#126) — a third, distinct one, never
@@ -8,11 +8,12 @@
8
8
  * It was the only mechanism there was, and it was never the one anybody should
9
9
  * have had to rely on.
10
10
  *
11
- * What actually stops a release now is that the session cannot perform one. It
12
- * holds no credential (#125) and its only route to a mutation is the mediated
13
- * verbs, whose checks run in the daemon against the configured holder
14
- * (`verbs/server.ts`). A worker that talks its way past every regex below still
15
- * has nothing to push with.
11
+ * What actually stops a release now is that the daemon performs every one, and
12
+ * checks it first: the mediated verbs run their checks against the configured
13
+ * holder (`verbs/server.ts`), on the daemon's side of the socket. A worker that
14
+ * talks its way past every regex below has still not cut a release — and if it
15
+ * reaches for `gh` directly instead, the release is not in the ledger, which is
16
+ * the discrepancy an operator reads.
16
17
  *
17
18
  * It stays installed anyway, for the two things a tripwire is good at and a
18
19
  * boundary is not: it refuses *early*, in the session, with an explanation the
@@ -5,13 +5,13 @@
5
5
  * daemon never handles a token itself: credentials stay in the user's keychain
6
6
  * or `gh` config and are never passed as argv, written to a file, or logged.
7
7
  *
8
- * That reachability is exactly what #125 confines. The environment this adapter
9
- * hands `gh` comes from `credentialedEnv()` the one named construction site
10
- * of credential material in the daemon so "which processes can reach the
11
- * operator's GitHub write credential" is answered by that function's call
12
- * sites, not by auditing every spawn in the tree. Sessions get the opposite
13
- * environment, and on a `per-run` host they are a different OS principal
14
- * entirely and could not use this credential even if they found it.
8
+ * The environment this adapter hands `gh` comes from `credentialedEnv()` the
9
+ * one named construction site of credential material in the daemon so "what in
10
+ * this package reaches the operator's GitHub credential" is answered by that
11
+ * function's call sites rather than by auditing every spawn in the tree. It is a
12
+ * legibility property, not a boundary: sessions run as the daemon's own user and
13
+ * can reach the same credential. What keeps a run's mutations accountable is
14
+ * that the daemon performs them, and records them.
15
15
  *
16
16
  * ponytail: shelling out to `gh` is the deliberate simplification. The ceiling
17
17
  * is per-call cost (one process spawn plus one TLS handshake per operation,
@@ -151,10 +151,9 @@ async function gh(argv: string[], stdin?: string): Promise<string> {
151
151
  stdin: new Blob([stdin ?? ""]),
152
152
  stdout: "pipe",
153
153
  stderr: "pipe",
154
- // The privileged side of the boundary (#125). Explicit rather than
155
- // inherited so that adding a credential-scrubbing default to this process
156
- // could never silently break the tracker, and so that the grep for
157
- // `credentialedEnv` finds this call.
154
+ // Explicit rather than inherited, so that a future change to this process's
155
+ // own environment could never silently break the tracker, and so that the
156
+ // grep for `credentialedEnv` finds this call.
158
157
  env: credentialedEnv(),
159
158
  });
160
159
 
package/src/types.ts CHANGED
@@ -1324,9 +1324,9 @@ export const VERB_NAMES = [
1324
1324
  "conductor_pr_merge",
1325
1325
  "conductor_label",
1326
1326
  "conductor_release",
1327
- /** The one read verb. A session with no credential cannot otherwise observe
1328
- * the pull request it just pushed, and "pushed-green" would go back to being
1329
- * a claim a worker makes about itself. */
1327
+ /** The one read verb. It answers with the merge gate's own verdict, so
1328
+ * "pushed-green" is the dispatcher's reading of the PR rather than a claim the
1329
+ * worker makes about itself from whatever it happened to run. */
1330
1330
  "conductor_pr_status",
1331
1331
  ] as const;
1332
1332
 
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * The privileged half of the verbs, wired to real commands (#126).
3
3
  *
4
- * This is the *only* module in the verb path that touches a credential, and it
5
- * runs in the daemon. Push and pull-request creation are delegated to the
6
- * credential boundary (#125) rather than re-shelled here, so there is one
7
- * implementation of "how does a run's work reach the remote" and it is the one
8
- * that scrubs the environment. The remaining verbs shell `gh` through
9
- * {@link credentialedEnv} for the same reason.
4
+ * This is the only module in the verb path that performs a mutation, and it runs
5
+ * in the daemon. Push and pull-request creation are delegated to `gitops.ts`
6
+ * rather than re-shelled here, so there is one implementation of "how does a
7
+ * run's work reach the remote" fast-forward only, no force path. The remaining
8
+ * verbs shell `gh` through {@link credentialedEnv} for the same reason: one
9
+ * named construction site, so its call sites are the audit.
10
10
  *
11
11
  * Kept apart from `server.ts` deliberately: every policy check above it is
12
12
  * testable against the {@link VerbActions} interface with no repository, no
@@ -23,8 +23,8 @@ import type { ActionOutcome, ReleaseExecution, VerbActions } from "./server.ts";
23
23
  *
24
24
  * The three GitHub-shaped ones, and deliberately not `package-publish` or
25
25
  * `deploy`: the daemon holds a GitHub credential and nothing else — no npm
26
- * token, no registry login, no deploy key — and #125 exists to keep it that
27
- * way. A shape outside this list is refused by name in `server.ts` rather than
26
+ * token, no registry login, no deploy key — and that is deliberate. A shape
27
+ * outside this list is refused by name in `server.ts` rather than
28
28
  * attempted and reported as a command failure, because "we tried and it did not
29
29
  * work" and "we were never able to do this" are different answers and only one
30
30
  * of them tells an operator to go and do it themselves.
@@ -87,10 +87,10 @@ export function githubVerbActions(project: ProjectConfig, run: CommandRunner = s
87
87
  return {
88
88
  releasableShapes: GITHUB_RELEASABLE_SHAPES,
89
89
 
90
- // Both of these are #125's, not this module's: they know the run's own
91
- // repository layout and they are the two paths that must never see an
92
- // ambient credential. Re-shelling `git push` here would be a second
93
- // implementation of the one thing the credential boundary exists to own.
90
+ // Both live in `gitops.ts`, not here: they know the run's own repository
91
+ // layout, and they are the two paths whose fast-forward-only rule must have
92
+ // exactly one implementation. Re-shelling `git push` here would be a second
93
+ // one, and the second one is where a `--force` eventually appears.
94
94
  push: (target) => pushRunBranch(project, target),
95
95
 
96
96
  createPr: (target, opts) => openRunPr(project, target, opts),
@@ -2,10 +2,9 @@
2
2
  * The child-side half of the mutation verbs (#126): a thin client, and nothing
3
3
  * else.
4
4
  *
5
- * This module runs **inside the untrusted per-run process**. Under #125 that is
6
- * a different OS principal with no access to the daemon's `gh` config, keychain
7
- * or SSH keys; before #125 it is the same user. Either way it is code the model
8
- * can read, and on a bad day rewrite — so it decides nothing:
5
+ * This module runs **inside the session process**, as the daemon's own user, so
6
+ * it is code the model can read and on a bad day rewrite — and it can reach the
7
+ * same credentials the daemon can. That is exactly why it decides nothing:
9
8
  *
10
9
  * - **No policy branch.** It does not know what `authority.merge` says, which
11
10
  * release shapes are granted, or whether the fleet is paused. Every answer
@@ -15,8 +14,8 @@
15
14
  * There is nothing here to steal.
16
15
  * - **No local fallback.** No socket means the verb fails, loudly, with an
17
16
  * explanation. A client that fell back to `git push` when the daemon was
18
- * unreachable would make the boundary optional, and an optional boundary is
19
- * one that is missing precisely when something has gone wrong.
17
+ * unreachable would leave the settlement record silently incomplete, and it
18
+ * would do so precisely when something had already gone wrong.
20
19
  *
21
20
  * The identity fields are absent by construction rather than stripped: this
22
21
  * client has no project, run or role to send, because it was never told one.
@@ -2,10 +2,10 @@
2
2
  * The privileged half of the mutation verbs (#126): every check, in the daemon.
3
3
  *
4
4
  * The child-side tool handler forwards arguments and renders an answer. It
5
- * holds no credential, evaluates no policy and writes no ledger entry, because
6
- * under #125 it runs inside the untrusted per-run process anything it decided
7
- * would be decided by code the model can rewrite. Everything below runs on the
8
- * other side of the socket.
5
+ * evaluates no policy and writes no ledger entry, because it runs inside the
6
+ * session process — anything it decided would be decided by code the model can
7
+ * rewrite, and a session runs as the daemon's own user. Everything below runs on
8
+ * the other side of the socket, which is what makes the checks real.
9
9
  *
10
10
  * Four rules shape the order of the checks, and each is here because prose
11
11
  * alone did not hold it on `main`:
@@ -819,7 +819,7 @@ async function releaseVerb(
819
819
  return refuse(
820
820
  "release-shape-not-executable",
821
821
  `refused: this daemon cannot cut a ${shape}. It holds a GitHub credential and nothing else — no npm token, ` +
822
- "no deploy key — and #125 exists to keep it that way. It can do " +
822
+ "no deploy key — and that is deliberate. It can do " +
823
823
  `${deps.actions.releasableShapes.join(", ")}. Escalate this one to your operator.`,
824
824
  );
825
825
  }
@@ -843,8 +843,8 @@ async function releaseVerb(
843
843
 
844
844
 
845
845
  /**
846
- * The one read verb, and the reason a credential-less worker can still report
847
- * honestly (#125 took `gh pr view` away from it).
846
+ * The one read verb, and the reason a worker reports a PR's real state rather
847
+ * than its own recollection.
848
848
  *
849
849
  * Reuses `Tracker.verifyPr`, so the answer is literally the merge gate's own
850
850
  * verdict rather than a second opinion assembled here — including the
@@ -361,14 +361,14 @@ export function peerVerdict(
361
361
  return {
362
362
  ok: true,
363
363
  basis: "socket-ownership",
364
- why: "no uid was named for this socket, so the 0600 mode under the daemon-owned 0711 parent is the whole boundary",
364
+ why: "no uid was named for this socket, so the 0600 mode under the daemon-owned 0711 parent is all that keeps other local accounts out",
365
365
  };
366
366
  }
367
367
  if (peer === undefined) {
368
368
  return {
369
369
  ok: true,
370
370
  basis: "socket-ownership",
371
- why: "this host exposes no peer credentials, so the 0600 socket under the daemon-owned 0711 parent is the whole boundary",
371
+ why: "this host exposes no peer-credential call, so the 0600 socket under the daemon-owned 0711 parent is all that keeps other local accounts out",
372
372
  };
373
373
  }
374
374
  if (peer.uid !== expected.uid) {
package/src/worktree.ts CHANGED
@@ -6,11 +6,12 @@
6
6
  * once and refreshed, rather than a fresh full clone per issue: a module repo's
7
7
  * history is fetched one time and every later run pays only for the delta.
8
8
  *
9
- * **A run gets its own repository, not a linked worktree of the mirror, and
10
- * that is a security property rather than a preference (#125 1b).** A linked
11
- * worktree shares the mirror's common dir — refs, objects, index locks — so it
12
- * needs *write* access to it, which hands every run write access to every other
13
- * run's refs. Here the objects arrive read-only through git's alternates
9
+ * **A run gets its own repository, not a linked worktree of the mirror, and that
10
+ * is load-bearing rather than a preference.** A linked worktree shares the
11
+ * mirror's common dir — refs, objects, index locks — so every run would be
12
+ * writing into one store: concurrent runs contend on the same index lock, a
13
+ * `git gc` in one can pull objects from under another, and a run's refs are one
14
+ * `git update-ref` away from a sibling's. Here the objects arrive read-only through git's alternates
14
15
  * mechanism and the run's refs, index and commits live in its own git dir. The
15
16
  * documented residual is that a run can still *read* another run's objects out
16
17
  * of the shared store: bounded, same source, no write path, no credential, and
@@ -61,9 +62,9 @@ async function runGit(
61
62
  stdin: "ignore",
62
63
  stdout: "pipe",
63
64
  stderr: "pipe",
64
- // Every git call in this module runs on the *privileged* sidethe daemon
65
- // provisioning, salvaging or publishing — so it goes through the one named
66
- // construction site of credential material (#125). It also carries
65
+ // Every git call in this module is the daemon's ownprovisioning,
66
+ // salvaging or publishing — so it goes through the one named construction
67
+ // site of credential material. It also carries
67
68
  // GIT_TERMINAL_PROMPT=0, because an unattended dispatcher must fail loudly
68
69
  // rather than block forever on a prompt nobody is there to answer.
69
70
  env: credentialedEnv(),
@@ -287,8 +288,8 @@ export async function ensureMirror(
287
288
  * git's own read-only borrowing mechanism — so provisioning still costs one
288
289
  * fetch of the delta rather than a full clone, exactly as the linked worktree
289
290
  * it replaces did. What changed is who can write what: the run's refs, index
290
- * and commits are its own, and nothing it does can reach a sibling run's refs
291
- * or the mirror (#125 1b).
291
+ * and commits are its own, and no ordinary git operation in the checkout reaches
292
+ * a sibling run's refs or the mirror.
292
293
  *
293
294
  * `origin` is deliberately the real clone URL and not the mirror path. Pointing
294
295
  * it at the mirror would make a worker's ordinary `git push origin HEAD`
@@ -553,9 +554,9 @@ function parseCachedNameStatus(raw: string): { files: string[]; newPaths: string
553
554
  * commit and re-runs its checks. That is the price of work outliving its host.
554
555
  *
555
556
  * `publish` is a **required** parameter rather than an optional one with a
556
- * no-op default, and that is the whole design. The salvage commit no longer
557
- * pushes for itself: the run's repository holds no credential (#125), so the
558
- * network hop is the daemon's, through `pushRunBranch`. A defaulted parameter
557
+ * no-op default, and that is the whole design. The salvage commit does not push
558
+ * for itself: the network hop is the daemon's, through `pushRunBranch`, so the
559
+ * sha that reaches the remote is the sha the dispatcher recorded. A defaulted parameter
559
560
  * is how a future call site silently stops publishing salvaged work and nobody
560
561
  * finds out until the host that held it is gone — which is #121's failure with
561
562
  * one extra step. Passing `undefined` is allowed, and is a visible decision at
@@ -676,7 +677,8 @@ export async function removeWorktree(
676
677
  if (existsSync(worktreePath)) {
677
678
  // A run repo is a plain directory now, so removal is a plain removal — but
678
679
  // the `worktree remove` is still attempted first, because a fleet upgrading
679
- // into #125 has live trees that ARE linked worktrees of this mirror, and
680
+ // from a release that used linked worktrees has live trees that ARE linked
681
+ // worktrees of this mirror, and
680
682
  // deleting one of those without deregistering it leaves the mirror
681
683
  // believing the branch is still checked out. It refuses `worktree add` and,
682
684
  // worse, refuses to delete the branch at cleanup. The failure is expected