Skip to content

Agent: ship ​

The coding → review → fix loop to LGTM as one pipeline, run by the plan runner: agent:ship hands a request — a task, a plan's units, or a ship pull request to resume — to a Workflow coordinator in the bot's shim Worker, which runs each unit's pipeline as child dispatch() runs in a thread of the unit's own, gets the pull request reviewed, hands the findings back, and repeats until the review approves; on a plan branch it merges, elsewhere a person does. One dispatch hands off, one card follows the plan, one run record carries its story; every side effect (branch create, PR open/edit, review post, board comment, merge) is executed by the bot process from typed artifacts. There is one ship implementation: the runner. No round loop lives in the bot process.

  • Code: src/core/coordinator/checkRecoveryEffect.ts (native per-call checks recovery), src/core/coordinator/recoveryStep.ts (immutable recovery action execution names), src/core/publicationSettlement.ts, src/core/checkpointArtifact.ts (checkpoint publication and private preservation), src/core/publicationPush.ts (item 10: paired leased-push evidence), src/core/budgets.ts (item 8: ASKS, FLOORS, reserveMinutes, carve, fit — the table the pipeline's clocks derive from), src/core/pipelineStanding.ts (item 19: the standing fold — Stage, PipelineUnit, PipelineStanding, PipelineSummary, pipelineStandingOf, summaryOfStanding, pipelineOfEvents, isPipelineSummaryShape) and scripts/pipeline-standing-diff.ts (item 19: the shadow diff), src/core/unitRuns.ts (item 17: unitRunsOf, roundBoundaries, UnitRun, UnitRunsView, UnitFacts/unitFactsOf, InstanceFacts/instanceFactsOf), src/core/findingsLedger.ts (item 18: ledgerOf, FindingRow, FindingStatus, LedgerRun, parsePullRequestRef/PULL_REQUEST_REF_PATTERN), src/core/runsService.ts (item 17: RunsService.listUnitRuns, RunsService.listInstanceUnits; item 18: RunsService.listFindings, FindingsLedgerView), src/channels/liveView.ts (item 17: the unit route and the UnitSeed; the parent record's units seed), src/channels/adminCoordinator.ts (item 17: hostPublish — the runner's routes write the hosted parent's stream, finish seals its one record), web/green/pages/Unit.vue, web/green/pages/Unit.vue, web/src/components/run/RunUnitsBlock.vue (item 17: the unit page and the parent record's unit list), web/src/components/unit/FindingsBlock.vue (item 18: the Findings block), src/channels/liveView.ts (item 18: the unit seed's findings, the history seed's findingsLedger), src/channels/webSeed.ts (item 18: UnitSeed.view.findings, RunHistorySeed.findingsLedger), web/green/routes.ts (item 17: /runs/unit/:key), src/core/coordinator/contract.ts (item 17: UNIT_KEY_PATTERN, unitKeyOf, parseUnitKey), src/core/commands/runs.ts (item 17: runs unit; item 18: runs findings), src/core/dispatch/ship.ts (the ship branch: the preflight, the one run record and card, the hand-off to the runner, the reply; over ShipDeps), src/core/coordinator/handOff.ts (item 16: the request as a plan runner instance — the records written, the shim asked, the reply) and src/core/coordinator/instancesClient.ts (item 16: the bot's POST /admin/coordinator/instances on its own shim), src/core/dispatcher.ts (the fork into the branch; a spawned child's contract in its first user turn), src/core/runnerOwnership.ts (canonical durable owner routing; reads never reserve or authorize publication), src/core/shipPipeline.ts (the ship config block, the caps, shipPresetFor, the card's round header; the interrupted note and the caps' shape re-exported from the runner's machine), src/core/ship/ (preflight.ts — the entry checks, shipTaskText, shipUnitText, ShipEntry; codingChild.ts — the coding child's prompt block: the contract's place in the first user turn; reviewChild.ts — the review child's turn; contract.ts — the child contract, contractFromPlan, generatedUnit, unitTitleOf, renderContract, GUARDS; handoff.ts — the handoff a coding child hands back, pure: Handoff, parseHandoff, isHandoffShape, redactHandoff, renderHandoffComment, renderHandoffLedgerRows; coordinator.ts — the plan runner's state machine, pure over the coordinator's step returns: the plan graph and cursor (parsePlanGraph, openPlanCursor, readyUnits, startUnit, settleUnit, unitBranch, parsePlanBranch, parseShipPlanRequest, generatedPlanId) and the unit pipeline (openUnitPipeline, nextAction, applyReturn, renderUnitReport), shipInterruptedNote), src/core/coordinator/ (contract.ts — the instance record and the unit row, resume included; briefs.ts — a child's turn composed from a spawn's brief; driver.ts — the Workflow's body over the machine) and src/channels/adminCoordinator.ts (the bot steps the runner calls; http-ingress.md item 9), deploy/cloudflare/coordinator.ts (the ShipCoordinator Workflow), src/core/commands/contract.ts (contract render, the by-hand render), src/core/reviewRound.ts + src/core/codingPrPostStep.ts (the per-round units a child run passes through), src/core/reviewVerdict.ts (findings), src/tools/submit.ts (submit_dispositions, submit_handoff), src/agents/registry.ts (AGENTS["ship"]), src/config.ts (ship caps block), src/config/validate.ts (validateShip), src/core/pullSweep.ts (item 20: the sweep's two-rung resolver — decideSweep, createPullSweepService, PullSweepService, SweepGit, SweepEffects), src/execution/gitRebase.ts (item 20: rung one — rebaseOntoBase, patchUnchanged, forcePushWithLease, localGitRunner), src/core/commands/pulls.ts (item 20: pulls rebase), src/core/pullSweepWiring.ts (item 20: the production wiring — buildPullSweepDeps, sweepThreadKey), src/execution/sweepCheckout.ts (item 20: the throwaway checkout — createSweepGit, sweepCloneUrl, sweepAuthHeader), src/core/mergeWatch.ts (item 21: the watch — resolveMergeWatch, createMergeReadyBook, createMergeWatch, spendCapLine) and the push intake beside the check-run intake (handlePushIntake in src/core/coordinator/checksIntake.ts; sendPullMerged in src/core/coordinator/contract.ts)
  • Tests: src/core/coordinator/checkRecoveryEffect.test.ts, src/core/coordinator/recoveryStep.test.ts, src/core/publicationPush.test.ts, src/core/pipelineStanding.test.ts (item 19: the fold), src/core/unitRuns.test.ts (item 17: the cut), src/core/findingsLedger.test.ts (item 18: the join and the reference), src/core/runsService.test.ts (RunsService.listUnitRuns, its facts, listInstanceUnits; item 18: RunsService.listFindings), src/channels/liveView.test.ts (item 17: the unit route's seeds and 404), web/src/pages/unitPage.test.ts and web/src/pages/runPage.test.ts (item 17: the unit page, the parent's unit list), src/core/commands/runs.test.ts (runs unit, runs findings), src/core/coordinator/contract.test.ts (the unit key), src/core/dispatch/ship.test.ts (the branch: the preflight refusal, the hand-off, the resume, the refusals by name), src/core/dispatcher.test.ts (agent:ship (the hand-off to the plan runner): the entry checks and the hand-off through dispatch()), src/core/ship/surface.test.ts (the source scan), src/core/ship/codingChild.test.ts, src/core/ship/reviewChild.test.ts (the prompt blocks), src/core/ship/contract.test.ts, src/core/ship/handoff.test.ts, src/core/ship/coordinator.test.ts (the machine), src/core/coordinator/handOff.test.ts, src/core/coordinator/driver.test.ts, src/core/runnerOwnership.test.ts, src/core/coordinator/contract.test.ts, src/core/coordinator/briefs.test.ts, src/core/coordinator/instancesClient.test.ts, src/channels/adminCoordinator.test.ts (the runner's steps), src/tools/submit.test.ts (submit_dispositions tool: item 6's tool), src/config.test.ts (the ship block), src/core/pullSweep.test.ts (item 20: the decision table, the lines, the per-repository bound), src/execution/gitRebase.test.ts (item 20: rung one against real git — the rerere replay, the range-diff, the lease push), src/core/commands/pulls.test.ts (item 20: the command), src/core/pullSweepWiring.test.ts (item 20: the wiring), src/execution/sweepCheckout.test.ts (item 20: the checkout end to end)
  • Docs: pr-description.md (the PR gate ship builds on), agent-coding.md, agent-review.md, resident-repos.md, http-ingress.md item 9 (the runner's routes and Workflow), run-history.md items 47–50 (the runner's records)
  • Budgets: the ship preset declares the pipeline's wall clock — AGENTS["ship"].maxMinutes, 1440, which a deployment's ship.maxMinutes replaces (shipPresetFor) — as its budget, a profile field like any preset's (routing-and-config.md item 2): a scope's boundary clips it, a budget: directive narrows it, the card and the record say so, and the runner is handed the effective minutes as each unit's wall clock; ship.maxRounds (default 48) is the pipeline's own cap. Every round's minutes are carved from the unit's remaining wall clock by carve in src/core/budgets.ts (decision 0046; harness-pi.md item 15 holds the table): the remainder minus the reserve for the rounds that must still follow (each later review and findings round at its floor plus provisioning, and the merge wait's floor), capped at the round's ask (coding 90, review 25, the merge wait 60), and refused under the round's floor (coding and fix 15, review 7, the merge wait 10) — a round the remainder cannot carve above its floor is not dispatched, and the unit ends at the cap naming the round, the minutes it would have got and the floor. The wall clock is authoritative and whichever cap hits first ends the unit. Push-before-abort: a coding child of the pipeline (a coordinator's spawn) whose loop ended at its time budget does not strand its partial work — after the workspace observation the run loop commits the tracked changes and pushes them with any unpushed commits to the unit's own branch and publishes the pushed head as a pushed_head event (by: "salvage", run-history.md item 2), so the record carries the sha a renewal continues from — the coordinator-owned unit branch, never an auxiliary push or a moved checkout — never the plan's base, and nowhere when the base cannot be named, since the branch might then be the base (salvageTargetOf, salvageWorkOf, salvageBudgetPush, src/core/codingPrPostStep.ts; a cold sandbox clone is found below the workspace root before checkpoint commands run; a budget_salvage note says what was pushed, that there was nothing to push, that the workspace could not be measured — a measure the observation could not take is never read as clean, and the salvage is not attempted on it — that the push failed, or that the salvage was skipped and why), so a re-issued attempt starts from the partial work instead of zero. The grant rides the request (decision 0046, the renewable lease): what the request authorizes beyond one lease — Grant { renewals, costCapUsd? } in src/core/budgets.ts, zero renewals and no cap by default (DEFAULT_GRANT), the count bounded by GRANT_RENEWALS_MAX (12, thirteen one-day segments if explicitly granted) — resolved once at the fork by resolveGrant (the request's renewals:<count> directive sets the count and keeps the cap the scopes set; the user's ship.grant over the channel's over the org's ship.grant, the default counting as the org's), written on the instance beside merge as grant and grantSource, answered by the plan route, carried on the pipeline's own run_meta.grant, and named by the unit report (Renewals: 0 of 6 spent, cost cap $50 (granted by channel)); a malformed grant is refused by name at load on every block that can carry one. The grant decides, the lease continues (decision 0046, Renewal): a round 0 that ends without a pull request is a segment over with the unit unfinished, and the machine judges renewal there — progress is read off the coding child's record, never asked of it (progressOf, src/core/ship/renewal.ts: the last head pushed to the unit's branch that differs from the sha the segment started at and is not older than the lease, else a handoff whose follow-ups shrank or deviations grew), then the grant's count, the session's summed spend against the cap (each child's cost through costs.prices; unknown once any run's model has no price, and a cap never trusts an unknown total) and the fit, in that order (renewalDecision). A renewal ends the segment continued — the round noted continued, the report naming budget renewed, N of M, continues <sha> — and the driver opens the next segment of the same unit: a new pipeline under a fresh lease with every step named under the segment (U10/s2/…), whose coding child is briefed as a continuation (the branch and sha to continue from, the previous run's write-up and handoff before the unit's own request; composeChild) — a run, not a resume, from the recorded sha in a clean tree. The bot writes the renewal as a row on the unit (segments, keyed by the segment it opens; run-history.md item 50) before the segment runs and never twice, so a runner reclaimed between a segment's end and its renewal finds the row. A refusal names the clause on the abort (no progress on the last budget; 5 renewals left unspent — a renewal is spent only by a budget that pushed to the unit's branch or moved its write-up; re-issue the request to try again; the cap; the exhausted grant; the fit), and a plain abort keeps its shape when nothing was pushed under a grant of zero. Nothing spends a renewal by hand today: follow-ups route by thread context, never by a keyword (routing-and-config.md item 3), and a re-issued request opens a fresh grant — so the stop names what actually spends one and the honest recourse instead of teaching a continue reply nothing parses. The idle flag rides beside the grant (record 0051): ship.idleDays — an integer count of days, 0 to IDLE_DAYS_MAX (365, the platform wait's ceiling), IDLE_DAYS_DEFAULT 0 so it ships off — resolved once at the fork by resolveIdleDays (the user's ship.idleDays over the channel's over the org's; routing-and-config.md item 2), written on the instance beside the grant and answered by the plan route (readIdleDays in the driver reads anything unreadable as 0 — nothing idles on a guess); a malformed value is refused by name at load on every block that can carry one. Above zero, the machine ends every idling kind idle instead — item 8 — and an idle spends no renewal: the wake's segment does (IDLE_WAKES_MAX, 100, bounds one idle's wakes there). Spending a renewal by hand is a later unit's

Behavior ​

  1. A directive, never a registry command. agent:ship in <owner>/<repo>: <task> resolves through parseDirectives/AGENTS["ship"] (machine: "repo-resident", toolset: "full") and forks inside dispatch() after agent resolution — a CommandRegistry handler may never start an agent run (AGENTS.md invariant 3). The channel rule is the request handle's own capability (record 0060): the runner posts its card and opens each unit's thread through the requesting thread's channel, so the preflight admits a handle that can open a thread of its own (canOpenThread, passed by the ship branch as io.openThread !== undefined — Slack, the CLI, the web chat, HTTP /ingress and MCP dispatch) and refuses one that cannot with the spawn's reason — "cannot open a thread of its own" — and a pointer to the run page, never a channel-prefix list and never calling a channel single-shot. Thread follow-ups are sticky like every agent.
  2. Compound permission gate. Running ship requires canRunAgent for ship AND coding AND review, plus the repo allowlist for the target repo; the ship branch forks after the dispatcher's repo gate so canUseRepo always runs. Every child the runner spawns is an ordinary dispatch() run authorized again as the requesting user by the authorize stage (http-ingress.md item 9), so the compound gate refuses the whole pipeline up front rather than letting a unit fail at its first spawn; it grants nothing the children's own gates would not. The refusal names the missing grant.
  3. Ship owns the pipeline branch. The resident keeps its sticky ref only as the fallback for an ask that names none; a child spawn's named ref is authoritative and replaces the sticky binding (resident-repos.md item 16), so a re-issued attempt cannot inherit the earlier attempt's branch. Ship names the branch itself through the one namer (unitBranch, item 15): every unit's branch is plan/<plan-id>/<unit-slug> — a seeded plan's from the file's id, a generated one-unit plan's plan/<generated id>/u1 (item 16's generatedPlanId over the task's text and the thread key, so a re-issued task reuses its own deterministic branch), and the runner's branch step creates it on origin from the pull request's base through the GitHub REST API BEFORE round 0's spawn (the exact base SHA is frozen before durable unit/action/stop/owner admission; pending precedes the POST to refs/heads/<branch>, and accepted/refused/unknown outcomes are retained. A lost response never replays the POST; only an exact authenticated ref read can resolve that creation) — the resident refuses to bind a ref origin does not have — and every child is dispatched on branch <ref> so its thread binds to it at attach. A known creation refusal (or absent base configuration) ends the unit with the reason before any round starts. Unavailable reads and uncertain writes remain retryable observations and never authorize another POST. A positively verified existing ref may be reused without a creation call. A coding child's attach must land on the pipeline branch: the executor fails closed before any model call if a resident predating named-ref authority answers another ref, and a review child attaches at the pinned head (the extracted attach-head guard, agent-review.md item 8). After pr_opened, read-record accepts the pull request's head as the pipeline row's branch only when the child's push-status-derived pushed record names that same ref, keeping the row and pull request head equal. Proof: [unit] src/execution/resident.test.ts::ResidentExecutor.attach over a heartbeat stream (item 59: an attach that waits on an install must not lose the connection)::a re-issued child after an aborted attempt rejects the previous sticky ref, while an unnamed ask may keep it; deploy/cloudflare-resident/rebindAttach.test.ts::a named ref replaces the sticky fallback before the worktree is provisioned::*; src/execution/githubPulls.test.ts::githubPulls::createBranchRef (ship round 0)::*; src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::branch freezes its base, persists pending before the POST and never replays an unknown outcome; src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::a known 422 remains refused and does not become unknown or permit another POST; src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::stop between admission and begin cancels only the provably unstarted creation; src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::carries the actual Workflow and action step on branch requests; src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::after pr_opened the pipeline row records the ref the coding child's push status named, matching the pull request head.
  4. Round 0 = the PR gate end to end. The coding child implements, pushes, and submits its PrDescription; its post-step is the coding PR post-step (pr-description.md item 5) against the base the runner's tag names (CoordinatorTag.base, since the child's thread binds the head branch itself) — a base that survives a bot roll: the claim publishes the tag as a coordinator_tag event, a resumed child rebuilds it from its row and, failing that, reads the instance's base from the coordinator store, and one with no base anywhere opens nothing and says the plan's base was lost across a roll rather than targeting the unit branch or the default branch (run-history.md item 48a) — so the pull request is opened from typed artifacts and the runner's pr-check after the round reads {repo, prNumber, headSha} from what heads the branch. Round-0 terminals other than "PR opened" — clarifying question, budget write-up, no description, unproven push — end the unit with a report naming the terminal and the child's final words leading; ship never retries a child round. The question passthrough keeps the thread alive: the user answers and re-issues.
  5. Review rounds run on a pinned head, in the unit's thread. The review child is an ordinary review run given the pull request and the head to pin (buildShipReviewTurn; a re-review round carries the previous round's findings and the dispositions the coding run recorded against them, matched by the runner with an id the review never issued named as dropped, item 6). It runs in the unit's thread, like every child of the unit (record 0055): it seeds from its own session (<thread>:review, session-log.md) and attaches a read-only tree of its own life, released like every run's at its end, so nothing needs a second thread; a run live in the unit thread answers the spawn busy. A row a bot wrote before that record names a review thread (reviewThread, run-history.md item 50); its review rounds stay there. The head is pinned before its attach (readonly), the run submits the structured verdict with findings, the reviewed-head gate + repush-void machinery apply unchanged, and the review posts pinned to the head it read. A verdict always covers the full diff against base — the re-review-delta skill narrows reading, never verdict scope. No verdict = abort: a review child whose record carries no submit_verdict (budget, refusal, stop) ends the unit with a report naming that terminal — it is never converted into a request_changes the reviewer did not make.
  6. Findings and dispositions are typed artifacts. ReviewVerdict.findings[]: { id, severity: blocking|major|minor|nit, file, line?, title, kind?: single|pattern, invariant?, cases?: [{ scenario, expected }] }, validated fail-closed per finding; every NEW finding submitted through submit_verdict must declare kind (single or pattern). A pattern requires a non-empty invariant and at least one complete case; a single cannot hide a case table. The tool refuses untyped or incomplete pattern submissions without replacing the last valid verdict; stored legacy findings without kind or a matrix remain readable. The invariant and cases survive record redaction, the fix/re-review turns and the posted review (not only the model's prose); buildReviewPostBody renders them under the verdict line, and the body starts with the exact LGTM: token only for approve — an approve carrying a finding at or above the severity to address is downgraded to request_changes in parseVerdictInput (agent-review.md item 5a), so the auto-approve workflow can never fire over a finding the loop has to address. The findings step's coding run records one disposition per finding through submit_dispositions (fixed|declined + note). Dispositions are kept per round: finding ids are only unique within one review round, so a later round reusing an id for a new finding inherits nothing, while a finding carried forward unchanged (same id, severity, file, title, invariant and ordered cases) keeps its recorded disposition. Adding a case to the same invariant retains its id for the review trail but cannot inherit a fixed disposition claiming to have covered a smaller matrix. The coordinator binds the round's exact review and check finding ids to its findings child in the durable run tag (run-history.md item 48a). submit_dispositions accepts only a complete set of distinct, exact issued ids; an unknown, duplicate, missing or altered id is refused with the issued ids before replacing the last valid set. A run without the binding is refused without recording. The accepted set rides the run record (run-history.md item 2), where the runner's read-record reads it. The runner still matches at completion (matchDispositions, src/core/ship/coordinator.ts): when the machine reads the record and when the spawn route composes the re-review turn, the dispositions naming the round's finding ids are kept and one naming an id the review never issued in a legacy record is dropped, with a note to the re-review (Dispositions naming no finding of the previous round (dropped): …), so the state, the cap report and the reviewer agree on what was answered. A context without the sink (outside the run loop: a unit test's, a CLI's) is told that no run is recording, never a false "recorded" ack.
  7. The findings step: the review's findings are a message into the unit thread, and the coding session there addresses everything and repushes (record 0034). After a verdict that requests changes the runner enters findings (item 15), never a fix child briefed from the review: the bot dispatches the review's findings into the unit thread as the requester with the coordinator's tag through the spawn route (a findings brief naming the review run, http-ingress.md item 9), the directive agent:coding explicit in the message's text so a person's agent:review detour in the thread or a lost store never routes the findings elsewhere. The message is what the requester would paste: every finding verbatim with its id and invariant/cases table when present, the review's own words, and the ask (check every applicable case against its expected result and record a disposition per finding through submit_dispositions, the description resubmitted, the branch pushed, never a merge or an approve; the address-review-findings skill carries the craft); it carries no contract, but exact review and check finding IDs ride the coordinator's durable tag rather than prompt text alone. The message retains typed review-versus-check provenance: when only required CI checks failed, it asks to repair only those checks, never claims that the review requested changes or reissues closed resolutions. Review-only and mixed findings retain their respective asks; a prose-only review fallback asks to address the feedback without inferring a verdict from the prose or identifier spelling. The run resolves coding by directive and starts as any coding run in that thread does, so on a harness that keeps the thread's coding session the agent that wrote the code answers its review with the reasons it had, and on a native coding preset the run seeds from the channel with the findings as the request. A run live in the unit thread at that moment is a person's (the runner awaited its own child's end): the spawn is refused coordinator_thread_live (thread-admission.md item 8) and answered busy, the step waits and asks again under the unit's wall clock, and a busy answer that reaches the reserve ends the unit wall_clock_cap with no coding run started (item 8). Every severity including nits gets a disposition, commits are squashed coherent, the description is resubmitted (the bot re-renders and edits the PR at the new head), and the branch repushed. A coding round — round 0 or a findings step — that pushed the pipeline branch onto an open PR without resubmitting gets the same bounded description turn a plain coding run gets (pr-description.md item 5), while its workspace is still attached, before its post-step; only a turn that still submits nothing leaves the post-step's warning. The post-step's note reaches the unit's thread like any coding run's, and a findings step that opened a NEW PR (the old one closed out from under the pipeline) is adopted at the round's pr-check for every later round and report. A findings step that ends with the branch still at the previously reviewed head repushed nothing — the unit aborts with that reason instead of burning a review round on the same diff, with ONE exception: a round that declined EVERY finding on the record changes no code on purpose, and the re-review still runs over the same head to verify those arguments and possibly concede (the decline path's designed resolution — maxRounds still bounds a decline stalemate, except the separate bounded same-invariant continuation in item 8). The runner passes the decision's tier to its children (the one-door plan's tiers rule; routing-and-config.md item 2): the spawn route's body takes model (<provider>/<model>) and effort, each refused by name when malformed (parseSpawnStep) and the model held to the child preset's allowed tiers before any store is read (spawnTierRefusal — a coding or review child never runs the fast tier, spawn_tier), and writes them into the child's request as its own model:/effort: directives (childRequestText), the request slot of the resolve ladder, so the child runs on the tier the decision named ahead of every scope. Rounds are strictly serial — the runner waits on each child's run-finished event and confirms its record before the next spawn (item 15). A findings run advances only when its status is completed, it recorded one matching disposition for every finding, it submitted the updated pull-request description and it recorded its final observed head; its handoff is not a findings output. The runner then independently reads the adopted pull request and its branch: exactly one next review starts only when both are open and their current head exactly equals the observed head. A push event alone proves no remote effect. Missing outputs, an unfinished run, no open remote, an unreadable branch or head, or a different head stops before review with separate saved-work, failure and next-action facts; a duplicate pull-request answer cannot start a second review. An operator stop of a child ends the unit as a stop naming the mode; the stop is honored between outcomes, never over one: a review verdict that already posted settles first — an approve that posted is merge-ready, stop or no stop, and a stopped report names a posted changes-requested review.
  8. Caps are ceilings, honestly reported. A posted re-review whose sole actionable finding is the sole finding from the immediately previous posted review, with the same id and invariant and a strict superset of unchanged {scenario, expected} cases, spends one of at most TWO same-invariant continuations instead of an ordinary review round. No previous pattern, unchanged/removed/altered cases, duplicate rows, check finding, or additional finding earns a continuation; sharing a file or id alone is insufficient. Review indexes remain unique; the ordinary maxRounds ceiling applies to physical reviews minus earned continuations, and a separate two-continuation ceiling prevents endless widening. A settled review records each earned continuation on its durable round note; original-unit recovery credits only marked slots whose immediately adjacent owned, completed, posted review records prove that same strict widening, carries the verified count and the immediately preceding posted review’s case table on its claim into a directly recovered review (a resumed review compares its next verdict against that table), and refuses ambiguous or unmarked evidence rather than resetting or inventing credit. A continuation still needs a findings fix, a fresh review, the remaining wall-clock carve and the ordinary publication/head guards. Failed checks and genuinely new defects spend ordinary rounds and end at the cap; approval at the last allowed round still proceeds to checks. The pipeline's wall clock is the ship run's effective profile (routing-and-config.md items 2 and 4): the ship preset's declared budget — ship.maxMinutes, one number the profile and resolveShipCaps both read, the registry's 1440 by default — as a boundary on the path or the request's budget: directive clipped it (caps.maxMinutes is profile.minutes; maxRounds is the greatest count the effective minutes can hold, up to the configured ceiling), handed to the runner on the instance record as each unit's clock. The card names the clip before the fork like any run's (budget 200 min (channel boundary; preset asks 1440)), and the ship run's ledger row and record carry the profile under the preset's name. The child rounds run under that profile: each attaches on its own preset's class and identity — within the parent's, which the gate judged once before the fork, because coding and review declare ship's class and an identity at or under ship's write — and is spawned with the minutes carve hands it — the unit's remainder minus the reserve for the rounds after it, capped at its ask — and a round whose carve falls under its floor is not dispatched (the Budgets bullet above). The fit is asserted before the fork as well as at config load: fit (src/core/budgets.ts) says whether a pipeline's minutes hold its first child at its ask and every later round at its floor — provision + ask(coding) + reserve(coding), 169 at three review rounds and 197 at four — and the fork selects the greatest count up to ship.maxRounds that fits the effective minutes (a boundary or budget: directive may clip them). It refuses a lease that cannot hold one review round, naming the sum on the card and in the reply and opening no instance. validateShip refuses an explicitly configured ship.maxMinutes/ship.maxRounds pair that does not fit; an omitted rounds setting adapts to a shorter configured lease. A plan runner's child card names the carve (budget 45 min (carved by the plan runner from the pipeline's remaining clock; preset asks 90)). The one-day default holds 48 rounds at their floors; the wall clock may end a unit earlier when rounds take longer. Push-before-abort is keyed to independent end facts: the run record carries the final Git head the run loop observed as headSha, beside the handoff and pushed heads; read-record returns all three. A completed child with a handoff whose by: "salvage" push is exactly that head takes the pull-request recovery path and enters review, because salvage only republished finished work; a different head, another terminal status or a missing handoff remains an interrupted checkpoint. A cap report distinguishes declined findings (disposition recorded) from unaddressed ones (no disposition), computed against the LAST review round's findings with only the dispositions recorded for them — a carried-forward finding keeps its disposition, a reused id inherits nothing. A unit idles instead of ending when the flag is on (record 0051): with the resolved ship.idleDays above zero, every ending but merged, closed, already_landed, merge_ready, refused, idle, idle_expired, a draft held, and an aborted findings ending carrying a typed merged or closed PR observation becomes idle; a human-gated held becomes idle regardless of this flag because the finding is a pending question (idleEnding in src/core/ship/coordinator.ts, at the machine's one ending funnel; a draft hold waits only for the pull request to become ready, while a human-gated hold is always the pending question and a blocked hold — issue 2086 — idles when configured: in both question cases the person's word is exactly what the wake carries) — the old kind as why, the old ending carried whole so its report renders byte for byte at every level (the thread's copy at the request's verbosity, routing-and-config item 28), and what a continuation needs on the ending (the renewals the grant still holds, unspent; the head to continue from — a review_pending's own pending head, else the last coding child's, and the driver still names it as headSha so the row's lastPush survives the idle; the last coding child's run id, absent when none ran; the spend; its handoff) — the round notes keeping the old kind's outcome; unit-end writes it on the unit's row as idle with wakes: 0 and no ending (run-history.md item 50), leaves the unconsumed thread events waiting, and the unit's facts, its page and the parent card read idle · <why>. The driver parks the walk behind the idle under <segment>/idle/<n> for the days left, wakes through the durable unit-wake answer keyed by that identity, and either opens the answered segment, waits again, settles a stopped unit or ends idle_expired; later units cannot start while it waits. The wake re-resolves the requester's scoped grant, sets progress aside but still enforces its count, cost cap and fit, consumes each attributed event once, and stores the answer, marks and segment row atomically; a stopped segment reopens under its remaining lease only when that remainder reaches leaseMinimum("coding"). The flag still ships at 0 — at zero the idle mapping changes no ending or continuation sentence; above zero the stop, renewal and interruption cards say “reply in this thread to continue”, and the segment card names the folded senders.
  9. LGTM → merge-ready; the merge is the runner's on a plan branch only. The pull request is part of every transition, never a snapshot the runner owns: before dispatching review or findings, before reading checks and before merging, the runner re-reads its state, current head and same-repository head-ref existence; an unknown branch-existence read is retryable and permits no transition. A merge by another actor is the normal terminal under merge: person: merged ends the unit with GitHub's reported login and merge sha; closed-unmerged ends it closed with the closer; a deleted branch receives no child; a moved review head restarts review at that head; a moved findings head reaches review only after the child's required outputs and exact-head proof, otherwise the unit ends and same-unit findings recovery requires saved-work proof. A child already live when one of those changes lands is steered through its inbox to end without a push or review post (never stopped), then drained; after a moved-head child drains the adopted pull request is read again before any review or recovery decision, so a merge or close during the drain ends the unit; a review child's late verdict cannot authorize a post; a findings child's late dispositions or push pass only through typed completion and exact-head proof, never as credit for a foreign head. Merge-ready stands on the POSTED approval: the review child's post-step returns a typed outcome the runner reads back (reviewPosted), and an approving verdict whose post failed or was refused by the reviewed-head guard ends the unit with an honest report naming the reason — the PR carries no approving review; re-issue ship in the thread with the PR URL to retry (item 10). On a posted approve of a generated one-unit plan's branch the unit ends merge_ready, persists that final reviewed full head as the unit row's durable lastPush, and reports: PR link, rounds used, declined findings, the checks at the approved head (record 0055: the ending's facts read asks pr-check for them, and the headline calls the head merge-ready only over green checks — a pending check is named as pending, and over a failed one the report says approved but not merge-ready and names the check, the ending kind unchanged; no fact read leaves the headline as before — and the READY STATE rides the same read, issue 1460's second half: the facts carry the pull request's mergeable_state and the head's self-declared fix-up commits (the fixup!/squash!/amend! autosquash subjects, fixupCommitSubjects), so a head that conflicts with its base re-enters the runner's rebase step before an ending is published, while an unsquashed fix-up commit is reported with the squash as the remedy, never "merge-ready"; a clean state and an empty fix-up list leave the headline to the checks), and the pending human merge as the remaining gate — or, when the pull request carries auto-merge at the approved head, that fact instead — or, when it has already merged by the time the ending is composed (auto-merge fired, or a person merged), that merge by commit and time, never a gate that has passed. The round verdict is now the whole of record 0055's design, not the headline alone: after a posted approve settles (and past the severity gate), one machine step named checks reads the check runs at the reviewed head with the merge door's own reading (total, pending, failed — the bot's checks route, classified by src/core/ship/checkFindings.ts). A failed check becomes a check finding of the round — id check:<name>, the check's name as the finding's file, severity blocking so the level in force always counts it, the conclusion and URL as its title — under a round note of its own (checks_failed, never the parser-mismatch gate), and the findings step then runs as for any changes-requested round: dispositions, a fix round, a re-review — the failed check is the round's answer as soon as it is read, whatever else is still pending, so no merge_ready ending and no merge step is reached at a head where the checks read failed. The round cap bounds fix rounds, never the terminal steps (issue 2023): an approval that lands in the last round maxRounds allows proceeds to the checks step and then to the merge (merge: runner) or merge_ready (merge: person) exactly as an approval in an earlier round does; only a findings verdict at the cap ends the unit, and then as round_cap with the findings in the report — a red check read at that last approve's head ends round_cap too (no fix round remains), the report naming the red check instead of claiming no approval landed — never an unended unit. A check finding sits on no run's record, so it rides the findings brief and the re-review brief by value (checks on both brief kinds) and the coding session's dispositions match it by id exactly as a reviewer's; the rows are told apart by provenance — the check flag only the machine's checkFinding sets — never by the id's check: prefix, since a reviewer's id is a free string and a re-review brief names the prior check findings by id, so a reviewer's own check:… row rides the review run's record like any finding. The checks step is one decision table over the reviewed head's facts (issues 2063 and 1991), read once GitHub has recomputed and re-read on every checks-settled event from the merge-wait book: (a) any failed check at the head is the round's answer at once, whatever else is still pending — the check finding and the findings step above, the flake rule keeping its place; (b) no failure and a check pending, queued, or expected but not yet reported — a required check of the base's rules (githubPulls.requiredCheckContexts, carried as RoundChecks.expected: the base's required contexts not among the reported runs), the repository's approve workflow whose run does not exist at the verdict instant being exactly such a check — registers the instance at the head in the merge-wait book and waits on checks-settled-<head> in the merge wait's own chunks for at most its sixty-minute ask, then reads again — an unreadable GitHub reads as pending — and a head still pending at the ask's end proceeds, the ending's facts read naming what is pending; (c) no failure and every expected check reported green adds no wait: merge-ready, or the runner's merge under plan:merge; (d) a draft pull request (the checks route answers the pull request's own draft fact beside the runs) waits for the ready event the same way — marking it ready starts the head's suites, whose completion fires the settled event — and one still a draft at the ask's end ends held: draft — mark it ready to continue; a red check on a draft still buys its fix round, cell (a) outranking (d). A head with no required check reported waits one chunk of grace; if the base names required contexts and all remain absent, the runner re-fires pull_request once by closing and reopening the pull request without moving its head — close and reopen are separate admitted native calls; an unknown response retains its original effect without replay, and an accepted close retains its reopen obligation until the pull request is restored open — records checks restarted on the round's card, then returns to the normal bounded wait; a repository with no required contexts still proceeds after that grace, and a reviewed head the machine never learned skips the step as the merge step's guard does. And unfinished is not an ending a person can read — it is the machine's word for "no ending was chosen": every exit of the table names its cause in the user's nouns in one line, and a unit row a seal finds with a thread and no ending prints "no ending was recorded — re-issue agent:ship in its thread to continue", never the bare word (issue 2063). That seal line is a last resort, not a normal exit (issue 2100): any unit step that throws inside the walk — including a stopped or blocked ending — becomes the unit's ending before the Workflow fails — the driver posts unit-end with kind failed, cause step_threw, and a report naming the step, the round and the throw's one line in the user's words ("The runner failed after round N's review verdict: …"), so a walk that dies between a review's verdict and its outcome write leaves a cause a person can read; and the seal's remedy holds either way — a re-issue in the thread of a pull request already approved with green checks resumes at the checks step (item 10's entry facts), never at a fresh coding round. The flake rule: a failure the bot classifies a suspected flake — a test timeout or runner stall whose output names only test files the pull request's changed paths never touch, judged conservatively so anything unprovable is a real failure — is re-run once before it becomes a finding (frozen native target IDs and one separately admitted request per target): the Actions rerun-failed-jobs retry where an Actions run backs the check, and GitHub's check-run rerequest — the ask to the app that created the run — for any other, this repository's Depot CI legs included; a second failure is the finding, a real failure beside a suspect spends no re-run, and a re-run the bot answers it could not dispatch (retried: false) spends the one retry with the head read again at once — never a wait on a settle that never comes. The transient re-run (issue 1932): a round-0 coding child whose record names a retryable model-call failure (failure: provider_transient for verified provider transport failure or failure: model_stream_incomplete for a pi-local cut stream, both past the harness's own retry ladder and riding read-record; agent-coding.md item 11) with nothing pushed — the recover pr-check read no_commits, so the durable preservation receipt proves a clean workspace and the remote branch is unchanged and a re-run costs only minutes — re-runs round 0 once instead of aborting the unit: a fresh attempt under fresh step names (RoundRef.attempt, the /a2 suffix on the round's steps) so the Workflow's durable cache never hands the re-run the dead attempt's answers, the boundary reported as the round outcome transient; a second transient in the same round is the ending, kind transient — named so it reads as a condition in the plane's table beside checks_failed and held, never as the child failing on its task — while a transient with an ordinary agent push takes the recover path as any dead child's, while a mechanical by: "salvage" WIP push aborts with the branch/head named (unfinished work is never sent to review) and no_base, a plain none, or a dead child whose record names no transient keeps the abort. When the instance's merge field says runner (the hand-off writes it: runner for a seeded plan, person for a generated one — item 16) the approve is followed by the runner's merge step under the plan:merge grant (http-ingress.md item 9; authorization.md item 2): a squash at exactly the approved head, only with the bot's LGTM standing at that sha and every check green, refused by reason otherwise; a pull request the door finds already merged (auto-merge fired, or a person merged after the approval) ends the unit merged with by: other, the merge commit and the time — exactly as the pre-check does for one merged before the attempt — never merge_refused. A base that takes changes only through a merge queue is enqueued, never squashed and never refused (issue 2011): before the squash the door reads the base branch's rules (the merge_queue rule of the repository rulesets API, githubPulls.branchHasMergeQueue) — or, when the rules could not be read, recognises GitHub's 405 with the queue's wording (MERGE_QUEUE_405) after the attempt — and either way enqueues the pull request (the GraphQL enqueuePullRequest mutation, the same act gh pr merge --auto performs; a pull request already in the queue is success, so a replayed step enqueues nothing twice) and answers enqueued; the machine records the boundary once as an enqueued round note and stays in its merge wait with every later ask marked queued, on which the door reads the queue's outcome instead of squashing — merged from the facts (by: other, so the unit ends merged as today), still enqueued with the position, or removed with the queue's own reason (the pull request's mergeQueueEntry and the timeline's last RemovedFromMergeQueueEvent, githubPulls.fetchMergeQueueState); a removal becomes the round's finding — id check:merge-queue, severity blocking, the reason as the title — under a dequeued round note, and a fix round follows exactly as a red check does through the checks step (the round cap standing when the rounds are spent; a removal with no review run to brief the fix from — a resume straight at the merge decision — ends merge_refused with the queue's reason); one still queued past the merge wait's budget ends merge_refused naming that the queue merges it on its own, and a repository without a queue keeps the direct squash; and the merge:person path names "queued" in its line too — the ending's facts pr-check reads the base's merge-queue rule beside the checks (baseHasMergeQueue, read only on checks: true, unreadable rules leaving the field out), so the merge_ready report's remaining-gate line says the person's merge enqueues the pull request (gh pr merge --auto) and the queue merges it on its own, never a direct merge; without the fact, or with no queue, the line is unchanged. The release pull request is never merged by the runner, an instance whose field says person or carries none waits for a person naming the field, and, as defense in depth, so does a unit whose branch is not plan/<its plan's id>/…, the refusal naming the field and the branch. Ship's bot-process GitHub writes are exactly the branch create, the PR open/edit, the checks step's one close/reopen recovery, the pinned review post, the board comments, and that one guarded squash; the coding prompts + fix skill carry never-merge/never-approve. Auto-merge is the pull request's OWN fact (auto_merge → PullRequestFacts.autoMergeEnabled), never the repository's: it is named at entry (the hand-off's reply) and again at the approved head (the driver reads the facts fresh when it composes the merge_ready ending — "auto-merge is on for this pull request: the approval merges it once checks pass"), and never refused; no repository-level auto-merge check remains, and a repository lookup failure only leaves the default branch unknown. A conflicting approved pull request (mergeable_state: dirty) stays owned by the live runner: the merge door returns the typed conflict before checks are read, and a generated plan's approved-not-merge-ready tail makes the same transition instead of publishing an ending. The runner re-enters rung one of the rebase resolver; an unchanged patch carries the existing approval and returns directly to checks, a changed clean patch returns to review, and a conflict git leaves buys one coding rebase round. Every later base move takes the same path again: fix-round eligibility is per conflict and each round is carved from the run's remaining lease, never spent once for the pull request's lifetime. No pipeline-owned path says “rebase it by hand.” The sweep remains the remedy only when no live runner owns the pull request (item 20). And the coding child's contract instructs a re-fetch of the base right before the push, rebasing once more when the base moved during verify, so the pull request is not born conflicting. An approve is held to the severity to address (review.addressSeverity, default minor; per-channel/user config set … --review.addressSeverity <level>, per-run severity:<level> — one lever with the review's own gate, agent-review.md item 5a — resolved once by the hand-off, directive > user > channel > org, and written on the instance beside merge so the machine reads one value; each review child receives it as the severity: directive of its request, item 5, so the child's verdict parser holds the approve to the same level and posts Changes requested: over a gated finding): a posted approve carrying a finding at or above the level (every parsed finding carries one of the ladder's four levels — the verdict parser drops an entry with any other severity, fyi or none, with a note, so nothing outside the ladder reaches the gate) continues into the findings step exactly as a request_changes does — dispositions, a fix round, a re-review — while an approve whose findings all sit below the level ends merge_ready (or merges), the report naming the level in force, its source (org, channel, user, run) and the findings it left below the gate (in the full copy — the row's and the board's; the thread's quiet copy is the one outcome line of item 12a and the findings, the level and its source ride it only at verbose — routing-and-config.md item 28, record 0066). With the parser holding the child's verdict to the same level, this check is defense in depth: it bites only on a record whose verdict was parsed at another level — a lost severity: directive on the child's brief, a record from before the parser's gate, a harness around submit_verdict — and when it bites it says so: the round note the machine emits for that approve carries gate: { level, findings: ["F1 (minor)", …] }, the driver forwards it on the round route, the route holds it to its shape (a level on the ladder, string findings; malformed → 400, nothing appended), keeps it on the unit row's round (rounds[].gate, the ship_round event's gate), draws ⚠️ gate fired: … at or above <level> on the card's unit line and warns in the bot's log — so the backstop doubles as a detector for the mismatch instead of routing around it silently. An approve whose findings all sit below the level carries no gate. A round whose actionable findings are all human-gated parks as a live question, never ending held or opening an unanswered fix round: a reviewer marks a finding whose remedy is a receipt only a person can produce — a replay needing a provider credential no sandbox holds, a procedure a person runs live — with humanGated: true beside its severity (agent-review.md item 5), and the coordinator reads that flag, never prose. When every finding the round would act on carries it — the gated set on an approve, every finding on a request_changes — no coding child is spawned before a person answers (a fix round could change nothing yet) and the unit parks as idle before the round cap is even asked: the report and the pull request's own comment (the bot's unit-end route posts it beside the board's copy) name the human-gated rows and the two answer surfaces — the unit thread and the pull request. One actionable finding beside a human-gated one keeps the fix round: the dispositions cover the human-gated row like any other. A check finding is never human-gated (only the reviewer's tool sets the flag). A human-gated-only round never ends the pipeline held: it parks as the unit's idle pending state and later units remain blocked behind that live owner. The next human unit-thread reply or pull-request conversation comment wakes it without spending a renewal; the full parked pattern case table and earned continuation count survive the wait, the findings and attributed answer are the coding fix round's brief, and the following review compares its verdict with that table. An adopted attempt whose newest human conversation comment postdates the last bot verdict marker takes the same fix-then-review path instead of reviewing first.

The merge door freezes the full approved commit and records its exact Workflow/action permission on the existing unit before writing. Native branch rules select squash or enqueue; an unreadable rule is retryable. A begun call with an unknown result retains ownership and can only be resolved from exact native facts. Enqueue acceptance remains an active obligation until GitHub proves terminal merge or known-entry absence, even after stop. Error messages never confer merge or queue authority.

  1. Entry checks make restarts safe. Ship never opens a duplicate PR (open-or-edit by head branch; a re-issued plan with a recorded prior push finds its unit's pull request at the pre-check, item 15 — and resumes it, never re-codes it. A fresh task adopting an existing pull request has no prior push in its unit row and enters coding round zero even when that PR is already approved with green checks: the unit-start's pre-check reads the entry facts beside the listing (entry: true — the branch's own tip, whether the bot's approval stands at the head, and the checks there), and for a re-issued unit with a prior push, an open pull request at the branch's own head enters the review round with that pull request, one approved with green checks resumes straight at the merge decision (merge_ready for a person, the merge door under merge: runner at exactly that head); a fresh adopted task instead spawns coding on that PR before any review (issue 1689). Existing-PR publication is one exact durable authority. An adopted or resumed pull request records its repository, number, head/base refs, full expected head SHA, owned publication ref and unit owner on the unit row; a pull request first discovered on a generated unit records the same authority before a later coding round. A normal reply to an ended generated pipeline may repair an unbound legacy merge_ready row only from that exact unit key's durable completed coding head and posted approving review head, when they yield one full SHA; a publication-bound merge_ready row is completed even without lastPush, so a continue bind refuses and only a separately named task can start new work; it freshly verifies the original requester, open same-repository PR number, head ref, base ref, live ref tip and sole runner ownership, then one conditional atomic transition requires the exact unchanged legacy row, reserves sole ownership, and writes lastPush with the complete binding before reissue. Missing, partial, ambiguous, moved, foreign, closed, wrong-base, wrong-requester or changed-owner evidence starts nothing and writes nothing. Every coding spawn with an existing pull request requires that binding and the runner's current sole ownership before dispatch. Immediately before the harness opens, the request's repository, PR, refs and head, the attached workspace's ref and head, the unit owner, the durable binding and one fresh pull-request read must all agree; unavailable, partial, foreign, missing, stale, moved or mismatched facts block every push. An allowed push names only the owned ref and carries --force-with-lease=refs/heads/<ref>:<expected-full-sha>, so movement after the fresh read fails atomically; budget, compaction, interruption and completion salvage use the same receipt, leave committed work unpublished when blocked, keep its checkout attached under the workspace's retention policy, say that retention beyond the run is unverified, and never create an alternate branch or pull request. A ship invocation resumes at review — skipping round 0 — when a user turn names the PR (the thread→PR inference reads user turns only), it is open with a same-repo head, and the invocation carries no new task text — for ANY author: the bot-authorship check is gone, and with it the process-identity resolution the entry once required. A generated task in the thread of an open pull request adopts it: the generated plan's one unit runs on the pull request's own head branch against its own base — round 0 pushes to that head and the pre-check finds the pull request, no new branch — for a person's pull request exactly as for ship's own. A seeded request (plan <path>.md) keeps the plan graph's own plan/<id>/u<n> branches: a pull request in its thread is context, never adopted, and its facts are never read — a thread pull request that could not be fetched refuses nothing on the seeded path. The fork check runs on adopt and resume: a head that lives on a fork is refused. A PR number quoted as evidence inside new task text in the CURRENT message (repoContext.prFromMessage) does not bind — it stays in the task as context and round 0 starts from the repo's default branch (or a typed ref token), never from the quoted PR's head branch, even when the cited PR's facts cannot be fetched or its head is a fork; the round-0 base drops any PR-derived ref (repoContext.refFromPr, flagged at the resolver — and set only from an OPEN pull request: a merged or closed PR cited as a receipt contributes no ref hint at all, and a coordinator child's attach ref is its contract's branch, always, any PR-derived ref in the child's own request text dropped before the attach; issue 1860, resident-repos.md items 16 and 29) and any unit branch of ship's own (plan/<plan-id>/<unit-slug>, isUnitBranch): a thread keeps the binding its last run opened a pull request on, so after a plan's unit it sits at that unit branch until the unit's merge deletes the branch — its next attach then returns the binding to the default branch, the branch being one the thread's own run pushed (resident-repos.md item 16's second movement) — and a task re-issued there would otherwise base the next generated plan on the earlier unit — its pull request targeting that branch instead of the repository's so a failed fetch cannot leak the stranger's head branch. The exception is the thread's OWN pull request: an in-message reference that resolves to the pull request the thread's own run opened or edited (repoContext.prIsThreadOwn, set off the thread's record PR wherever the reference came from, beside prFromMessage) is the target, never context — it adopts with new task text and resumes without, through the same fork and fail-closed checks as any adopt or resume, and only a CLOSED own pull request beside task text falls through to a fresh entry off the default branch. The unit end reads the child's record the same way before declaring no pull request: a pr-check that finds nothing heading the unit's branch while the machine holds the round's pull request (pr_opened off the child's record, or an earlier round's adoption — the child pushed to that pull request's own head branch, not the unit's) runs the review round on that pull request at the head the child pushed — a push onto a branch heading an open pull request is "PR updated", never "no pull request opened" — at round 0 and after every findings step alike, since the same child keeps repushing that branch through every later round (a findings step that repushed nothing keeps item 7's abort). And the fact is verified, never trusted from a record written minutes earlier: the round's pr-check carries the adopted pull request's number, and when nothing heads the unit's branch the bot follows it and answers its LIVE state — open at a fresh head, merged (the unit ends merged by other), or closed unmerged (the unit ends terminal closed with the closer and briefs no review); an unreadable follow fails closed and the step retries before the machine acts. Kept fail-closed on a fetch failure: an INHERITED thread PR (not in-message), and a BARE in-message reference with no task text (a resume attempt must verify the PR first); a bare reference to a CLOSED pull request is refused — there is nothing to resume. The repository lookup is advisory: a failure leaves the default branch unknown and refuses nothing — an adopt or resume still carries the pull request's own base, and a fresh unit with no base lands on the hand-off's "no base branch is known" refusal. A resume is handed to the runner like every request (item 16): the generated plan's one U1 row carries resume — the pull request, its head and url — and the machine re-reads that pull request at the unit pre-check, then opens it at its first review round with no branch and no round 0 (openUnitPipeline's resume), on the pull request's own head branch and base; the review child pins the head and the approve ends merge_ready for a person, as item 9 has it. No pipeline state lives in the bot process — the runner's is the Workflow's, durable across bot deaths (item 15) — so a bot death under a pipeline interrupts a child: the next generation closes that run interrupted and tells its thread (run-history item 36), the runner hears the close at once — the interrupted record's commit sends the same run-finished-<runId> event as a finish (run-history item 47), so the wait settles on the event, the confirming read-record reads interrupted, and after the recover pr-check of item 15 found nothing pushed the runner ends the unit with the same note (shipInterruptedNote): the cause, any already-recorded pull request and the fact that the stopped run's latest commit was not verified there. An idle unit may name its durable reply action; otherwise the note promises no reply-to-resume behavior and says the concrete next action is a new ship run after remote branch and pull-request state can be verified. A later attempt still uses this item's entry reconciliation, and a unit already merged is refused as merged already (item 16). Nothing restarts a pipeline unattended (the ship-restart plan's D1, which the runner keeps).
  2. Resident-only in practice; the children are ordinary runs. The coding and review presets are repo-resident (execution.md item 18), so a target repository that is not onboarded is refused at each child's authorize stage (repo_not_onboarded) — the spawn answers refused and the unit ends with the gate's name, never a per-round cold clone the requester did not ask for; a resident that is onboarded but cannot attach falls back exactly as a plain coding or review run does (execution.md item 8), with the fallback named on the child's card. Each round attaches its own executor with the child agent's toolset (readonly review attach / writable coding attach — the mode-switch wipe is the accepted cost, bounded by clipped budgets).
  3. Rounds are legible. Every round boundary the machine reports (CoordinatorNote, item 15) reaches the bot's round route: the unit's row records it (the ship_round vocabulary — index, agent, outcome; run-history.md item 50), the card is redrawn with the orchestrator-owned round header (shipRoundHeader: Round 0 — coding, Round 1 — review, Round 1 — fix; a fix round shares its review round's index; a round-0 end that renewed is the outcome continued, decision 0046, and one the child concluded blocked is the outcome held (issue 2086)), and the runner's finish writes the parent's record with one typed ship_round event per boundary in order and the plan's summary as its answer (http-ingress.md item 9). Every child is a run of its own with its own record, model.turn spans and cost, tagged with the instance and its step (parentInstanceId, idempotencyKey; run-history.md item 48) — per-round cost is the child run's, read from its record, and the parent's card is the runner's to redraw, never a child's to erase. ship joins NO_REFLECT_AGENTS: the parent's report is per-PR findings ephemera. Endings are truthful: the unit's ending kind distinguishes merged / closed / already landed / merge-ready / review pending (the wall clock capped after the coding child shipped its pull request) / aborted / capped / stopped / no verdict / interrupted / refused (item 15), the plan's summary lists each unit's, and the card closes ✅ only for a plan whose every unit is merged, already landed or merge-ready — anything else closes ⚠️ over the units' endings. A unit whose scope already landed ends done, not aborted (already_landed): when round 0's coding child ends without a pull request, its handoff names where the scope landed (item 14's landed list — a typed fact off the record, never its prose) and the round's pr-check reads no commits on the unit's branch over the base (aheadOfBase: 0, GitHub's compare, read by the bot on a plain check that found no pull request and left out when it cannot be read), the unit ends already_landed naming each landing and the child's run, the round is noted completed, and the report carries no compare link, no renewal line and no re-issue prompt — there was nothing to ship, so none of them has a question to answer; the cursor marks the unit done and its dependents start on a base that carries it. Either fact missing — a handoff that names no landing, commits on the branch (a description-less push), a compare the bot could not read — leaves the round-0 ending as it was. The coding post-step reads the same compare before its own note: a pushed branch with no commits over the base is said to have nothing to open, never offered as a compare link over an empty diff. A child's write-up is never repeated (issue 1806): an ending that carries a child's final reply — an abort, a stop, a no-verdict, a continued segment — keeps its own lines (the reason, the renewal line, the restart or durable-wake action) and points at the write-up instead of embedding it: the child's message in the thread stays the single copy of the detail, and the pointer links the child's run page when the plan route answered the runs page base (runPageBase, <PUBLIC_BASE_URL>/runs, absent without it — read by readPlan into the machine's input, never an environment read in the machine) and names the run id otherwise. 12a. The unit's thread says less, in the user's nouns (record 0066; routing-and-config.md item 28's ladder). At the default verbosity the runner's end-of-unit report (renderUnitReport's thread copy) is ONE line, the outcome in the user's words with the pull request link: ✅ Merge-ready after 2 review rounds: <url> (the headline's caveats — approved but not merge-ready over a conflict, a fix-up commit or a red check — keep their own words), ⏸️ Waiting for you: <the human-gated rows>, ⏹ Stopped., 🧢 Round cap reached: <the last review's non-zero severity counts>, 🧢 Out of budget, ⚠️ Aborted, ⚠️ Not merged; the full verdict line, the findings left below the gate, the declined list, the remaining-gate sentence and the restart or durable-wake action are verbose asides and stand on the pull request, where the round routes already put them (the row's and the board's copy stays the full report). The card, the plan summary and the web unit page print the same words: endingWordOf and roundOutcomeWordOf (src/core/pipelineStanding.ts, pinned to their unions) translate every ending kind and round outcome — merge_ready reads merge-ready, checks_failed reads checks failed, wall_clock_cap reads out of budget — so no user surface prints an internal token (the vocabulary page: outcome, round, unit, pull request); the card's unit lines (unitLines), the plan summary (planSummary), the idle line's why and the web unit header and units block all read through them. The review child's own thread reply says less the same way (agent-review.md item 5b): below verbose, a posted itemized verdict is one line — LGTM, or Changes requested: 2 blockers, 1 major, 2 minor, 3 nits (only the non-zero counts, verdictCountsLine) — with the pull request link.
  4. The child contract is a typed object the pipeline renders, never one the child assembles (src/core/ship/contract.ts): what a coding child is handed for a plan unit, and what the review child checks the diff against — the unit's own text, the spec rows it names, the repository's agent rules and the guards it may not weaken, because the instructions handed to a worker decide the outcome and the plan already knows more than a task string carries. Its first instruction orders the child's work: rebase onto the parent before anything else, push as soon as the change exists and the fast gates pass — the gates themselves are the coding preset's own instructions' (FAST_GATES_BEFORE_PUSH, src/agents/registry.ts, agent-coding.md item 13: the changed-set forms with their commands named, npm run verify named as CI's gate; issue 1796 — while the contract alone carried them, every ask that was not a plan unit had to repeat the paragraph by hand or watch the run spend most of its budget on the whole suite before its first push), so the first instruction points at that paragraph (FAST_GATES_POINTER, naming it and handing the full suite, the full typecheck and the full verification to CI) and never re-states it — a plan child reads the sentence once — keeping only what is the contract's own, the receipts (GATE_RECEIPTS): the child pastes each gate command's exit line into the PR description's validation table as the row's proof and puts a gate it could not run under the handoff's unproven list, never claimed clean — the handoff carries no verified list (agent-coding.md item 9), and only a plan child has a handoff to route them to — the project's full verification being CI's gate, run there after the push with any fix as a further commit (agent-coding.md item 13: an unpushed tree does not survive the run's end) — and hold the preset's rebase-before-every-push rule (REBASE_BEFORE_PUSH, agent-coding.md item 13; record 0071 mechanism one: the base fetched, the branch rebased onto it, the fast gates re-run on the rebased tree, then the push — always, not configurable), which the first instruction points at (REBASE_POINTER, naming REBASE BEFORE EVERY PUSH) instead of re-stating, replacing the conditional "rebase once more if it moved" the contract used to carry, so the pull request is not born conflicting. contractFromPlan({ planMarkdown, unitId, readSpec }) is pure — the plan and the specs arrive as text, nothing reads a disk or the network — and builds { unit, specRows, agentRules, guards, rebase }: the unit is its ### U<n>. heading to the next ###/## heading, verbatim (a closing thematic break dropped), with the plan's bullet keys (Goal, Requirements, Dependencies, Files, Approach, Test scenarios, Verification) parsed whole; the spec rows are the items the section names as <spec>.md item(s) <n>… — path-prefixed, backticked, linked or bare, comma/and lists and ranges, a parenthetical after the number, and a bare spec item <n> attributed to the nearest preceding spec named in the section (to nothing when another file such as AGENTS.md is nearer) — each once, in order, resolved to the spec's numbered item verbatim plus the validation-table rows whose criterion names that item (a leading <n>:, item <n>, a list, a range) with their proof bindings; a spec that is not readable or has no such item is said in the render, never invented. The agent rules are the text the caller read (AGENTS.md, else CLAUDE.md, else none); the guards are specs:check, specs:coverage --test-guard, hygiene:check, decisions:check and check:pr-title, one line each on what they refuse; a unit the plan lacks throws naming the units it has. There is no task-string builder: a generated one-unit plan (item 16) has its request text — read from the authenticated unit checkpoint written before the first child, never reconstructed from a run record or thread scan — built as its one unit (generatedUnit, titled by unitTitleOf — the first line cut to 80 characters, the rule the hand-off's unit row shares) and handed to this same contractFromPlan, never parsed back out of markdown, so every child contract, seeded or generated, comes from the one builder and a request that reads as a heading stays the request's text. renderContract(contract, { maxChars }) renders one block under ## Contract with the fixed sub-headings ### First instruction (rebase the unit's branch onto the merged parent, both named when known; a conflict ends the unit), ### Unit <id> — <title> (the section's body verbatim), ### Spec rows, ### Agent rules (with its source file) and ### Guards, in that order and to the same bytes for the same object. Over the budget (DEFAULT_CONTRACT_MAX_CHARS, the one both children render with, until real renders fix the number) it cuts in a fixed order — the unit's Approach text first, then the agent rules, then the spec items' behavior text (their proof rows stay), then the proof rows (the rows' names stay) — never spec rows before Approach, each cut leaving a placeholder saying where to read the dropped text, and a closing line saying what was dropped; a part that is not there is not reported dropped; the unit's other bullets (its test scenarios above all), the rows' names and the guards are never cut, and the render reports its length and whether it is still over. The runner's spawn route composes the contract bot-side from the plan at the base ref (contractFor, http-ingress.md item 9) and hands it to the child as DispatchOptions.contract: the dispatcher appends the rendered block to a coding child's FIRST user turn as its own text part after the request's text (withContractInFirstUserTurn; the description turn sees the same turn), and renders the same object into a review child's system prompt right after the REVIEW TARGET block (agent-review.md item 17), so both children hold one object rather than two readings of a tree that may differ; a run without a contract runs exactly as before. contract render --plan <path> --unit U<n> [--root <path>] [--branch <name>] [--onto <ref>] [--max-chars n] (command-registry.md Catalogue; CLI-only under contract:read, because it reads host paths the caller names) renders a unit by hand for a person: it reads the plan, the specs it names under the repository root the plan path implies (docs/plans/<file> → the directory above docs, else the plan's own directory, else --root) and the rules file, prints the block followed by a receipt line with the measured length, the budget and what was dropped, refuses a missing plan as not_found and an unknown unit as invalid_input naming the plan's units, and renders an unreadable spec as not found rather than failing. Two questions the design record leaves open are answered conservatively until decided: a unit naming no spec rows receives none (not the covering specs' rows by path), and a child reports a follow-up in its handoff (item 14) rather than proposing a unit.
  5. The handoff is data the parent posts, never prose a person copies (src/core/ship/handoff.ts; agent-coding.md item 9): what a coding child hands back beside its description when it ran for a plan unit — { deviations: [{ from, to, why }], followUps: [{ what, where }], unproven: [{ criterion, why }], landed?: [{ what, where }] } — so a deviation from the unit reaches the unit's board issue without a person writing it there. landed is optional and names what of the unit was already on the base when the run began and the pull request or commit that carries it: the fact the machine ends a unit already_landed on (item 12), absent on a handoff that named none and on a record from before the list existed, read as empty by every renderer. The module is pure and dependency-light (the run record's node-free contract imports its type and shape check, so nothing in it may pull in zod or a Node built-in): parseHandoff validates a tool call (the three lists present and landed when given, each at most HANDOFF_MAX_ITEMS entries, every field a non-empty string of at most HANDOFF_MAX_FIELD_CHARS once trimmed, unknown keys dropped; a refusal is a string naming the path); isHandoffShape is the structural check a stored record runs — shape only, no bounds, because redaction may lengthen a stored string; redactHandoff puts every string leaf through the redaction seam; renderHandoffComment renders the board-issue comment — the unit id and the pull request in its first line, each non-empty list under its own heading, an empty list omitted with its heading, then the ledger rows in a fenced block a person pastes into the plan's follow-ups ledger while the plan is proposed, and nothing at all for an entirely empty handoff, the same bytes for the same object; renderHandoffLedgerRows renders one row per entry in the ledger's exact three-column shape (HANDOFF_LEDGER_HEADER), the source naming the unit's handoff and its pull request, the disposition open until a person decides, cells escaped so a pipe or a newline cannot break a row. A coding child takes the handoff through the same ToolContext sink as the description (the last call wins; an affirmed empty handoff is three empty lists, distinguishable from none) and it rides the child's run record, redacted by the one record assembly (run-history.md item 2), where the runner's read-record sees that one exists. The parent posts it at the unit's end: the runner's unit-end step names the last coding child (codingRunId, the machine's lastCodingRunId), and the bot's route — when the unit's row names a board issue (the open issue titled by the unit id, found at unit-start) — reads that child's record, a run of this instance and no other, and appends the rendered handoff under the ending's report in the one comment it leaves there, through the bot's own GitHub identity; an empty handoff, a run outside the instance, a run the history lacks or a record without one leaves the comment as the report alone, a failed comment never fails the step, and a task-string unit (no issue) posts nowhere while the child's record still has the handoff. The plan's own follow-ups ledger is amended by a person at review points, each row open until decided: no bot path writes a plan record, and a plan's body freezes once accepted.
  6. The plan runner's state machine is pure over the coordinator's step returns (src/core/ship/coordinator.ts; record 0031; the coordinator itself is http-ingress.md item 9). The ship coordinator — a Workflow instance in the bot's shim Worker — has no model turn and holds no credential: it asks the bot for one step at a time and feeds the answer back, and everything it decides is a pure function here. Nothing in the module reads a clock (every bot answer carries at, the machine's only time) or carries a task's text or a thread's contents (a spawn's brief names the unit and the run ids whose records the bot reads to compose the child's turn). Two machines. The plan graph and its cursor. parsePlanGraph(planMarkdown, planId) is every ### U<n>. unit in the plan's order with the unit ids its Dependencies bullet names (lists and U<a> to U<b> ranges, itself excluded), its slug (u<n>- and at most 24 characters of its title) and its head branch plan/<plan-id>/<unit-slug>; the plan id is the plan file's name without its extension (planIdOf: lowercase letters, digits and hyphens, or a throw naming the path), parsePlanBranch reads a branch back and answers undefined for every other shape — the line the merge grant draws — and planInstanceId names the plan and, from the second, its attempt (plan-<plan-id>, plan-<plan-id>-<n>; the suffix never trims into the name), so a second runner for one attempt meets the engine's duplicate-id refusal and a re-issue gets the next id; parseShipPlanRequest reads plan <path>.md [units U<n>, U<m>] and nothing else, so a task string stays a task string. openPlanCursor(graph, selected?) puts the selected units (every unit when none are named) in play in the plan's order and refuses an unknown unit naming the plan's units and a dependency cycle; readyUnits are the pending units whose in-play dependencies are done — a dependency outside the selection is the requester's assertion that it is done; startUnit refuses a unit outside the selection, one already started or settled and one not ready, each by name; settleUnit(…, "done") frees the dependents and settleUnit(…, "failed") — any ending but a merge: a conflict, a cap, a stop, a refused merge — blocks them transitively and leaves every other ready unit in play; cursorFinished when nothing is pending or running. The unit pipeline. openUnitPipeline(input, at) over { unit: { id, branch }, repo, base, caps, merge: runner | person, resume? } (the children's asks and floors come from src/core/budgets.ts, never from the input) — a resume (item 10: the pull request of ship's own the requester named, its head and url) opens the pipeline at its first review round with the pull request adopted and no pre-check, branch or round 0; nextAction(state) names the step the machine is at — pr-check first (<unit>/pr-check: what already heads the branch), branch (the head branch from the base), spawn (the preset, the round, the child's budget, the brief), wait (run-finished-<runId> with a timeout of one chunk), read-record, pr-check again after round 0 and every findings step, merge, sleep, end — under a step name <unit>/<round>/<kind> (<unit>/0/coding, <unit>/1/review, <unit>/1/findings; the waits, reads and busy waits numbered under it, <unit>/pr-check, <unit>/branch, <unit>/merge/<n>), so a spawn's idempotency key is unique across a plan's units and never carries a colon; applyReturn(state, ret) is the transition, and an answer for any step but the one the machine is at — a duplicate run finished, a replayed spawn — changes nothing, so every return is safe to apply twice. The rounds follow items 3 to 9 over the returns: first the pre-check — a pull request already merged heading the unit's branch (a person's merge, or an earlier attempt's that died after it: what a re-issued plan meets) ends the unit merged at once with by: other, no branch created and no child spawned, so the cursor marks it done and its dependents run; none or open proceeds — an open pull request is round 0's to rebase and re-describe, adopted at the round's own pr-check, never here, unless the pre-check's entry facts show it heading the branch's own tip (branchHead, the ref's tip read beside the listing — issue 1689) or the unit's row carries lastPush (the head a previous attempt's review_pending ending named) and the open pull request still heads exactly there: nothing to code, so the pull request is adopted and the attempt starts at the review round — or straight at the merge decision when the entry facts say the bot's approval stands at that head over green checks; then the branch, then round 0 as a coding child briefed with the unit's contract and the rebase onto the base; a spawned or alreadySpawned answer starts the round's wait — one child, never a second — sliced: each wait is one chunk of five minutes (WAIT_CHUNK_MS) with a read-record between the chunks, the slices summing to exactly the child's budget plus five minutes (the last the remainder) and, past that, an overdue child asked about every chunk, never in a zero-length wait — so an event the engine never delivered costs a chunk, not the budget; the budget being the preset's own clipped to the pipeline's remaining wall clock (item 8's clip, never under the two minutes a spawn accepts) — and a coding child's clipped further to leave the loop's reserve (SHIP_LOOP_RESERVE_MS: two review rounds and one merge poll, 11 minutes; validateShip holds ship.maxMinutes to at least SHIP_MIN_MAX_MINUTES, 14) so a child that uses its whole directive still hands the review its time, and a findings child's clipped to leave the re-review and the merge poll (SHIP_FIX_RESERVE_MS, 8 minutes), never the whole loop's, since the first review has already run by then; a busy answer waits a chunk for the live run's end and asks again, or ends at the wall-clock cap when the clock ran out meanwhile — and a wall-clock cap entering a review round with the child's pull request standing ends the unit review_pending instead: the pull request and the child's last push named, the review the only thing missing (both cap endings carry the budget split — coding, review, waiting — ShipBudgetSpent, accrued as each answer moves the clock); a refused answer ends the unit with the authorize stage's own name and a failed one as an abort; every wait — an event or a timeout alike — is confirmed by read-record: a run still live waits again under the next step name and advances nothing, a coding child that ended failed or interrupted is followed by the recover pr-check — the check carries the dead run's id and the bot opens the pull request from the pushed branch itself through item 10's open-or-edit (the title from the child's submitted description when the record holds one, used as is — the submit tool's gate already judged it — else the unit's title as a conventional fallback: the type its own leading word when the release config knows it else chore, the scope the plan's area — the first plan-id segment naming a code-map scope, omitted when none does — and the whole line cut to the 72-character cap at a word boundary, never the unit heading verbatim, whose U<n>: fails the required title check and costs a fix round on a title, not on code (issue 1877); the body from the description when the record holds one, else a minimal body naming the unit) — after the identity rewrite (record 0062; agent-coding.md item 2) verified or rewrote the branch's commits over an empty start state (every earlier round's commits were rewritten before their own pull request opened, so they pass), an unreadable rewrite answering github_unavailable, never an open over unverified identities — so pushed work is never stranded: an open answer at a new head carries the round on to review, and none ends the unit with the child's own reason — interrupted with the ship-restart note of item 10, failed as an abort that repeats why nothing was recovered (the answer carries it: unrecovered: no_commits when GitHub refused the create over an empty branch, no_base when the instance names no base to open against and no create was tried; any other failure of the create is github_unavailable, asked again, never none) — never the budget clip; a findings child that died at the head the review already read ends the unit the same way, with the child's own reason and never as the round's inaction; a review child's interrupted ends the unit with the same note at once; a finished coding child is followed by pr-check — no pull request heading the branch is the round-0 terminal at round 0 (already_landed when the child's handoff names where the scope landed and the check read no commits over the base, item 12; held with cause blocked when the handoff records a deviation — the round concluded with an answer and is never renewed, issue 2086; else the renewal decision or the abort) and an abort after a findings step; a merged one and no open one (the merge landed during the round: a person's, or an earlier attempt's the child found nothing left to ship for) ends the unit merged with by: other the same way, the round noted completed; the open pull request's number and head are adopted, so one closed and reopened under the pipeline is followed — and a findings step whose head is the one the review read aborts unless every finding of that review was declined on the record (item 7); a review round is briefed with the pull request, the head to pin and, from the second round, the previous review's run and the coding run that answered its findings (prior.codingRunId), and its verdict settles the round: none is the no-verdict abort (item 5), request_changes the findings step (item 7: a findings brief naming the review run, spawned as the coding preset under the review round's index, the dispositions its run recorded matched to the round's ids by matchDispositions when its record is read, item 6) — or the round cap when the rounds are spent, the wall-clock cap when the reservation fails (item 8) — and approve merge-ready, unless the approval did not post, an honest abort (item 9); a stopped child ends the unit as an operator stop naming the mode, honored around a posted verdict and never over one (item 7); a failed child aborts the unit only once the recover pr-check found nothing pushed. Under merge: runner an approve is followed by the merge step at the approved head — merged ends the unit merged with by: runner, pending waits on the intake's checks-settled-<head> event at the approved head (http-ingress.md item 12) under a fallback timeout bounded by the remainder of MERGE_WAIT_MAX_MS, then asks again under <unit>/merge/<n>, refused ends it merge_refused naming the reason, and an approve with no known head to merge at is merge_refused too, never a person's merge-ready; under merge: person the unit ends merge_ready and a person merges, as item 9 has it. Every transition reports the round boundaries the card and the run stream take (CoordinatorNote: started, pr_opened, approve, request_changes, no_verdict, aborted, stopped, held — item 12's vocabulary, drawn by shipRoundHeader) and the ending; renderUnitReport(state) renders the thread's report in the pipeline's own words — merge-ready with the verdict and the declined findings, the cap with its declined/unaddressed split and the budget split, review pending with the pull request, the head and the budget split, the stop, the abort, the interrupted note — plus the merge's (merged — the runner's under plan:merge, or by: other: already merged, naming the merge commit and when GitHub merged it and never that the runner merged; merge_refused) and the refusal's. The Workflow that drives this machine (driver.ts: the machine's steps under its names, one retry policy, every wait confirmed by read-record, a task row's resume and a unit row's lastPush read into the machine's input, the last coding child and a review_pending ending's head named on the unit's end beside the pull request — and each wait chunk armed as three waitForEvents, the deploy-roll signals beside the finish: child-interrupted-<runId> under <step>/interrupted settles the wait like the finish so an interrupted child ends the round at once with its own reason once read-record confirms it, and child-resumed-<runId> under <step>/resumed[/n] is consumed and re-armed, never a settlement — a resumed child keeps the wait, whether the bot's reattach path or the plane's restartOf claim sent it, so the duplicate is harmless; and a restarting ending keeps the wait only through its persisted claim grace: read-record searches for a same-instance-and-key successor first (a successor before or after expiry wins), then answers an interrupted record carrying restarting: true as still running before restartUntil; with none at or after the deadline it answers the original interrupted ending and its recorded cause, so a whole-process death ends the unit on the interruption note instead of its wall-clock cap, while a legitimate resident replacement still resumes (run-history.md item 47a; record 0064)) and the bot routes it asks are http-ingress.md item 9; what hands a request to them is item 16. The merge is item 9's merge step under plan:merge (authorization.md item 2): merge on UnitPipelineInput comes from the plan route — the instance's field, read person when the answer lacks it — never from the branch's name, so a plan's unit is merged by the runner once the review approved at its head and the checks are green, and ship on a task string still never merges.
  7. Every agent:ship request hands to the plan runner (src/core/coordinator/handOff.ts, src/core/dispatch/ship.ts; record 0031; the runner itself is http-ingress.md item 9). There is no switch: the ship config block holds maxRounds and maxMinutes and refuses any other key at load by name (validateShip) — ship.coordinator, the key that once chose between the runner and an in-process loop, with a line saying the runner is the one implementation and pointing at the migration notes. The branch runs the preflight (item 2's compound gate, item 3's branch name, item 10's entry checks), registers the run record and card — the run created with hosted: true and its label on the registry's meta, and claimed on the ledger under the host key (the thread key plus #host, run-history.md item 29; record 0060) with the ledger meta's threadKey the thread itself, hosted and the label, no seed and no session, so every surface lists the pipeline under its conversation while it occupies no thread on the ledger's occupancy read; one pipeline per thread: a host-key claim answered thread-live — another pipeline is live in the thread — is refused by name (ship_thread_live, "a pipeline is already running in this thread") with nothing handed to the runner, while any other untracked answer (no ledger, missing routes) hands off exactly as before — and hands the request to the runner: the request names a plan (plan <path>.md [units U<n>, …], parseShipPlanRequest over the directive-stripped text) or a task; a plan is read at the pull request's base ref through the App (GithubApi.readFile) whole, up to PLAN_MAX_CHARS (2,000,000 characters, ten times the model tool's file clip; the read names its own bound because the tool clip once shortened the program plan to its first 25 units and the seed answered "no unit U38"), a plan the read still cut refused naming the bound with nothing created, and the brief for a unit reads the same way and refuses the same cut; every referenced spec and the first available AGENTS.md or CLAUDE.md is also read to PLAN_MAX_CHARS and refused by file and bound if cut, while an absent spec or rules file stays distinct from a cut one; the plan is parsed with item 15's parsePlanGraph and narrowed by item 15's cursor to the selected units in the plan's order, and a task becomes a generated one-unit plan: the door guard — an operator-bound ship request runs generated plans alone: a bound seeded form (plan <path>.md) is refused naming agent:ship, since seeded units merge under the runner’s grant and only a typed directive may start one; an operator-bound task hands off merge: person. The unit's text is the request as written (shipUnitText, the same text in the hand-off's unit row and in the child's contract and prompt): only the mention and a leading in <repo>: go, and Slack link markup unwraps to the bare url so the child reads the address rather than Slack's elided display label — every url, issue reference and slug in prose stays. Item 10's probe (shipTaskText, which strips every url to ask whether a task is there at all) decides only that a task exists; it is never the unit. Its id is generatedPlanId(text, threadKey) — at most 24 normalised characters of the task's text plus a six-hex-character hash of the text and the thread key, fitting PLAN_ID_PATTERN, so two requests sharing a prefix differ and a re-issue is deterministic — with one unit U1 on plan/<id>/u1 (item 3), run in the requesting thread; the instance is marked plan: { id } with no path, and every reader keys on that mark, never on a unit name. A resume at review (item 10) is that one U1 unit with the pull request on its row (CoordinatorUnit.resume), on the pull request's own head branch and base. The bot writes the instance record — the requester, channel and thread, the card's handle, the caps as the profile gate clipped them (item 8: the rounds cap from the block, the wall clock the profile's minutes), the run id, the card's label and merge, who merges the units' pull requests (runner for a seeded plan, person for a task, whatever the request's words say — item 9's door checks it; run-history.md item 49) — under the plan's id (planInstanceId, plan-<plan-id> — seeded or generated alike; nothing is keyed by the run id), then one unit row per selected unit with its branch and the dependencies the plan states (run-history.md item 50); when that unit's own text asks to write a decision record, admission also reserves the next number unused on origin/main and unclaimed by every open pull request, stores it as record: NNNN on the row, and the composed coding brief begins with the same record: NNNN — concurrent admissions serialize to consecutive numbers, and a re-issue carries the prior row's number rather than scanning docs/decisions/ — the rows are the seeded runner's live selection: its sequencer reads them back at every unit boundary (http-ingress.md item 9), so a row appended to a live instance joins the walk after the current unit and a row removed — a unit merged by hand — is walked as merged, a dependency on it counted done — and, for a generated task whose triggering message carried accepted inline images or documents, appends one steer seed event to U1 AFTER its row is durable and BEFORE the Workflow starts. The seed uses a stable id under the instance and unit; an append retry returns the original sequence, so a failed create and re-issued hand-off never duplicate the event. The first coding spawn receives its media through the ordinary foldThreadAttachments path; when Slack supplied the accepted file's by-reference source, that fold also promotes it to the child's staging list, so the artifact path writes attachments/0-<name>, records it in the file catalogue and appends the path to the prompt. The spawn consumes the seed only after the child registers; a failure before registration leaves it for the retry, a review spawn leaves it alone, and after successful consumption a later coding round sees none. The event cap keeps its existing all-or-nothing dropped count and prompt note, applied by the Worker client before the HTTP request-body fence and again by the Worker after assigning the sequence; a task without accepted media appends nothing, and a seeded plan never copies one request's media across its units — then asks its own shim for the Workflow instance: POST <PUBLIC_BASE_URL>/admin/coordinator/instances with the coordinator bearer of the token map the bot holds (createInstanceViaShim; the answer read by readCreateInstanceAnswer, the reverse of the shim's createInstanceResponse). The answer and the reply say where the plan runs — the instance, the plan and its units in dependency order (a task: its branch in this thread; a resume: that the review loop resumes at its next review round with no coding round first), that each of a seeded plan's units runs in a thread of its own in the channel under the requester's grants (a one-unit plan's unit runs in the requesting thread), that the card follows the plan and its summary lands in the requesting thread — and the card closes ✅. The thread choice keys on the unit count (record 0055 item 3's wording, decided by count): a plan whose selection is exactly one unit — generated or checked-in alike — runs that unit in the requesting thread: unit-start opens no thread (a checked-in plan's board issue is still looked up; only the generated mark, which means the task wording, skips it) and finish posts no summary there, the unit's report having already landed; a plan of two or more units opens a thread per unit and finish posts the plan's summary into the requesting thread. unitRowOf answers the instance's rows beside the matched one and unitThread decides from their count, so every route's thread reading agrees (src/channels/adminCoordinator.ts). The hand-off keeps the run live (record 0060): on a tracked, taken hand-off the branch skips the finish, finishing and the seal — the registry row stays unfinished and the ledger row live — sets the row's state hosting: { instanceId, until } with until the hand-off time plus the instance's caps.maxMinutes plus one hour (HOSTED_DEADLINE_MARGIN_MINUTES), and publishes a second run_meta { instanceId }, the fact every reader of a run's instance id resolves from the last run_meta carrying one; a hosted tracked run reports resumable, so SIGTERM hands its row to the next generation and the drain neither waits on it nor tombstones it (run-history.md item 39); a refused hand-off, and every other exit after the host-key claim that hands nothing off (a throw included), still finishes as today — no host-keyed row outlives a request that handed nothing off, so the thread's next ship request claims the host key — and an untracked hand-off (no ledger, missing routes) finishes as today; the runner's steps call back into the bot from there, and its finish writes the plan's story under the same run id (http-ingress.md item 9). The hosted parent's hard stop stops the runner too (record 0060; issue 1924): the seal (live-view.md items 10 and 16) writes a stop mark on the instance row (CoordinatorInstanceStore.markStopped → CoordinatorInstance.stop, the state Worker's /runs/coordinator/stop; required before sealing — a missing, refused or unknown mark without an exact committed readback leaves the parent unfinished and returns unavailable), and the runner reads it back and honours it by ending: the bot's plan answer carries stopped: true, on which the walk — before every unit start — ends every unit not yet ended stopped with the reason on its row and starts nothing more; the spawn route refuses stopped (a terminal refusal, never a retry) before any child is dispatched, ending the unit stopped; and the read-record answer carries the mark beside a finished child's record, so a unit whose child is live when the stop lands ends stopped when that child ends, whatever the child's own status (the child itself takes its own hard stop as today). A stop first seen once every unit has ended changes nothing, and the sealed parent's answer says the runner was stopped. plane stop <instance> is the same stop as one move (record 0064's runner_stop move): a destructive command under runs:write (plane.stop, src/core/commands/plane.ts over PlaneService.stop) that terminates the runner instance and ends its live children together, so no orphan is left — the stop mark on the instance row first (best effort, the report says when the runner may still be walking), the hosted parent's hard stop (recorded on the parent run by the seal's events), then every live child tagged with this instance in its unit and review threads ended hard; unrelated runs in those threads keep running, each stop recorded on the child's own run in its unit thread; an instance the caller's predicate does not admit is unknown, like a run they may not see, and a process without the run-history stores refuses by name instead of half-stopping. Every refusal is a reply and an aborted hand-off (the card closes ⚠️; the run record still says completed, because the request was answered), never a throw, and nothing is created on one: no base branch known; a plan file the repository lacks at the base, naming the path, the ref and GitHub's words; a plan file whose name is not a plan id; a plan with no unit headings; a unit the plan lacks or a dependency cycle, in the cursor's words; a process without a durable instance store (NullCoordinatorInstanceStore — run history not on the state Worker); the shim's duplicate_instance for an id the state Worker had no record of; the shim's create_failed, a shim that could not be reached, PUBLIC_BASE_URL unset or a token map without a single coordinator entry, each by reason — a deployment without the runner's prerequisites is told which one is missing and runs ship no other way. The records are written before the create. Plan selection retains numbered admission compatibility: no record under plan-<plan-id> is a first attempt; a record there is an earlier attempt's, and the shim's status of the latest attempt (GET /admin/coordinator/instances/<id>, the highest suffix with a record) decides before anything is written — still running (queued, running, paused, waiting, waitingForPause) refuses the request naming the instance and its status, and its rows, a live runner's threads, rounds, pull requests and endings, are never touched; no such instance (the leftover of a create that failed after the records were written) is replaced with this request's record and rows under the same id and attempt (replace, run-history.md item 49), the units earlier attempts merged skipped there too, and the reply says so; ended (complete, errored, terminated) is resumed as the next attempt under plan-<plan-id>-<n> with attempt on the record, rerunning only the units the earlier attempts did not merge (a merged ending on any attempt's row), each dependency kept on the row as the plan states it so a merged one counts as done, the reply naming what is left and what was merged; every named unit merged already is a refusal (a generated request already merged is refused as merged already; an existing generated unit with publication evidence retains its original owner and uses original-unit recovery rather than another attempt); a status the bot does not read as ended, a shim that could not say, and a duplicate_instance for an id the state Worker had no record of (a store wiped or restored) are refusals a person decides, nothing written. Thread follow-ups steer the runner's children like any run (thread-admission.md item 2): a reply in a unit's thread reaches the child in flight there; the requesting thread's own dispatch returns once the hand-off answered — the admission slot is released as for any run — while the hosted parent stays live and occupies no thread (thread-admission.md item 1: the coordinator's spawn check skips hosted views), so a task's U1 child is admitted and tracked in the requesting thread beside it.

A refused unit registration retains its exact owned, stale, incomplete or unavailable reason, operation, instance id and unit count. The Ship run records these bounded facts in a ship_admission note before closing; diagnostics do not start a Workflow or change the refusal.

  1. The unit is the reading unit (record 0034, "The unit is the reading unit"; src/core/unitRuns.ts, RunsService.listUnitRuns in src/core/runsService.ts, runs unit in src/core/commands/runs.ts). A unit's story is read from one place. Its name outside its instance is the unit key <instance>:<unit> (unitKeyOf, parseUnitKey, UNIT_KEY_PATTERN in src/core/coordinator/contract.ts) — the prefix every child's idempotency key carries before its /<round>/<kind> step, so a unit is addressed by the words its runs are stamped with. runs unit <key> (GET /api/runs.unit?unit=… on the dashboard's surface, the same on MCP, the CLI and chat) answers { unit, instanceId, threads: { coding?, review? }, rounds, runs }: the unit row's two threads and its round boundaries as the runner reported them (item 12, run-history.md item 50), and the runs of both threads — live and finished, through the store's thread listing (agent-conductor.md item 10) — each a RunView the run page renders plus { round, thread: "coding" | "review" }, in time order: coding 0, review 1, coding 1 (the findings step, item 7), review 2 … as they started, the round vocabulary the runner's own (round 0 is the coding round; review round n and its findings step share n). The cut (unitRunsOf): each thread's rounds begin at the earliest entry the row records for that round index and agent (roundBoundaries; the runner reports a round's started before it spawns the round's child, so a run that started at or after a boundary is that round's, and a row missing the started note still cuts where the round began); a run belongs to the last boundary at or before its start; a run that started before the thread's first round is not the unit's (a task's thread is the requesting thread, whose past is not the unit's); the pipeline's own record (agent: ship, written in a task's requesting thread when the instance ends) is never a round's run; a thread the row does not name contributes nothing; a unit with one thread cuts that thread's runs by agent, the review agent's at the review rounds and the rest at the coding rounds, a row written before record 0055 that names a review thread cuts its runs there, and a unit not started lists nothing. Two runs that started in the same millisecond order coding before review, then by id. not_found — unit not found — for a key that names no unit, a malformed key, a process without the coordinator's records, and for a reader outside the predicate (run-visibility.md item 9); the text surfaces render one runs list line per run with its round and thread under the unit's threads. A conductor's children are the sibling read (agent-conductor.md item 11). The view also carries the row's readable facts (UnitFacts, unitFactsOf: the unit's id as the plan spells it, title, branch, pr, issue, sourceUrls for its two threads, ending, startedAt) and its instance's (InstanceFacts, instanceFactsOf: repo, base, plan, attempt, label, the parent record's runId, createdAt) — what a page states beside the runs, never the requester's ids, which the runs carry under the predicate. The unit page (live-view.md item 28) is GET /runs/unit/<key>: this listing drawn as one page — the facts in its header, the runs in round order as rows that open to their own timeline in place, a search over one session at a time, keyed by the unit's working lanes (session-log.md items 11 and 13) — and a reader the predicate admits to nothing of the unit gets the run 404, as the route gives not_found. The parent record is the pipeline's own stream (record 0060): the runner's unit-start, round, unit-end and finish routes write the pipeline's facts to the hosted parent through one hostPublish (src/channels/adminCoordinator.ts), the run id read server-side from the instance's own runId — a route body naming any other run id publishes nothing and answers not_found, byte-identical to a missing run — and every write moves the row's hosting.until forward by caps.maxMinutes plus an hour (record 0046's lease shape). When this generation hosts the run: unit-start publishes ship_unit { state: "started", threadKey, lead }, round publishes ship_round and a ship_unit carrying the round's outcome as the unit's state, unit-end publishes ship_unit with the ending's kind and report (the report still replied through the handle), and finish publishes the plan's summary as the answer, finishes the registry row and seals the ONE record — the run's own events, in seq order — through the ledger sink under the metadata's thread, releasing the host key with the row. When a ledger row for the run is live under another generation, each of the four routes answers 409 { ok: false, error: "not_host", at } and writes nothing (a passing condition the driver re-asks on, http-ingress.md item 9); when no row exists anywhere — an untracked hand-off, whose parent finished at the hand-off — nothing is published, finish writes no record (the parent's already exists) and the routes answer as before. drawCard keeps redrawing the Slack card from the unit rows. The pipeline's record names its instance in its run_meta (instanceId, on the second run_meta the ship branch publishes at the hand-off — the LAST run_meta carrying one is the fact every reader resolves), and its page reads the instance's unit rows as UnitFacts through RunsService.listInstanceUnits — admitted by the instance's requester and channel exactly as a unit not started is; empty for an unknown instance, a process without the coordinator's records or a reader outside — and lists them under Units, each opening its unit page. The /runs index nests the instance's unit runs under the hosted parent's row, and each child's page names the pipeline and its unit (live-view.md item 33).

  2. The findings ledger per pull request is a read over the records (src/core/findingsLedger.ts, RunsService.listFindings in src/core/runsService.ts, runs findings in src/core/commands/runs.ts). A person reading a unit can see, per finding, when it was raised, what the coding run did about it and whether the next review agreed — from the records alone, never a store of its own. The runs are gathered from two sources under the reader's runs:read predicate (run-visibility.md item 9), so only runs the reader may see enter the join: every record that names the pull request (ListRunsOptions.pr → RunListOptions.pr, run-history.md item 58 — the coding runs whose post-step opened or edited it, pr, and the reviews that posted to it, reviewPost.target; a review whose post was skipped names nothing there), and, when one of those runs is a child of a coordinator instance whose unit row names the pull request (item 17, run-history.md item 50), that unit's runs cut at its rounds (unitRunsOf), so each run carries its round and a review that posted nothing still enters through its thread. ledgerOf(runs) is the pure join: the runs in finishedAt order (then startedAt, then id; a live run contributes nothing yet), one row per finding id — the id is the key across rounds (item 6), never merged, a later verdict's different title kept under it — carrying the words of the latest review that listed it (severity, file, line, title), where it was first raised and last seen (run id, the reviewed head, the round when a unit row supplied it), the latest disposition recorded after it was raised (kind, note, run id; within one run the last entry for an id wins) and a status that is a function of the sequence alone: open — raised by the newest review with no disposition after it; awaiting re-review — a disposition recorded and no review after it yet (a decline-only round is re-reviewed at the same head, item 7, so a later review counts whatever head it read); fixed — disposition fixed and the newest later review did not list the id; conceded — disposition declined and the newest later review did not list it; re-raised — the newest review after the disposition listed the id again, reRaisedAfter naming the kind it answered; not re-raised — raised at an older head, absent from the newest review and no disposition recorded, so the record does not say whether it was fixed or conceded (a person's own fix, a run that recorded no dispositions); unknown id — a disposition names an id no review issued, kept as submit_dispositions recorded it (item 6) with no severity, file or title. A review whose verdict lists no findings, or carries no findings array, is a review that listed none: every earlier id is settled by it. A disposition recorded before the id was first raised answers nothing. Rows come in the order ids were first raised, unknown id rows last. runs findings <owner/repo#N | pull request URL> (runs:read; enabledWhen: runHistory — the ledger reads finished records, so without a history store the command does not exist, the one runs.* command that is gated, command-registry.md item 28) answers { repo, pr: { number, url? }, unit?, runs: [{ id, agent?, startedAt, finishedAt, head?, round?, verdict?, findings?, dispositions? }], findings: FindingRow[] } on every surface; not_found — no runs name this pull request — when no run the reader may see names it, one answer for an unknown pull request, one no run worked on and one whose runs are all outside the predicate. A finding's title and a disposition's note are the reviewer's and the coding run's own words: they leave the JSON surfaces wrapped as untrusted content, as a search snippet does, and the text renderers unwrap them for the person reading the table — a header naming the pull request, the unit when one names it and the status tally, then one line per finding (id, severity, file:line, title, status, note), aligned columns for the terminal and one bullet per finding for chat. The page (live-view.md item 28): the unit page draws the ledger as a Findings block when its row names a pull request, each trail stop opening the run it names in place, and a run page whose record names a pull request links Findings · <rows> to that block — both seeded by the same read under the viewer's predicate. A person's override of a status (a finding judged irrelevant) is a write with a store and an authorization action of its own and is not built here; the ledger reads records alone.

  3. The pipeline's standing is one fold (record 0065; src/core/pipelineStanding.ts, node-free and pure). pipelineStandingOf(events) is the ONLY reader of the hosted parent's ship_round/ship_unit events for standing: the units in order of first appearance — each with a stage from the closed vocabulary coding | review | fix | approved | merge-ready | merged | idle | ended, its latest round index, its segment count, its pull request (the latest pr seen), its thread key and the stamp that set the stage (since) — plus the rounds no unit names (unnamedRounds, the shape of records written before ship_unit) and the changes the fold made (the pipeline page's log). Binding is the round route's own batch contract: a ship_round binds to the ship_unit published next at the same stamp, and that companion contributes only its pull request and thread key, NEVER a stage — the round's (agent, outcome) sets it (coding started → coding at index 0, fix at any higher index; review started → review; coding pr_opened → review; review approve → approved; request_changes and checks_failed → fix; coding completed → merged; continued → coding with the segment count raised; idle → idle; aborted, stopped, held, no_verdict and any word the fold does not know hold the stage — the ending will say). An UNBOUND ship_unit whose state is an ending kind closes the unit (merged and already_landed → merged; merge_ready → merge-ready; idle → idle; continued is not an ending and holds; blocked — a unit status the driver posts for a unit that never started — and every other kind → ended with the kind as detail); an idle or closed unit reopens at its next started as the next segment, and a round-outcome word on an unbound ship_unit (a route that skipped its round) holds the stage. The fold is total over any event list and never throws; both mapping tables are pinned to their unions (ROUND_STAGE to ShipRoundOutcome, ENDING_STAGE to UnitEnding["kind"] plus blocked), so a word added to either fails the build before it ships. summaryOfStanding gives PipelineSummary { current, counts, total, lastRound? } — the open units whole, zero-filled stage counts, the units SEEN (the plan's total is not on the parent's stream and is not returned), and an older record's newest unnamed round — the shape the three row sources carry (run-history.md items 2 and 19) and runs get prints under the meta block on every surface. The runner's round route accepts every ShipRoundOutcome: ROUND_OUTCOMES in src/channels/adminCoordinator.ts is pinned by a type-level exhaustiveness check, so the route and the union can never drift apart again (a renewed round 0's continued once threw in the driver; the idle outcome has no emitter until record 0051's wake lands). The shadow diff (scripts/pipeline-standing-diff.ts, run by hand over sampled records and their unit rows) compares each finished unit's final stage with the card's ending or idle word under the record's mapping; its count is record 0065's acceptance receipt — one disagreement is a fold bug, a class amends the record's mapping before the words reach a reader.

  4. The sweep a person runs (record 0071, mechanism two; src/core/pullSweep.ts, src/execution/gitRebase.ts, pulls rebase in src/core/commands/pulls.ts). One registry command over the pull request noun — pulls rebase [pr] [--repo owner/name], on every surface the registry serves (command-registry.md) — walks the open pull requests the pipeline owns in the repository, or the one named (a number, #N, owner/name#N or a GitHub URL), and rebases each DIRTY one onto its base with the two-rung resolver. Rung one is git alone (rebaseOntoBase): fetch the base, rebase with whatever merge drivers the repository itself declares in .gitattributes and with git rerere enabled and auto-staging, so a resolution made once replays on every later rebase of the same hunks; a failure that is no conflict stop at all — a dirty checkout, a --continue that cannot advance — throws git's own words and surfaces as the pull request's error line, never as a conflict; after a clean rebase, git range-diff against the pre-rebase head (patchUnchanged) — judged on the commit-pair header rows alone, anchored to the marker column so a = inside a subject or an interdiff line never counts: when every header maps =, the patch is byte-identical, the existing approval carries to the new head and no re-review is requested; the sweep force-pushes with lease (forcePushWithLease — a remote that moved under the sweep refuses the push) and regenerates the pull request description's anchors. For a pull request no live runner owns, rung two, only for a conflict git leaves, is ONE bounded model round — a coding child on the pull request's own branch with the thread's context and the repository's AGENTS.md, a short lease and a per-pull-request spend cap (PULL_SWEEP in src/core/budgets.ts) — and a changed patch then gets a delta re-review; the round is the service's seam, filled by the wiring, never started by the command handler (AGENTS.md invariant 3). A command sweep defers before touching git when a live runner owns the pull request. That runner invokes rung one in runner mode: clean and unchanged carries approval back to checks, clean but changed returns to its review loop, and every conflict returns to a fresh coding rebase round carved from the runner's remaining lease. The unowned sweep's decision table is pure and total (decideSweep): not dirty → skipped (a stale-but-clean pull request is never rebased — it merges as it is; an unknown state — GitHub still recomputing after a base move — is named, mergeability still computing — run the sweep again in a minute, never claimed current); clean and unchanged → carry; clean but changed → delta re-review; a conflict → the model round; a conflict whose round is already spent → the ending with the conflict named in one line, no retry loop. One process-wide throughput queue permits one rebase in flight per repository across command, authenticated watch and runner services; a second sweep of the same repository queues, while different repositories remain independent. Canonical per-PR admission and unknown-effect protection remain separate from this queue. The resolver never knows what a generator or a formatter is: what a model round may regenerate is only what the repository's own AGENTS.md names. The answer is one line per pull request in user words (#2060 rebased, patch unchanged, approval carried / #2060 conflict in provision.ts, a fix round is running / #2057 skipped, already current). The production wiring (issue 2067) fills PullSweepDeps through CoreCommandWiring.pulls in src/index.ts (src/core/pullSweepWiring.ts, buildPullSweepDeps): the listing is the repository's open pull requests whose head is a plan branch on the base repository (listOpenPullRequests over src/execution/githubPulls.ts), each one's mergeable_state read fresh off its own facts (fetchPullRequestFacts — never the listing's, stale the moment a sibling merges) and the approval read off the posted reviews pinned at the head — a genuine APPROVED state (a person's, or the auto-approve workflow's where the repository opted in) or the bot's own review there whose body starts with LGTM:, identity-checked as the merge door reads it (reviewPostedAt), since the pipeline posts its approvals with event COMMENT; an unknown bot identity counts only APPROVED states; rung one runs in a throwaway clone of the pull request's branch (src/execution/sweepCheckout.ts, createSweepGit — the App token rides an Authorization header stored only in the clone's config (sweepAuthHeader), never the remote URL git quotes verbatim in its failure messages, and the directory is removed when the pull request's walk ends: a conflict, the push, or an error), and a git failure's line passes redactSecrets before it surfaces — no credential shape reaches chat, the CLI or MCP; the approval carry is the bot's LGTM: review pinned to the new head — exactly what the merge door and the auto-approve workflow read — the anchor regeneration rewrites the description's blob permalinks to the pushed head, and the unowned sweep's delta re-review and one bounded model round go through dispatch() as the requester on the sweep's own http:pulls:<owner/name>#<n> thread, the round under budget: of the lease's minutes (PULL_SWEEP.spendCapUsd has no per-run lever on dispatch() today — the lease is the round's bound). Model-round admission and charges survive restarts in the original unit's durable effect and rounds. The repository queue is process throughput only; it never grants write authority or releases an uncertain effect. Runner mode never reads or spends that lifetime flag: each distinct conflict buys its own lease-bounded round. The merge door returns a typed conflict to the live runner, and the merge: person tail re-enters that same runner before publishing an ending; pulls rebase retains the hand-rebase remedy only when no live pipeline owns the pull request (item 9).

  5. Watch until merge, as a setting (record 0071, mechanism three; src/core/mergeWatch.ts, the push intake in src/core/coordinator/checksIntake.ts). Off by default; org-level with a per-repository override on the config surface that exists — config set org|repo --pulls.watch on|off, the caps beside it (routing-and-config.md item 32) — resolved per repository by ConfigStore.mergeWatchOf. When on for a repository, a unit whose pull request reaches merge-ready stays on it with no sandbox and no child alive while it waits: the unit is registered in the merge-ready book beside the merge-wait book (MergeReadyBook — the pull request, head and base, the instance and unit, the thread and the requester; in-memory like the merge-wait registry, a restart loses the waiters and the waiting unit re-registers), and on each push-to-base webhook (handlePushIntake: GitHub's push event on the same signed /webhooks/github route, a branch ref only) that leaves the pull request DIRTY — mergeable_state read once GitHub has recomputed it, never on a clock; a still-recomputing unknown stands until the next push — the watch runs the sweep's two-rung resolver (item 20) for that ONE pull request, fixing tests and re-requesting review as the sweep's rungs do, until the pull request is merged or the person ends it. The caps: one rebase in flight per repository (pulls.rebaseInFlight, default 1 — a second DIRTY pull request queues and runs when the slot frees, in arrival order) and a per-pull-request spend limit (pulls.spendLimitUsd, default the sweep round's cap; the production spend read counts whole rounds — a spent round reads as the full round cap — so the default buys one rebase round per pull request and a finer value waits on a per-run spend lever) — at the spend cap the DIRTY stands and the unit's card names the sweep a person can run (spendCapLine: pulls rebase <owner/name>#<n>). A merged fact, whoever merged, ends the unit merged by other: the watch reads the merge off the pull request's own facts, sends pr-merged-<instance>-<unit> to the waiting instance (sendPullMerged, best effort like every relay send) and drops the book's entry; a closed-unmerged pull request is dropped without a word. A stale-but-clean pull request is never rebased: DIRTY is the only trigger the watch answers, and a base push that leaves the pull request clean stands. With the watch on, the waiting unit's row keeps no ending while it waits, so the unit owns its thread under the unfinished-unit clause (thread-admission.md item 9) and an addressed reply is its turn, never the router's; with the watch off, merge_ready stays an ended kind exactly as today. The live runner is the sole owner before the watch's remedy is considered (record 0071 criterion 5): the merge door returns a typed conflict to that runner regardless of the watch setting, and the machine immediately re-enters its rebase step; a command sweep on the same pull request defers without touching git. The watch remains the mechanism for a unit already registered in its merge-ready wait, while the sweep's hand-rebase remedy remains only for a pull request no live pipeline owns (item 9). [gap] The driver's watch loop — the unit's own registration into the book at its merge-ready ending, the wait on the merged event, and the merged by-other ending composed from it — is not wired yet: the book, the intake, the watch service, the caps and the setting land here, and the pipeline still ends merge_ready under either setting until the loop lands.

  6. A generation that starts draining admits no new pipeline child, and every resumable child already across the boundary is handed off. draining flips synchronously at SIGTERM/SIGINT before the Slack socket closes. From then on the coordinator's spawn route answers stamped 409 { error: "queued" } with waiting for the next bot generation; it checks both before its reads and at the dispatch boundary, so a request that was awaiting the instance, unit, history, or brief cannot cross after the signal. A synchronous admission permit spans dispatch through child registration, and the drain counts that permit: a signal after the final check cannot exit the process before the registry can hold the child. The durable driver treats a queued answer as a passing condition and re-asks the same step, so the replacement generation launches it once. The drain marks the write-through's resumable live runs handoff immediately and on every drain poll: a child admitted just before the signal but reaching the ledger after the first pass is found by a later pass and reclaimed by the replacement instead of holding the old process for fifteen minutes. Already-handed rows are never marked twice; an untracked run remains explicitly held as not handed off rather than being claimed recoverable.

Roadmap (gaps) ​

  • [gap] Historical recovery has no durable cumulative-spend/cost-cap carry through its checkpoint and no retained typed check-findings set for an approve followed by failed checks. These cases stop at named refusals; the admission audit is not a budget reconstruction, a new lease, or functional live acceptance. Historical stopped-segment wake answers also lack their resume timestamp and cannot establish a lease origin.
  • [gap] An arbitrary-duration original-unit extension (recover unit <instanceId>:<unit> for 90 more minutes) is not implemented. The narrow grant-backed no-work findings renewal below instead uses the original unit's capped lease; it does not reconstruct missing budget authority for other stages. The extension follow-up linked from record 0051 defines the closed authorization set, original requester/thread checks, durable one-use entitlement, budget/spend evidence, unchanged cumulative rounds/cost policy, full refusal matrix and live acceptance. It starts with one writer only after the binding/head prerequisite merges; until then the parser rejects the suffix without starting a generated plan.

Publication binding changes use the existing complete canonical owner CAS, preserving exact child evidence and fresh GitHub target validation. Local claim/readback/rollback is retired for these changes. A lost CAS response succeeds only from one exact proposed durable row, without replay or rollback. An identical bind is read-only replay, never authority for an effect.

Validation criteria ​

Failed publication preserves one producer-owned settlement bound to the original run, requester, repository, branch, owner generation and attach head. Checkpoint, transport, private artifact and release observations stay independent. A verified incremental Git bundle carries the exact checkpoint and non-ignored new files. Its prerequisite is an actual shared ancestor of the original base and checkpoint; after a head rewrite, the existing origin parser must bind the fetch to the recorded repository and a fresh remote-default-head fetch must prove that ancestor is still remotely reachable. The recorded base need not itself be an ancestor. Unrelated histories or an unverified remote prerequisite leave preservation unverified. The private store's configured lifecycle bounds retention. Artifact capture is limited to 64 MiB; unavailable storage, capture or readback leaves preservation unverified. Ordinary release is deferred while the only copy may remain local; hard-stop, in-flight-command and bypass teardown still apply. A lost acknowledgment remains unknown. Restart does not replay an unsettled checkpoint or launch a fresh writer. Remote emptiness is insufficient for the transient retry: it additionally requires a recorded clean workspace with no unpushed commits.

CriterionProof
A failed recovery snapshot reread retains its 503 response and emits only coordinator.read.failed, phase recovery_snapshot_reread and a closed error kind through the existing operator log. Exception text, names, causes, stacks and record identifiers are omitted; the existing recovery owner is not restarted.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::keeps recovery snapshot failure responses and logs only a closed error kind %s
A refused unit write retains its exact admission reason and starts no runner. Incomplete writes carry an optional closed ownership diagnostic through the Worker client, the closing Ship admission note and the failed unit-start report. Stage, cause, source, row position and scan counters survive the report text limit; private or malformed extras are omitted. Older reason-only responses remain valid.[unit] src/core/coordinator/handOff.test.ts::Ship admission refusal diagnostics::*, src/core/dispatch/ship.test.ts::Ship admission diagnostic events::*; src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::unit-start retains an incomplete ownership diagnostic without starting the unit; src/core/coordinator/driver.test.ts::the plan runner's driver — a step that throws inside the walk becomes the unit's ending (issue 2100)::retains a complete closed ownership diagnostic in the failed unit-start report
Canonical publication binding ignores obsolete process-local owners, refuses durable rivals without displacement and confirms an exact committed row after lost CAS acknowledgement.[unit] src/channels/adminCoordinator.test.ts::pr-check recover — the answer says why nothing was recovered, and GitHub being down is never none::binds publication through the canonical owner without reading process-local claims, ::confirms only an exact publication binding after a committed CAS with*, ::a canonical rival refuses the publication binding without rollback or displacement, ::retains publication uncertainty after a*CAS readback
16: an ended original coding unit with one verified final published head may adopt only that exact still-present head into one create-only draft PR on the same unit through agent:ship adopt unit <instanceId>:<unit>, including after a terminal stop. Admission verifies the original requester, generated task, sole terminal child, a retained contiguous event prefix through the accepted push, the record's typed pushed-head, PR and pending-effect fields even when later audit events were trimmed, nonempty remote range, read-only commit identities and no PR in any state. Multiple native updates and same-final-head salvage require the existing run-bound accepted checkpoint/publication settlement, a matching final native push and typed summary, and a contiguous prefix through that final native push. Foreign or malformed push events, salvage at a different head, a gap before the final push, contradictory or unavailable summary, active child or moved head refuse. The unit claims one action durably before POST; a lost response is reconciled by exact GitHub readback and never retried as another create. Binding records the exact PR and head without a new coding lease or review round; an unanswered description, later missing events and sandbox bytes receive no credit.[unit] src/core/coordinator/publishedHeadAdoption.test.ts::publishedHeadEvidence — one original committed head::*; src/channels/adminCoordinator.test.ts::original committed head adoption — create-only draft PR::*; src/execution/githubPulls.test.ts::githubPulls::lists every PR state for exact-head adoption and refuses an incomplete page; ::creates a draft PR once using POST and never edits an existing PR; src/execution/identityRewrite.test.ts::rewriteRunCommits — the run's commits carry only the allowed identities (record 0062)::holds a published head requiring rewrite without creating commits or moving its ref; src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::routes explicit original-head adoption with the real requester and no new plan
A failed first publication saves an exact-commit bundle, including tracked and non-ignored untracked files, with size, digest and downloaded-byte verification. A rewritten sibling head uses a shared ancestor reachable from both the original base and bound repository's current remote default head as its actual prerequisite; unrelated histories, a foreign origin or a missing remote ref remain unverified. The source checkpoint can be reconstructed in a fresh clone after checkout deletion; corrupt readback never proves preservation.[unit] src/core/publicationSettlement.test.ts::checkpoint artifact preservation::*
Checkpoint, publication and release facts cross the durable ledger and sealed run independently. An interrupted checkpoint is not replayed on restart; failed preservation defers ordinary teardown to existing retention, mandatory teardown still occurs, and a lost release reply remains unknown.[unit] src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::settles an unconfirmed first publication before sealing:…, src/core/dispatch/runLoop.test.ts::a resume with the answer in hand (the \finish` plan)::does not replay an unsettled checkpoint after restart:…`
An empty remote alone starts no fresh writer when local preservation is unknown or present. A known unfinished checkpoint ends before PR creation or review.[unit] src/core/ship/coordinator.test.ts::the transient re-run — round 0 dies on a provider transient with nothing pushed (issue 1932)::does not start a fresh writer from an empty remote when local work is unknown or preserved
Authorized checkpoint reads use the validated durable run/attempt receipt after reclaim even when the stream event was never appended. Reclaim and interrupted finish retain a present invalid canonical receipt as null; the event cannot replace it and the coordinator holds the original unit; foreign, missing-receipt and denied-viewer reads fail. Saved bytes remain readable when publication is unknown.[unit] src/channels/liveView.test.ts::artifact route (item 26)::a finished run, tokenless::serves a verified private checkpoint only through the run that recorded it:…, src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::holds an invalid canonical checkpoint through read-record without a new attempt
A first publication records its original run/ref and local checkpoint before transport. Rejected publication and unknown acknowledgment remain distinct from preservation; remote emptiness cannot prove no local work.[unit] src/core/publicationSettlement.test.ts::publication settlement::*, src/core/runRecord.test.ts::isRunRecord::binds checkpoint preservation to the original run and coordinator attempt
The privileged resident publication URL follows the Git Door path contract for discovery and upload.[unit] src/execution/residentPublication.test.ts::resident runner-owned Git publication::constructs a repository URL accepted by the Git door for discovery and upload
6–7: a pattern finding's invariant and cases reach the fix and re-review, survive redaction and the PR post, and a widened table does not inherit a fixed disposition from its smaller predecessor[unit] src/core/reviewVerdict.test.ts::invariant case tables in review findings::*; src/core/coordinator/briefs.test.ts::composeChild — the child a brief names::a findings brief binds exact review and check IDs beside its message, with no contract; a review run the history lacks throws by name; no \fix` brief composes; src/core/ship/reviewChild.test.ts::buildShipReviewTurn — the review round's one user turn (item 5)::a re-review carries the previous invariant and every case into the next review turn; src/core/ship/coordinator.test.ts::the unit pipeline — every ending the ship pipeline has, on step returns::a widened invariant under the same finding ID does not inherit an earlier fixed disposition, ::bounds widened-only continuation separately and never grants it to a different or unchanged invariant; src/tools/submit.test.ts::submit_verdict tool::findings (agent-ship item 6)::requires a new finding to declare single or pattern, and rejects pattern findings without a well-formed case table before recording`
8, 16: original-unit recovery carries at most two earned same-invariant slots only with durable marked-round and unique completed posted strict-widening evidence; an unmarked, unposted or unchanged table cannot raise the cap, including when an unposted live predecessor is followed by a posted widening, and a recovered findings round can use its earned slot for a fresh review[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::earned continuation is credited across original-unit recovery only with posted strict-widening evidence, ::unposted continuation is credited across original-unit recovery only with posted strict-widening evidence, ::not a strict widening continuation is credited across original-unit recovery only with posted strict-widening evidence, ::no durable credit continuation is credited across original-unit recovery only with posted strict-widening evidence, ::does not convert a capped no-verdict review into a new slot without earned continuation; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::carries both independently earned continuation slots through original-unit recovery; src/core/ship/coordinator.test.ts::original-unit recovery accounting::spends a verified continuation in recovered findings and allows one re-review past the ordinary cap, ::compares the next widening with the posted prior table after recovery starts directly at review; src/core/ship/coordinator.test.ts::the unit pipeline — every ending the ship pipeline has, on step returns::does not grant a continuation from an unposted predecessor even when the next review posts a strict widening; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::carries the verified previous case table into direct review recovery after a widened findings push; src/core/coordinator/driver.test.ts::runOriginalUnitRecovery::briefs a directly recovered review with its verified prior pattern table
5b, 9: a posted human-gated pattern marker retains its invariant and every case through an adopted answer into the resumed fix brief; older markers without fields remain readable; only the final generated marker is trusted when rendered cases contain marker-shaped text[unit] src/core/reviewVerdict.test.ts::invariant case tables in review findings::retains the independently checkable cases across parse, stored shape, redaction, post and fix brief; src/channels/adminCoordinator.test.ts::pr-check entry — the unit-start's resume facts beside the listing (agent-ship item 10, issue 1689)::recovers a human-gated pattern's invariant and cases from its posted verdict marker for the resumed fix brief, ::an adopted pull request recovers a legacy human-gated verdict marker before consuming the person's answer, ::does not treat a marker-shaped pattern case as a human-gated finding when the final verdict is actionable; src/core/ship/coordinator.test.ts::the held ending — every finding the round would act on is human-gated (issue 1990)::preserves earned continuations and the parked pattern table through an answered human gate
10: a bare reference to an already merged pull request starts no Ship child and names the merge time; a closed unmerged pull request is reported separately. Both retain the no-resume safety gate.[unit] src/core/ship/preflight.test.ts::shipPreflight — the entry cases (agent-ship item 10) and the auto-merge fact (item 9)::a merged PR resume says it was merged and does not start a child
9, live DM: a coordinator-rebuilt Ship child carries private-audience authority only when the requester, DM address and thread match the durable instance and the Slack adapter verifies that audience now. The watched child channel retains its verification methods so dispatch checks again. A failed check or app-authored parent gives the child no private stamp; a DM-shaped id alone grants nothing.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/spawn — the child as the parent record's requester (item 9)::a Ship child in a direct DM carries freshly verified requester audience through its watched channel; unverified and app-authored DMs do not
3, 16: a retry of a generated plan whose first unit has no pull request keeps that unit's branch and base when thread context names an unrelated pull request or resolves a new base; it does not adopt that pull request. Recorded work on another branch refuses the retry before a new unit is written, even when the retry has no entry branch. A pull request recorded in any attempt remains bound to that same pull request and branch, or the retry refuses before writing a unit. An absent first Workflow with an instance but no unit row can still be safely replaced; a recovered older PR-bound unit retains its saved push and record under its original identity, and replacement admission refuses[unit] src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::a generated unit with no PR keeps its original branch when a retry inherits an unrelated thread PR, ::keeps the original base when a no-PR retry has no entry branch, ::keeps the original base when a no-PR retry retains its entry branch, ::refuses to discard a PR first recorded in a later attempt, ::replaces an absent first Workflow after its instance was written without a unit, ::retains a recovered legacy unit's saved push and record under its original owner, ::refuses to carry work recorded on another branch into a generated unit's original branch, ::refuses foreign-branch work even when the next retry has no entry branch, ::refuses to abandon or replace an originally adopted PR on retry, ::refuses to transfer an originally adopted PR into a replacement attempt
10: an adopted PR whose coding child reports a blocked deviation without an accepted push remains held at the old head; the coordinator starts no review on that head[unit] src/core/ship/coordinator.test.ts::the unit pipeline — every ending the ship pipeline has, on step returns::a fresh adopted PR stays held when its coding child stopped before pushing, rather than starting review on the old head
8: a completed coding child with clean Git status rendered as (no output) and no unpushed commits leaves the PR post-step free to run. A Git commit or push reported as exit N: text fails salvage; it never records a successful pushed head or proceeds to push after a failed commit[unit] src/core/codingPrPostStep.test.ts::salvageBudgetPush — a ship coding child pushes what it has at the budget (push-before-abort, agent-ship.md item 8)::a completed child with no work leaves the ordinary PR post-step free to run, ::reports a text-rendered failed commit without claiming a push, ::reports a text-rendered failed push without recording a successful head
6: a findings brief binds the review and check IDs by value on the child's durable tag, including check:<name>; submit_dispositions refuses unknown, duplicate, missing or unbound IDs before recording, showing the issued IDs so the same child can correct them; the runner still names mismatched legacy submissions in an incomplete findings report[unit] src/tools/submit.test.ts::submit_dispositions tool::rejects unknown, near-match and duplicate IDs…, src/core/coordinator/briefs.test.ts::composeChild — the child a brief names::a findings brief binds exact review and check IDs…, src/core/ship/coordinator.test.ts::completed findings recovery — typed completion plus independently verified remote head::names submitted IDs that missed check findings in the incomplete-output report
22: once draining, coordinator spawn answers queued for the next generation and dispatch is never entered, including a request already blocked in a parent read; a dispatch-bound permit holds the drain until the child registers; the driver's queued answer remains a retry of the same durable step; repeated handoff passes find a run that reached the ledger after the first pass without marking older rows twice[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/spawn — the child as the parent record's requester (item 9)::SIGTERM closes child admission immediately…, src/channels/adminCoordinator.test.ts::POST /admin/coordinator/spawn — the child as the parent record's requester (item 9)::SIGTERM fences a spawn that was already reading its parent before the drain boundary, src/channels/adminCoordinator.test.ts::POST /admin/coordinator/spawn — the child as the parent record's requester (item 9)::the drain holds a dispatch admission until the child registers, src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::a 409 queued on the spawn step…, src/core/runLedger/writeThrough.test.ts::finishing and finish::liveRuns names the runs this generation drives…
7: the runner passes the decision's tier — model and effort on the spawn body become the child's own directives ahead of every scope; a coding child on the fast tier is refused spawn_tier before any store is read; a malformed model or effort is refused by name[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/spawn — the child as the parent record's requester (item 9)::the decision's tier rides the child's request…, ::a coding child on the fast tier is refused \spawn_tier` before any store is read…`
AGENTS["ship"]: machine class repo-resident, full toolset, the pipeline's default wall clock (1440) as its budget, turns and tokens placeholders; its child presets run within its profile (ship's class, an identity at or under write); directive resolves on Slack/CLI[unit] src/agents/registry.test.ts::ship agent…
One ship implementation: shipPipeline.ts keeps the config block, the caps, the preset and the round header and nothing of a round loop; the child modules export only the prompt blocks the spawn route composes a child's turn from, the coding module the contract's place alone; the fix child is gone from the tree (no fixRound option, no buildShipFixTurn, no knownFindingIds check, no fix brief kind, no no-sink answer naming a fix round); the ship branch imports the hand-off and no loop and reads no switch[unit] src/core/ship/surface.test.ts::the ship modules after the round loop's retirement — a source scan::*
8: shipPresetFor is the registry's def with ship.maxMinutes as its budget, the default the def's own and SHIP_DEFAULT_MAX_MINUTES that same number; always a copy[unit] src/config.test.ts::ship caps block (agent:ship pipeline)::shipPresetFor…
8, 15: every round is carved from the remainder minus the reserve for the rounds after it — the coding child's carve leaves the whole loop the config allows (24 from a 100-minute pipeline at three rounds, 90 from 240, 40 from 60 at one round), a findings child's leaves the rounds after it (90 with 225 left, 16 with 64), and a round under its floor is not dispatched: the unit ends at the cap naming the round, the minutes and the floor, also when a busy spawn's wait ate the remainder[unit] src/core/ship/coordinator.test.ts::the unit pipeline — every ending the ship pipeline has, on step returns::the coding child's carve leaves the reserve for the whole loop…; ::a findings child's carve leaves the rounds after it…; ::the wall-clock cap: every round is carved from what remains…; ::the findings step waits out a person's run…
8: the fit at config load — a ship.maxMinutes/ship.maxRounds pair that cannot hold its loop is refused naming the sum (40 against the 169 three rounds need; 180 against the 197 four need), and a pair at the sum loads[unit] src/config.test.ts::ship caps block (agent:ship pipeline)::a ship pipeline that cannot hold its loop is refused with the sum…
8: the fit at the fork — a boundary that clips ship under its loop's sum refuses with the sum on the card and in the reply, no instance opens; a boundary at the sum starts the runner[unit] src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::a boundary that clips ship under one review round refuses at the fork with the sum…; ::a boundary at the fit's sum starts the runner…
8: a plan runner's child card names the carve — carved by the plan runner from the pipeline's remaining clock — while a person's directive still reads budget directive[unit] src/core/dispatch/provision.test.ts::budgetClipLabel — the card's budget line::names the plan runner's carve for a coordinator's child…
8: the fit — a pipeline holds its first child at its ask and every later round at its floor, provision + ask(coding) + reserve(coding): ship at three review rounds needs 169 minutes, a fourth needs 197 — it fits a 240-minute explicit lease and is refused at 180 — and the 40-minute deployment fails naming 169[unit] src/core/budgets.check.test.ts::the fit — a pipeline holds its first child at its ask and every later round at its floor::*
8: push-before-abort — every ship coding child ending checkpoints a dirty tree or unpushed commit before workspace release, including a completed contract abort or handoff; budget, failed, stopped and interrupted/restarting endings retain their mechanical WIP marker. Each successful checkpoint records pushed_head by:salvage; the runner aborts rather than reviews unfinished WIP, names the recorded branch/head, requires remote reconciliation and directs a new ship or plan run without promising a reply resumes it, and no checkpointed commit emits the old discarded-work card. The run's independently observed final headSha is persisted and returned by production read-record beside handoff and pushed: round zero still requires completed + handoff + same-head salvage before it opens or recovers the pull request and enters review; a differing returned head, a missing handoff or any other ending keeps round zero's checkpoint. Findings rounds use item 7's separate completion and remote-head reconciliation contract and never treat the salvage record itself as readiness.[unit] src/core/codingPrPostStep.test.ts::salvageBudgetPush — a ship coding child pushes what it has at the budget (push-before-abort, agent-ship.md item 8)::*; src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::a coding child's final description turn checkpoints dirty work before release, ::a coding child that ends with an unpushed commit checkpoints it before release and its card never says discarded, ::a coding child whose transport retry budget is exhausted commits and pushes its interrupted workspace before teardown, ::a coding child whose model call limit is exhausted commits and pushes its interrupted workspace before teardown, ::a stopped coding child checkpoints its WIP before the hard-stop teardown, ::a restarting coding child pushes a WIP checkpoint before release and its card never says the work was discarded; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/read-record — the renewal's facts off the record (decision 0046)::a completed child's production read-record view carries its observed final head, so same-head salvage opens the pull request and enters review, ::a completed child whose salvage differs from the head returned by production read-record still aborts at the checkpoint; src/core/ship/coordinator.test.ts::the transient re-run — round 0 dies on a provider transient with nothing pushed (issue 1932)::a stopped child keeps its same-head salvage checkpoint even when it submitted a handoff, ::a completed coding child whose ending checkpoint reached the unit branch aborts with the resumable head, ::a transport failure whose WIP checkpoint reached the unit branch aborts with the recoverable head instead of reviewing unfinished work; src/core/ship/coordinator.test.ts::the unit pipeline — the event, the timeout and the confirmation (the durable half)::an interrupted coding child whose WIP checkpoint reached the unit branch ends with that recoverable head, never a discarded-work restart
8: a cold coding sandbox whose repository is cloned below the workspace root checkpoints only from the uniquely discovered clone whose origin matches the bound repository; multiple or foreign clones fail without staging and report the gap. When relative discovery finds no clone, the exact seeded checkout is eligible only after repository and branch proof; a missing seed fails closed. Resident checkpoints use their attached path instead of discovering another tree.[unit] src/core/codingPrPostStep.test.ts::salvageBudgetPush — a ship coding child pushes what it has at the budget (push-before-abort, agent-ship.md item 8)::checkpoints a cold sandbox clone from its repository directory, ::refuses a cold clone without an expected repository rather than guessing its owner, ::refuses an ambiguous cold checkout instead of committing another clone's work, ::refuses a cold clone whose origin does not match the bound repository before staging, ::refuses to stage work from a foreign checkout with the owned branch name
8: resident and seeded-sandbox observation and checkpoint use the exact checkout path from the selection, including after reattach; a cold sandbox without a bound path discovers its clone or verifies the exact seeded checkout. A coordinator checkpoint reached after unreadable workspace observation proves the owned repository and branch before staging.[unit] src/core/codingPrPostStep.test.ts::observeCodingWorkspace::reads a resident checkout from its bound path without probing the executor's cwd, src/core/codingPrPostStep.test.ts::salvageBudgetPush — a ship coding child pushes what it has at the budget (push-before-abort, agent-ship.md item 8)::checkpoints a resident checkout from its bound path even when the executor starts elsewhere, src/core/dispatch/runLoop.test.ts::a resume with the answer in hand (the \finish` plan)::a successful gated push receipt survives restart into…, src/core/dispatch/runLoop.test.ts::the pi harness — the container replaced under a living bot: the relaunch ceiling::observes a coding run after reattach in the new workspace instead of its old bound checkout`
7: completed findings recovery — a findings run needs completed status, all matching dispositions, the updated description and its observed final head, but not round zero's handoff; an attempted push is not readiness. A fresh read must verify the adopted pull request's exact source head before either the next review or merged success; while open, its live branch must also be verified. A merged pull request at an older or unreadable source head, including one returned beside the finished child's production read-record, ends terminally without crediting the findings; its report directs an authorized follow-up PR. Terminal state also survives incomplete outputs, including cost-capped publication checks, and retains every missing result. Missing or unreadable remote state without a terminal PR observation, an unattributed open head, incomplete outputs on an open PR and genuine interruption remain resumable. Reports state only proven saved-work, failure and next-action facts without internal labels or an unsupported reply-to-resume promise.[unit] src/core/ship/coordinator.test.ts::completed findings recovery — typed completion plus independently verified remote head::*; src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::read-record recovery — the real runner step over the real store (item 9)::a finished findings read-record carrying an older merged PR head…
7: when a findings child ends at one local head and the open PR is at a different head, the runner re-reads the adopted PR before acting. Neither a child with no push receipt nor one that pushed only an earlier Hx can restart review of a foreign H1 before required fixes are redone. It charges the child's spend but does not attribute the new head or missing dispositions to the child. The second read checks PR identity and reapplies the child's typed completion gate before review or merged success when the head has returned to the child or the PR has merged. If the PR number changes, the branch or exact head becomes unreadable, the child's results remain incomplete, or the original recovery cost cap is spent, it dispatches no review.[unit] src/core/ship/coordinator.test.ts::completed findings recovery — typed completion plus independently verified remote head::a superseded unpushed findings child cannot restart review before its required fixes are redone, ::an earlier intermediate push cannot restart review of a foreign head before findings recovery, ::a completed findings child with an earlier push cannot apply its dispositions to a foreign head, ::does not restart review when a failed findings child left an unattributed moved head, ::does not bypass missing findings results when the pull request holds the child's own head, ::reapplies the findings gate when the second read returns to the child's head, ::uses completed findings only when the second read verifies the child's exact head, ::does not claim a merge at a foreign head as the completed findings child's result, ::does not restart review when the foreign branch becomes unreadable after findings, ::does not redirect the review to another pull request after a foreign head is read, ::charges a superseded findings child before deciding whether another review fits the cost cap
8: the grant — DEFAULT_GRANT is zero renewals and no cap, GRANT_RENEWALS_MAX permits twelve renewals, or thirteen one-day ship segments; resolveGrant layers user > channel > org with a renewals: directive setting the count alone; ship.grant and a scope's ship.grant load and are refused by name (a count outside 0–12, a non-positive cap, an unknown field, a non-mapping); the hand-off writes the grant and its source on the instance, the default when the fork carried none; the plan route answers them; the unit report names spent of granted, the cap and the granter; renewals:<count> parses and strips like the other directives, is refused outside the ceiling and is never sticky[unit] src/core/budgets.check.test.ts::the grant — what the request authorizes beyond one lease, sized by the person::*; src/config.test.ts::ship caps block (agent:ship pipeline)::ship.grant: parsed at load on the org block and the scopes…; src/core/coordinator/handOff.test.ts::the grant — resolved once, written on the instance beside \merge` (decision 0046, the renewable lease)::; src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::plan answers the instance's units with where each stands…; src/core/ship/coordinator.test.ts::the severity gate — an approve's findings held to the level in force::the report names the grant the instance carries…; src/directives.test.ts::parseDirectives — renewals::`
8: the idle ending — each idling kind maps to idle with itself as why, its outcome intact at every verbosity, its terminal restart line replaced by the indexed wake's durable reply action, and the continuation facts filled at idleDays: 7 (an idled review_pending continues from its own pending head; an interrupted review child names the last coding child's run); the terminal kinds and findings aborts carrying a typed merged or closed PR observation never map; ordinary open-PR findings aborts still idle; at 0 and absent nothing maps[unit] src/core/ship/coordinator.test.ts::the idle ending — an idling kind maps to \idle` when ship.idleDays is above zero (record 0051)::; src/core/coordinator/driver.test.ts::the plan runner's driver — a shipped pull request at the wall-clock cap (review pending)::an idled review_pending continues from the pending head…; src/core/ship/coordinator.test.ts::completed findings recovery — typed completion plus independently verified remote head::keeps a typed merge terminal when incomplete unpushed findings are, ::keeps incomplete unpushed findings terminal when the pull request closed`
8: the idle flag — ship.idleDays accepts 0 and 365 and is refused by name outside them on the org block and a scope; an idle parks under indexed waits, a timeout or the wake limit ends idle_expired, a segment answer continues with its attributed texts, and at zero no idle is emitted[unit] src/config.test.ts::ship caps block (agent:ship pipeline)::ship.idleDays (record 0051): accepts 0 and 365…, src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::an idle ending waits under the first indexed identity…, ::a nudge answered without a segment re-enters the indexed wait…, ::a wake segment carries the handoff…, ::a wake segment carrying leaseMs reopens the cut segment…, ::an explicit expired answer…, ::a wake that throws after the step retries…, ::later units do not start while the walk is parked…, ::idleDays zero never emits an idle ending…
8: renewal — progressOf reads progress off the row (a head pushed to the unit's branch after the lease began and differing from the segment's start, else a checkpoint handoff whose follow-ups shrank — a handoff recording a deviation never reaches the decision, issue 2086; a push elsewhere, of the start head — on a fresh branch the base head stands in for it — or before the lease is none), an exhausted grant of one reads singular (the 1 renewal granted is spent), renewalDecision renews only when progress, a renewal, the cap and the fit all hold and names the failing clause, renderRenewal says the card's words — a no-progress stop with renewals left says what actually spends one (a segment's progress) and to re-issue the request, teaching no continue keyword the router does not have; the machine ends a round 0 without a pull request continued under a grant with renewals when the child pushed, reads a later segment against the sha it continued from and the previous handoff with every step prefixed by the segment, accrues the session's spend from each child's cost and refuses at the cap or on an unknown cost, and names the refusal on the abort while a grant of zero with nothing pushed keeps the plain abort; the driver loops segments with the session and the continuation brief, the bot's read-record answers the pushed heads, the lease's start, the cost through the prices and the handoff's lists, unit-end writes the segment row once and refuses a continued ending without it, and the continuation brief prefaces the unit's request with the previous run's write-up and handoff[unit] src/core/ship/renewal.test.ts::*; src/core/ship/coordinator.test.ts::the unit pipeline — every ending the ship pipeline has, on step returns::a round 0 without a pull request whose child pushed a head under a grant with renewals ends \continued`…, ::a later segment reads progress against the sha it continued from…, ::a refusal names its clause on the abort…; src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::a segment that ends `continued` opens the next…; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/read-record — the renewal's facts off the record (decision 0046):😗, src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::unit-end writes the ending and the pull request on the row…; src/core/coordinator/briefs.test.ts::composeChild — the child a brief names::a contract brief with a continuation prefaces the unit's request…`
8, 12: a concluded round is not renewed (issue 2086; record 0046, amended) — the runner reads the round's ending before any renewal: a round-0 coding child whose handoff records a deviation has concluded its round, so the unit ends held with cause blocked and the deviations as its reason (<from> → <to> — <why>, joined), the round is noted held, no renewal is judged and no second segment opens even under a grant with renewals and a pushed head; the report names the reason, points at the write-up and says the person's word continues the unit; the blocked hold idles for the person's word when the flag is on while a human-gated question parks independently of the flag; a child that stopped without an ending — a lease end, with or without a checkpoint handoff carrying no deviations — keeps the renewal decision as today[unit] src/core/ship/coordinator.test.ts::the unit pipeline — every ending the ship pipeline has, on step returns::a blocked handoff ends the unit \held: blocked` with the deviations as the reason and no second child…, ::a lease end without a handoff is renewed as today…, ::a blocked hold idles when the flag is on, while a human-gated question parks even without depending on that flag`
9: the merge wait is carved like a round — 60 from a wide remainder, the remainder when narrower, refused under its floor of 10 — and the door's fallback chunks shrink to that carve's remainder[unit] src/core/budgets.check.test.ts::carve — a round's minutes from the parent's remainder, refused under the floor::the merge wait is carved like any round…; src/core/ship/coordinator.test.ts::the unit pipeline — the event, the timeout and the confirmation (the durable half)::the merge: * waits on the checks-settled event at the approved head under a bounded fallback…
12: the report points at a child's write-up instead of repeating it (a verbose aside in the thread's copy, routing-and-config item 28) — an abort, a stop (the round's child), a no-verdict (the review child) and a continued segment drop the embedded final reply and carry a pointer, the run page when the plan answered runPageBase and the run id otherwise, with the reason and renewal unchanged and the next-action line truthful for a terminal restart or durable wake; the plan route answers the base only when the deps carry one, and the base rides the plan answer into the machine's input[unit] src/core/ship/coordinator.test.ts::the unit report — the child's write-up is pointed at, never repeated (issue 1806)::*; src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::plan answers the runs page base…; src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::the plan answer's runPageBase reaches the machine…
12, 15: a shipped pull request at the wall-clock cap ends review_pending, not a failure — the unit-end names the pull request and the child's own last push, the report says review pending with the budget split (coding, review, waiting — a verbose aside in the thread's copy), and a unit row carrying lastPush starts the next attempt at the review round when the open pull request still heads exactly there, at round 0 when the head moved — the unit-end route persists the named head on the row as lastPush and a re-issue carries it onto the next attempt's row[unit] src/core/coordinator/driver.test.ts::the plan runner's driver — a shipped pull request at the wall-clock cap (review pending)::*; src/core/ship/coordinator.test.ts::the unit pipeline — every ending the ship pipeline has, on step returns::a wall-clock cap after the child opened or updated the pull request…, ::a lastPush on the input starts the attempt at the review round…, ::the wall-clock cap's report carries the budget split too…; src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::unit-end writes the ending…; src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::a re-issue after a review_pending ending carries the row's lastPush…
8: the runner is handed the parent's effective budget — caps.maxMinutes is the profile's minutes, maxRounds the largest count fitting the effective minutes up to the block's ceiling — and the record carries the ship profile with its clip[unit] src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::the caps handed to the runner…, ::a task: the request becomes a generated one-unit plan instance named by the task and the thread…
8: end to end — a channel boundary clips the one-day pipeline to 200 minutes and four rounds in the caps handed to the runner, the card names the clip and the record carries the ship profile, while a boundary of 10 is refused at the fork naming the one-round floor of 113 with no instance opened; agent:ship budget:200 clips as the caller's own boundary while the card names its explicitly configured 240-minute preset and two-round ceiling[unit] src/core/dispatcher.test.ts::agent:ship (the hand-off to the plan runner)::a channel boundary clips the pipeline's wall clock…, ::\agent:ship budget:200` clips the pipeline's wall clock…`
1: the preflight admits by the request handle's capability (canOpenThread) — web, CLI, HTTP and MCP job handles pass; a handle without openThread is refused with the spawn's reason and a run-page pointer, never called single-shot, nothing handed to the runner[unit] src/core/dispatcher.test.ts::agent:ship (the hand-off to the plan runner)::channel guard…, ::HTTP and MCP job handles admit the same Ship hand-off as chat, src/core/ship/preflight.test.ts::shipPreflight — the channel capability (agent-ship item 1, record 0060)::*, src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::the ship branch passes the request handle's capability…
2: compound gate — allowed ship but denied coding → refused naming coding; denied repo → refused; nothing handed to the runner either way[unit] src/core/dispatcher.test.ts::agent:ship (the hand-off to the plan runner)::permission: user allowed ship but not coding…, ::allowed all three agents but denied the target repo…, src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::refused at the preflight…
9: auto-merge is the pull request's own fact — parsed from auto_merge, named at entry in the hand-off's reply and at the approved head by the merge_ready report reading the facts fresh; no repository-level check remains and a failed repository lookup refuses nothing[unit] src/execution/githubPulls.test.ts::githubPulls::fetchPullRequestFacts (ship entry checks)::autoMergeEnabled is true for a non-null auto_merge…; src/core/dispatcher.test.ts::agent:ship (the hand-off to the plan runner)::an adopted person-authored PR behaves identically…, ::a failed repository lookup refuses nothing…; src/core/ship/coordinator.test.ts::the unit pipeline — every ending the ship pipeline has, on step returns::merge-ready under…; src/core/coordinator/driver.test.ts::the plan runner's driver — a resume at review (agent-ship item 10)::a task row carrying a resume re-reads the pull request…
3, 16: a task the preflight admits is handed as a generated one-unit plan instance named by the task and the thread — the U1 row on the deterministic plan/<id>/u1 branch off the default branch, no resume, the reply naming the runner, the card ✅, no workspace attached and no model turn in the bot; a repo-shaped repoCtx.ref never becomes the base[unit] src/core/dispatcher.test.ts::agent:ship (the hand-off to the plan runner)::a task the preflight admits is handed to the runner…, ::a repo-shaped repoCtx.ref never becomes the pipeline base…
9, 10: publication claims fail closed when the complete canonical ownership snapshot is unavailable. Runner rebase returns the named retryable publication_ownership_unknown; an ended-thread continuation delegates its original identity to durable recovery, whose own owner and admission checks govern execution[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/merge — the runner's squash of a unit's pull request (item 9)::the runner's rebase retains canonical ownership uncertainty before native writes; src/core/dispatcher.test.ts::a unit-owned thread (record 0051's reply-as-event and gone-instance rules)::plain continuation delegates to exact original-unit recovery without replacing its owner
9, 10: exact existing-PR publication — every merge_ready ending persists the final reviewed full head as lastPush; adopt/resume requires an open same-repository PR with explicit head/base refs, an existing base and a full head SHA. The original unit retains the full publication binding and owner. Implicit continuation uses its existing recovery action; no dispatcher task reconstruction, legacy repair, local ownership transfer, callback rollback or replacement attempt remains. Original recovery verifies requester, budget, child evidence and current publication authority. Spawn, harness, push and salvage require exact owner, request, workspace and remote agreement plus an atomic full-SHA lease; missing, stale, moved, foreign, unavailable and concurrent movement publishes no alternate ref[unit] src/core/ship/preflight.test.ts::shipPreflight — the base ref existence check before the pipeline branch is cut (agent-ship item 10, issues 1827 and 2161)::an existing pull request whose bound base is missing fails closed instead of publishing against a fallback, ::a pull request's own base that exists leaves the adopt unchanged; a PR without its own base fails before a lookup; src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::a resume at review (agent-ship item 10) is the one generated unit with the pull request on its row and the entry's branch — the pull request's own head — so the runner opens it at the review round; the reply names the pull request and that no coding round runs first; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/spawn — the child as the parent record's requester (item 9)::an existing-PR coding spawn carries the durable publication binding only for its sole owner and fails closed after ownership changes, src/channels/adminCoordinator.test.ts::pr-check keeps the machine's adopted pull request authoritative before branch discovery (agent-ship items 9, 10 and 12)::persists the first complete same-repository PR read as future coding rounds' exact publication authority; src/core/existingPrPublication.test.ts::verifyExistingPrPublication — exact existing-PR publication binding::*; src/core/dispatch/runLoop.test.ts::the pi harness — every preset's runs, in the run's container::the gate's push rules follow the thread: a pull-request thread's or a unit child's bound branch is the run's own — the one push target, its base protected; a plain thread's binding is the protected base, src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::a blocked existing-PR publication keeps the local checkpoint attached and renders truthful partial status without an alternate push; src/core/harness/pi/toolRules.test.ts::judgeToolCall — an existing-PR publication fence::*; src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::a review that requests changes is followed by findings and re-review at the moved head…; src/core/dispatcher.test.ts::a unit-owned thread (record 0051's reply-as-event and gone-instance rules)::plain continuation delegates to exact original-unit recovery without replacing its owner; src/core/runnerOwnership.test.ts::canonical runner pull owner::*; src/channels/adminCoordinator.test.ts::pr-check recover — the answer says why nothing was recovered, and GitHub being down is never none::refuses a rival admitted while the original publication awaits its binding CAS
9, 10: a reply to an ended external-PR unit without an accepted push selects its original identity from the durable publication row. It delegates authorization and exact head/base/owner verification to original-unit recovery without rebuilding a task, copying budgets or creating another instance; settled or refused actions retain their original result[unit] src/core/dispatcher.test.ts::a unit-owned thread (record 0051's reply-as-event and gone-instance rules)::continues at a durable publication head when an external PR unit has no push, src/core/dispatcher.test.ts::a unit-owned thread (record 0051's reply-as-event and gone-instance rules)::plain continuation delegates to exact original-unit recovery without replacing its owner
10: entry checks — a user-named open PR with no new task → a resume at review for ANY author, with the pull request, head and url on the task row and the PR's own base; a new task in a thread carrying an open PR → the generated plan adopts it (branch the PR's head, base its own, the reply names the PR and no new branch) and runs coding round zero before review despite old approval and green checks, a person's PR identically; a seeded plan request keeps the graph's branches; a fork head refused on adopt and resume; a closed or unfetchable adopt/resume target refused[unit] src/core/dispatcher.test.ts::agent:ship (the hand-off to the plan runner)::thread with user-named open PR and no new task text → a resume at review…, ::a resume prefers the PR's OWN base ref…, ::a bare reference to a person's open PR resumes at review…, ::thread PR open + new task text → the generated plan ADOPTS the pull request…, ::an adopted person-authored PR behaves identically…, ::a seeded plan request in the same pull-request thread stays seeded…, ::a fork-head PR is refused on adopt and on resume…; src/core/ship/preflight.test.ts::shipPreflight — the entry cases (agent-ship item 10) and the auto-merge fact (item 9)::*; src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::a resume at review: the open pull request…
10: an operator-bound Ship request carries a typed first stage; review of a named open PR starts at review on its verified head even when the request contains the word review; a bare PR URL misbound as work refuses instead of letting a text probe choose review; work citing a PR needs an exact requester-backed proposed change from the current or earlier actor-stamped turn and a Yes on that exact proposal before coding; and a missing PR, mismatched preset or exhausted operator decision refuses before coding while verified thread owners keep their guarded continuation[unit] src/core/ship/preflight.test.ts::shipPreflight — the entry cases (agent-ship item 10) and the auto-merge fact (item 9)::an operator review entry starts at review on the named foreign PR even though the request contains the word review, ::a bare PR URL misbound as work refuses instead of letting a text probe choose review, ::a misbound explicit PR review refuses coding without a separate work objective, ::a PR-citing work objective must quote the requester, not an operator invention, ::an operator review entry with no verified PR refuses before round zero; src/core/dispatch/operator.test.ts::bind_preset carries a separate code-change objective when Ship work cites a PR; src/core/dispatcher.test.ts::agent:ship (the hand-off to the plan runner)::an explicit Ship review request uses the operator's review entry and starts at review on the verified PR, ::an explicit Ship review request fails closed if the operator binds another preset, ::an explicit Ship review request fails closed when the operator returns no stage; src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::a PR-citing work bind cannot borrow another person’s turn as its objective; src/core/dispatcher.test.ts::a unit-owned thread (record 0051's reply-as-event and gone-instance rules)::an explicit ship reply naming the owned PR checks the original unit before starting work
10: when typed Ship work cites a PR, the offer names the exact proposed change and PR; only the requester's Yes on that stored pair grants the separate coding start, including when the original text merely quoted an example; a changed bind asks again; without Yes, no unit starts, and a channel unable to confirm does not offer a line that would re-ask forever[unit] src/core/ship/preflight.test.ts::shipPreflight — the entry cases (agent-ship item 10) and the auto-merge fact (item 9)::a quoted example beside a PR cannot start coding without the requester's confirmation; src/core/dispatcher.test.ts::agent:ship (the hand-off to the plan runner)::PR-citing Ship work waits for the requester's Yes before round zero, ::a PR-work Yes cannot authorize a different objective chosen on redispatch; src/core/dispatch/reply.test.ts::renderRefusal — the one rendering of a Refusal::a PR-work confirmation cannot be bypassed by retyping its line where no button is available
16: thread-sourced Ship work carries the requester's actor-stamped issue and earlier answer even when the operator calls it work; if that evidence becomes unavailable, no worker starts[unit] src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::attaches actor-stamped general research to a thread-sourced work unit after repo preflight, ::attaches actor-stamped general research to a thread-sourced work_from_thread unit after repo preflight, ::refuses a thread-sourced work hand-off if its requester checkpoint becomes unavailable after binding, ::refuses a thread-sourced work_from_thread hand-off if its requester checkpoint becomes unavailable after binding
10, 16: an explicit agent:ship plan <path>.md request may pass through the operator with typed plan intent and retain the seeded runner grant only after preflight validated that entry; an operator-bound work request whose words look like a plan stays a generated person-merged task[unit] src/core/dispatcher.test.ts::agent:ship (the hand-off to the plan runner)::an explicit Ship plan request keeps its runner grant after the operator binds the plan stage, ::an operator-bound work request with plan-shaped text stays a generated person-merged task; src/core/ship/preflight.test.ts::shipPreflight — the entry cases (agent-ship item 10) and the auto-merge fact (item 9)::a typed plan entry keeps an unrelated thread PR as context and rejects a missing plan path; src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::a typed plan entry needs the validated plan flag before it can read a seeded plan
16: a typed work entry passes the author's request to the generated, person-merged unit intact, including a URL and repository prefix that legacy task probes would remove; plan-shaped words cannot reclassify typed work as a runner-merged seeded plan[unit] src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::a typed work entry keeps a URL as the generated task instead of stripping it from a probe, ::a typed work entry keeps the whole authored task without a Ship prefix parser, ::a typed work entry that spells a seeded plan remains a person-merged generated task
10: a fresh adopted task with no recorded prior push codes on the existing PR before review even when its old approval and checks are green; a re-issued unit carrying its prior push still resumes at review or the merge decision[unit] src/core/coordinator/driver.test.ts::the plan runner's driver — the entry checks resume a re-issued plan's unit (agent-ship item 10, issue 1689)::a fresh task adopting an approved green pull request spawns coding before any review, ::a pre-check that finds the pull request approved with green checks at the branch head resumes straight at the merge-ready check…
10: entry checks — a foreign PR cited in new task text falls through to a task off the default branch on the task's own branch — even when its facts fetch fails — and drops the PR-derived ref (refFromPr) and a unit branch of ship's own the thread stayed bound at (plan/<id>/<slug> → the default branch; a person's own typed ref still wins); an INHERITED unreachable PR or a BARE in-message reference stays fail-closed; the resolver flags in-message PRs (prFromMessage) and PR-head refs (refFromPr)[unit] src/core/dispatcher.test.ts::agent:ship (the hand-off to the plan runner)::new task text citing a human-authored open PR does NOT bind it…, ::in-message cited PR + new task text + FAILING prFacts fetch…, ::inherited unreachable PR (prUnpostable) + new task text…, ::in-message cited PR + NO task text + failing prFacts fetch…, src/core/repoContext.test.ts::resolveRepoContext: PR URLs and shorthand::a PR URL yields repo + head ref via the GitHub REST API, ::a follow-up with no PR reference inherits the thread's OPEN PR for the post-step (pr + headSha, ref untouched), ::degrades gracefully: a failed PR fetch keeps the repo, leaves ref undefined, src/core/ship/preflight.test.ts::shipPreflight — the entry cases (agent-ship item 10) and the auto-merge fact (item 9)::a unit branch of ship's own is never a fresh task's base…
10: entry checks — a re-issued plan's unit with an open pull request resumes, never re-codes (issue 1689): the unit-start's pre-check carries entry: true and the bot answers the branch's own tip (branchHead, the facts read's ref-tip-preferred head), whether the bot's approval stands at the head (approved) and the checks there beside the listing — an open pull request at the branch's own head resumes at the review round (adopted, its head the review's pin; no branch step, no coding child), one approved with green checks resumes straight at the merge-ready check (merge_ready for a person, the merge door under merge: runner at exactly that head; red, pending or unknown checks resume at review), a stale listing sha behind the branch's tip — the previous attempt's lastPush — resumes pinned at the tip the entry facts were read at, never the listing's, and a branch with no open pull request — or a listing head behind the branch's tip with no lastPush — starts round 0 as before; an unreadable pull-request transition fails closed and retries rather than acting on the listing[unit] src/core/ship/coordinator.test.ts::the unit pipeline — a pull request already merged: a re-issued plan, or a merge that lands during a round::an open pull request at the branch's own head resumes the attempt at the review round (issue 1689)…, ::an approved pull request with green checks at the branch head resumes straight at the merge-ready check (issue 1689)…, ::a stale open-PR listing behind the branch's own tip resumes at the head the entry facts were read at…, ::an approved pull request whose checks are red, pending or unknown resumes at the review round…, ::the pre-check answering none, or open without the branch's tip beside it, proceeds to the branch and round 0 as before…; src/core/coordinator/driver.test.ts::the plan runner's driver — the entry checks resume a re-issued plan's unit (agent-ship item 10, issue 1689)::*; src/channels/adminCoordinator.test.ts::pr-check entry — the unit-start's resume facts beside the listing (agent-ship item 10, issue 1689)::*
10: the base ref existence check (issues 1827 and 2161) — the preflight verifies a bound base ref exists on the repository (one GET refs call, fetchRefExists: 2xx true, 404 false, anything else "could not ask") before the pipeline branch is cut: any missing typed ref, falls back to the repository default with the ref on the entry and the hand-off's first plan line saying what the door read and what it will do; an adopted or resumed pull request's missing own base instead refuses because retargeting would invalidate its publication authority; neither path hands a command to the person; an existing ref, an unanswerable lookup and a preflight without the seam proceed unchanged, and no lookup is spent when no ref is bound[unit] src/core/ship/preflight.test.ts::shipPreflight — the base ref existence check before the pipeline branch is cut (agent-ship item 10, issues 1827 and 2161)::*; src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::a base the preflight fell back from (issue 1827) is named on the reply's FIRST plan line…; src/execution/githubPulls.test.ts::githubPulls::fetchRefExists (the ship preflight's base check, issue 1827)::*
10: only an OPEN pull request contributes a ref hint (refFromPr) — a merged or closed PR cited as a receipt in a coding ask binds no ref while its facts still ride — and a coordinator child's attach ref is its contract's branch, always: a PR-derived ref in the child's own request text is dropped before the attach, so the push guard allows the unit branch and the pipeline does not orphan the commit (issue 1860)[unit] src/core/repoContext.test.ts::resolveRepoContext: PR-source flags for ship::a merged PR URL in a plain ask yields no ref hint — the PR's facts (pr, headSha) still ride as context (issue 1860), ::a closed-unmerged PR contributes no ref hint either, and an explicit … beside a merged PR binds the phrase's ref with refFromPr unset (a contract child attaches on its contract branch); src/core/dispatcher.test.ts::coding PR post-step (docs/reference/specs/pr-description.md)::a coordinator's contract child whose request text cites a PR attaches on the contract branch — the PR-derived ref never rebinds the attach, so the push guard allows the unit branch (issue 1860)
10: the thread's own PR named in the CURRENT message is the target, never context — the entry table over (task text, PR source, thread's own, PR facts): adopt with task text on an unowned path; under operator-on an ended unit keeps the original writer and exact-head gate only for a typed continue entry, while a work entry on a still-owned PR is refused even without a work objective, resume a continuation-only reply without new task text, an unfetchable or fork-head own target refused, a closed own PR beside task text a fresh entry, a foreign in-message PR still context; the resolver sets prIsThreadOwn off the thread's record PR wherever the reference came from; the unit end reads the pull request the machine holds before declaring no pull request — a child whose record names a pull request it updated on another branch gets its (re-)review round at the pushed head, at round 0 and after every findings step alike; the pr-check follows the held pull request by number and answers its live state — open at a fresh head, merged, or terminal closed with the closer, on which no review is briefed[unit] src/core/ship/preflight.test.ts::shipPreflight — the entry table over (task text, PR source, thread's own, PR facts) (agent-ship item 10, issue 1799)::*; src/core/dispatcher.test.ts::agent:ship (the hand-off to the plan runner)::a re-issue naming the thread's OWN pull request by URL beside new task text ADOPTS it — never a fresh plan branch, never 'context' (issue 1799); src/core/repoContext.test.ts::resolveRepoContext: PR-source flags for ship::an in-message PR that IS the thread's own…; src/core/ship/coordinator.test.ts::the unit pipeline — every ending the ship pipeline has, on step returns::row 10 — unit end: a round-0 child whose record names a pull request it updated on ANOTHER branch (the thread's own, adopted late) runs the review round on it at the head the child pushed — never 'no pull request' over work that stands (issue 1799); src/core/ship/coordinator.test.ts::the unit pipeline — the event, the timeout and the confirmation (the durable half)::a findings step whose child repushed the adopted pull request on ANOTHER branch (issue 1799) carries on to the re-review at the pushed head…; src/channels/adminCoordinator.test.ts::pr-check keeps the machine's adopted pull request authoritative before branch discovery (agent-ship items 9, 10 and 12)::*; src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::a pr-check that verified the followed pull request CLOSED carries…; src/core/dispatcher.test.ts::a unit-owned thread (record 0051's reply-as-event and gone-instance rules)::a new task on an unfinished Ship PR cannot reissue the old task, ::a new task on an unfinished Ship PR cannot replay the old task when the bind omits its objective, ::a separate task phrased as…, ::a new Ship task on a completed PR starts fresh work with fresh caps
10: the recovered pull request's title (issue 1877; record 0064's unit_title move) — the open-or-edit from a dead coding child's pushed branch titles the pull request by the child's submitted description when the record holds one (used as is: the submit tool's gate already judged it), else by the branch head commit's subject when it passes the title rule (branchHeadSubject; a failing or unreadable subject claims nothing), else by a conventional fallback from the unit's title — the type its own leading word when the release config knows it else chore, the scope the plan's area (the first plan-id segment naming a code-map scope) when one is named, the whole line cut to the 72-character cap at a word boundary — never the unit heading verbatim[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/pr-check — the open pull request heading the instance's branch (item 9)::the recovered pull request's title (issue 1877)…, ::the recovered pull request's title (record 0064's unit_title move)…
10: a task row's resume re-reads the pull request at the unit pre-check, then opens the unit at its first review round in the driver — no branch and no coding child; the review child is briefed with the current head; the approve under merge: person ends merge-ready for a person; the ending names no coding run[unit] src/core/coordinator/driver.test.ts::the plan runner's driver — a resume at review (agent-ship item 10)::*, src/core/ship/coordinator.test.ts::the unit pipeline — every ending the ship pipeline has, on step returns::a resume at review…, src/core/coordinator/contract.test.ts::isCoordinatorUnit — one unit's row::a resume at review is the pull request number…
12: the ship request is claimed on the ledger without a seed and a taken hand-off keeps its row live with state.hosting set, nothing finished on the ledger; a final reply that throws records failed, never completed; a throw anywhere after the registry row exists — the ledger claim, the hand-off — finishes the run failed with its record and a ❌ card, never a row left running with no runner behind it; the card closes ✅ when the runner took the request and ⚠️ when it refused it by name, the run completed either way[unit] src/core/dispatcher.test.ts::agent:ship (the hand-off to the plan runner)::the ship request is claimed on the run ledger without a seed…, ::a final reply that throws…, ::card close is truthful…; src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::a final reply that throws…, ::a hand-off that threw…, ::a ledger claim that threw…
5, 7: the children's turns — typed review and check sources shape the findings ask; an approved review with four fixed resolutions and zero new findings plus failed checks asks only for CI repair and issues only check IDs, never closed resolution IDs. Review-only, mixed and prose-only asks stay truthful without inferring a verdict or source from prose or ID spelling. Every finding, review context, disposition and publication rule remains; the coding child has no contract. Re-review retains the prior findings and matched dispositions[unit] src/core/coordinator/briefs.test.ts::composeChild — the child a brief names::an approved review with four fixed resolutions and failed checks briefs only the check IDs…, ::finding-source wording uses typed review and check arrays, not IDs or review prose, ::a findings brief binds exact review and check IDs…, ::a review brief is a review child on the pull request…, src/core/ship/reviewChild.test.ts::buildShipReviewTurn — the review round's one user turn (item 5)::*
5, 7: the spawn route — a findings brief is dispatched into the unit thread as the requester with agent:coding in the text and the coordinator tag as its only option, refused busy while a run is live there and dispatched when none is, a run that took the thread meanwhile answered from that run; a review brief is dispatched into the unit's thread as the review preset (a read identity), a run live there making it busy, a row written before record 0055 keeping its review thread; unit-start opens the unit's thread and no review thread, writes it on the row, answers the same thread twice and opens none again, gives a generated plan's unit the requesting thread and opens nothing, and refuses without a channel or on a failed open with the row unchanged[unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::spawn with a findings brief is refused busy…, ::spawn with a review brief dispatches the review child into the unit's thread…, ::spawn with a contract brief composes the coding child's turn…, ::unit-start opens a plan unit's thread through the requesting thread's channel and no review thread…, ::unit-start without a channel that can open a thread is 503…
7, 15: the driver — a review that requests changes is followed by the findings step under <unit>/<round>/findings, a coding spawn briefed with the review run and clipped to the unit's clock, its wait, read and pr-check under that name, then the re-review briefed with the review run and the coding run that answered it; one unit-start per unit, never a fix step[unit] src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::a review that requests changes is followed by findings and re-review at the moved head…
Verdict findings validated fail-closed; an approve over a finding at or above the severity to address downgraded (agent-review.md item 5a); LGTM token contract holds with findings below the level present[unit] src/core/reviewVerdict.test.ts::review verdict → post body::findings (agent-ship item 6)::*
6: submit_dispositions: only a complete set of distinct exact issued IDs records; invalid or unbound calls do not overwrite the last valid set, and a corrected call wins in the same child; a context without the sink says no run is recording; the coding child's record carries only its accepted set and a run that submitted none carries no key; the runner's exact match still drops stray legacy IDs for its state, report and re-review note[unit] src/tools/submit.test.ts::submit_dispositions tool, src/core/dispatcher.test.ts::coding PR post-step (docs/reference/specs/pr-description.md)::a findings child refuses unknown IDs…, src/core/ship/coordinator.test.ts::the unit pipeline — every ending the ship pipeline has, on step returns::the dispositions a coding run recorded are matched to the round's finding ids…, src/core/coordinator/briefs.test.ts::composeChild — the child a brief names::a review brief is a review child on the pull request…
9: the merge-ready read keeps a conflicting approved head with the live runner (issue 1460) — before any ending is published, the runner re-enters its own rebase step; an unchanged patch carries the approval back to checks, a changed patch returns to review, and every later base move takes the same path; an unsquashed fix-up commit (fixup!/squash!/amend! subject) is still reported with the squash as the remedy; a clean state and an empty list leave the headline to the checks; the facts ride pr-check (mergeableState, fixupCommits), unreadable GitHub leaves each field out, and fixupCommitSubjects reads only the subject line[unit] src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::two successive base moves on an approved pull request are rebased by the runner and end merged without a person's command; src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::pr-check for a unit looks up the unit's branch and remembers the pull request on the row; src/execution/githubPulls.test.ts::githubPulls::the plan runner's merge — mergePullRequest and fetchCommitChecks::fixupCommitSubjects names the head's self-declared fix-ups…
9: a conflicting pull request returns the runner's typed conflict at the merge door before checks are read, and the machine re-enters the same runner-owned rebase path — mergeable/mergeable_state ride the facts (null mergeable = still computing), zero checks stays pending only on a mergeable pull request, and the coding child's contract carries the pre-push base re-fetch[unit] src/execution/githubPulls.test.ts::githubPulls::fetchPullRequestFacts (ship entry checks)::mergeable and mergeable_state ride the facts…; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/merge — the runner's squash of a unit's pull request (item 9)::a conflicting pull request returns the runner's typed rebase outcome before checks are read — zero checks stays pending only on a mergeable pull request; src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::two successive base moves on an approved pull request are rebased by the runner and end merged without a person's command; src/core/ship/contract.test.ts::renderContract — one block under \## Contract`, fixed sub-headings in a stable order::renders the five sections in order…`
9, 10: the remaining dispatching transitions use the same fresh state and one head-ref read carrying existence plus its commit tip — a positive attributable receipt exists only when the PR object's head agrees exactly with that ref; the ship-only verdict guard blocks a deleted or unreadable head even when GitHub still reports its last sha; both a conflicting and a changed rebase stop at pr-check before spawning their coding or review child and pin any dispatch to the refreshed head; an open-or-edit recovery re-reads the recovered pull request before returning it to the machine; unknown branch state retries and dispatches nothing throughout[unit] src/execution/githubPulls.test.ts::githubPulls::fetchPullRequestFacts (ship entry checks)::one ref read yields a positive attributable head only for exact PR/ref agreement…; src/core/reviewRound.test.ts::runReviewPostStep (explicit AgentDef decides the post)::agent-ship item 9: a deleted or unreadable same-repository head branch blocks…; src/core/ship/coordinator.test.ts::the approved-head rebase transition (agent-ship item 9)::agent-ship item 9: conflict and changed rebase outcomes re-read…; src/channels/adminCoordinator.test.ts::pr-check recover — the answer says why nothing was recovered, and GitHub being down is never none::agent-ship item 10: a recovered pull request is refreshed before dispatch…
9, 10, 12: pull-request terminal transitions (issue 2185's reported merge-race fixture) — every child/check/merge transition reads state, head and branch existence first; an unknown branch state retries and dispatches nothing; a merge between review dispatch and verdict steers the live review to end, records but ignores a late verdict and spawns no findings child; a merge during findings steers the coding child to end without another push and considers a late recorded push only through completed findings and merged-source-head proof; after a moved-head child drains, a fresh adopted-PR read makes a merge or close terminal before review can restart; the ending facts read follows and enriches the adopted PR number before branch discovery, so a merge or close on its branch ends that adopted PR even when another PR heads the unit branch; closed ends closed with the closer; deleted heads receive no child; the report attributes merged by <login> at <sha> without calling the actor a person; the runner's own guarded merge still ends merged[unit] src/core/reviewRound.test.ts::runReviewPostStep (explicit AgentDef decides the post)::a pull request merged or closed while a ship review runs…; src/core/ship/coordinator.test.ts::the unit pipeline — a pull request already merged: a re-issued plan, or a merge that lands during a round::issue 2185: a person's merge between review dispatch and verdict…, ::agent-ship item 9: after a moved-head review child drains…, ::issue 2185: a person's merge during a findings child…, ::issue 2185: a moved review head restarts that review…, ::issue 2185: a pull request closed while review runs…, ::merge-ready in one round on a plan branch…; src/core/coordinator/driver.test.ts::the plan runner's driver — a resume at review (agent-ship item 10)::agent-ship item 9: the ending's fresh facts read follows the adopted pull request number…; src/channels/adminCoordinator.test.ts::pr-check keeps the machine's adopted pull request authoritative before branch discovery (agent-ship items 9, 10 and 12)::an unknown head-branch state fails closed…, src/channels/adminCoordinator.test.ts::POST /admin/coordinator/checks — the round's checks read at the reviewed head (record 0055, item 9)::agent-ship item 10: an unknown head-branch state dispatches no check read…, src/channels/adminCoordinator.test.ts::POST /admin/coordinator/merge — the runner's squash of a unit's pull request (item 9)::agent-ship item 10: an unknown head-branch state dispatches no merge…, src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::steer tells a superseded child to end without publishing…; src/execution/githubPulls.test.ts::githubPulls::fetchPullRequestFacts (ship entry checks)::reports a deleted same-repository head ref explicitly…, ::carries the merge actor and resolves a closed-unmerged pull request's closer…
9, 10, 12: after a moved-head review or findings child drains, including a failed child with no final push receipt, the runner names that child and the previous bound head in its fresh PR check. The admin door verifies the child's unit, step and identity, original publication owner, open PR and exact ref; it reads the ref again and atomically advances only the binding before the state machine decides recovery or re-review; adoption alone never authorizes review of a foreign findings head. The foreign head is never recorded as that child's push. A return to the already-bound head needs no adoption. A failed findings child's head on the unit ref may advance the binding for the fresh read, but its recorded push is routed through the unfinished-findings gate, not a new review, and earns no push credit; its local head alone is not a push receipt. A completed findings child's own head goes through verified findings publication and credits its push only with proof. Missing proof, a rival owner or a moving ref refuses without a binding change.[unit] src/core/ship/coordinator.test.ts::completed findings recovery — typed completion plus independently verified remote head::a superseded unpushed findings child cannot restart review before its required fixes are redone, ::does not restart review when a failed findings child left an unattributed moved head, ::a failed findings child's recorded pushed head cannot bypass its completion gate, ::a foreign first read that returns to a failed child's pushed head still applies the completion gate, ::issue 2185: a moved review head restarts that review…; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::a superseded check accepts a returned bound head without adopting or changing its push, ::a superseded check credits a completed*, ::a superseded check refuses to credit a bare approval without findings authorization, ::a failed findings child with a salvaged push hands its head to the findings gate without publication credit, ::a superseded check cannot credit a child's head without verified findings publication, ::a superseded check cannot credit a child's head after the ref moves during findings proof, ::a drained findings child hands a foreign moved head to the findings gate without claiming its push, ::a drained review child can adopt the moved head for re-review, ::a failed findings child with no push receipt hands a moved head to the findings gate, ::a foreign moved head refuses…
9, 10: round-zero coding on an already-bound original pull request carries its child ID in the ordinary pr-check; only a completed same-unit writer with the original dispatch binding, sole push to the exact PR/ref/head, complete child history, exclusive owner and second remote head read can atomically advance the original publication head and push credit before review. No foreign or moving head, competing writer, unverified push or replacement PR gains authority.[unit] src/core/ship/coordinator.test.ts::the unit pipeline — the event, the timeout and the confirmation (the durable half)::round-zero coding on an adopted PR names its completed writer at pr-check so the original binding can advance; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::round-zero coding/pr-check advances only the original PR binding for its verified completed writer, ::round-zero coding/pr-check refuses*
9: a runner rebase that pushes a new head verifies the exact PR ref and owner, then atomically advances its publication binding and last push before returning; the next PR check reads that bound head. Only a lost rebase outcome permits a verified head move to advance the review target without claiming a push. An explicit conflict or a complete report without a result never adopts another writer's head; a contradictory reported head or stale owner cannot claim the binding.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/merge — the runner's squash of a unit's pull request (item 9)::the runner's rebase durably advances its publication binding to its verified pushed head, ::the runner's rebase refuses a reported push when the PR ref points elsewhere, ::a reported rebase conflict wins over an unrelated verified head move without rebinding it, ::a rebase report without a result cannot adopt an unrelated head move, ::an unrecorded rebase result cannot adopt a mutable head as a push receipt
9: a pull request already merged when the merge door reads it ends the unit merged with by: other, the merge commit and the time — the door answers a typed merged outcome from the facts (merged_at/merge_commit_sha), the machine ends the unit and the plan completes[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/merge — the runner's squash of a unit's pull request (item 9)::a pull request already merged when the door reads it…; src/core/ship/coordinator.test.ts::the unit pipeline — the event, the timeout and the confirmation (the durable half)::a merge answered merged with by other…; src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::a merge door answering merged by other…
9: the merge's pending wait is event-driven — the machine waits on checks-settled-<head> at the approved head under a fallback timeout of one MERGE_WAIT_CHUNK_MS chunk (the old poll's cadence, clipped to the remainder of the merge wait's own carve — the merge wait is a round: its minutes are carved from the unit's remainder when the door is first asked, its ask 60 and its floor 10, and a remainder under the floor ends the unit merge_refused naming the numbers), so an undelivered event never slows the door's re-ask, the driver waits through waitForEvent, and the intake publishes the event only for a signature-verified, fully settled head[unit] src/core/ship/coordinator.test.ts::the unit pipeline — the event, the timeout and the confirmation (the durable half)::the merge…; src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::the merge's pending wait…; src/core/coordinator/checksIntake.test.ts::the GitHub check-run intake — the checks-settled event's publisher (http-ingress item 12)::*
9: merge and enqueue persist pending permission under the exact executing Workflow, action, full approved target and canonical owner before one native mutation. Lost replies cannot replay; only exact native fulfillment resolves them. Enqueue acceptance retains active ownership through stop until a merged target or known-entry absence, including a moved head, is proven. Incomplete GraphQL receipts and errored queue reads are unavailable, never success or removal[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/merge — the runner's squash of a unit's pull request (item 9)::*; src/core/coordinator/unitEffect.test.ts::unit current effect::*; deploy/cloudflare-memory/runLedger.test.ts::unit effect owner transaction::*; src/execution/githubPulls.test.ts::githubPulls::the merge queue — the rule, the enqueue and the queue's outcome (issue 2011)::*; src/core/coordinator/driver.test.ts::Workflow effect receipt ordinals::*
9: the merge queue — a base with a merge-queue rule enqueues instead of squashing under the exact durable unit effect, without a squash landing; unreadable rules authorize neither mutation, and error prose cannot select enqueue; a queued re-ask reads the queue's outcome (still enqueued with the position, removed with the queue's own reason or without one, merged by other from the facts, 502 on an unreadable queue); a door that cannot enqueue, a plain 405 and GitHub's refusal of the enqueue are refusals by reason; a repository without a queue merges directly as before[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/merge — the runner's squash of a unit's pull request (item 9)::a base with a merge-queue rule enqueues instead of squashing (issue 2011)…, ::a 405 with the queue's wording on a repository whose rules could not be read enqueues too…, ::a \queued: true` re-ask reads the queue's outcome, never the squash…, ::a repository without a merge queue merges directly as before…; src/execution/githubPulls.test.ts::githubPulls::the merge queue — the rule, the enqueue and the queue's outcome (issue 2011)::*`
9: the merge:person path names "queued" in its line too (issue 2011) — the ending's facts pr-check reads the base's merge-queue rule beside the checks (baseHasMergeQueue) and the merge_ready report's remaining-gate line says the merge enqueues (gh pr merge --auto) and the queue merges on its own; without the fact, or with the rules read answering no queue, the line is byte for byte today's, and an unreadable rules read leaves the field out[unit] src/core/ship/coordinator.test.ts::the merge queue — the door enqueues instead of merging (issue 2011)::the merge:person path names \queued` in its line too…; src/core/coordinator/driver.test.ts::the plan runner's driver — a resume at review (agent-ship item 10)::the ending's facts read carries the base's merge-queue fact (issue 2011)…; src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::pr-check for a unit looks up the unit's branch and remembers the pull request on the row`
9: an enqueued unit stays live until the queue decides — the machine records the enqueued boundary once on the unit, marks every later ask queued, and ends merged as today when the queue merges; a removal becomes the round's finding (check:merge-queue, blocking, the queue's reason) under a dequeued note and a fix round follows like a red check — the round cap standing when the rounds are spent and a removal with no review run to brief from ending merge_refused — and one still queued past the merge wait's budget ends merge_refused naming that the queue merges on its own[unit] src/core/ship/coordinator.test.ts::the merge queue — the door enqueues instead of merging (issue 2011)::*; src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::a merge door answering enqueued keeps the unit live…
9: the review post-step's typed outcome — posted, skipped, failed — that merge-ready stands on[unit] src/core/reviewRound.test.ts::runReviewPostStep… (posted/skip/failure outcome rows)
9: the severity gate, the runner's defense in depth — a posted approve carrying a finding at or above the level in force continues into the findings step as a request_changes does, and its round note carries the gate (level + id (severity) findings); one whose findings all sit below the level ends merge_ready with no gate on its note and the report naming the level, its source and the skipped findings; maxRounds still caps the loop[unit] src/core/ship/coordinator.test.ts::the severity gate — an approve's findings held to the level in force::*
9: the gate is a detector — the driver forwards a gated approve's gate on the round route and only that round carries it; the route holds the gate to its shape (400, nothing appended, when malformed), keeps it on the unit row's round, draws gate fired on the card and warns in the log[unit] src/core/coordinator/driver.test.ts::*::a gated approve — one the child's parser should have downgraded — reaches the \round` route…; src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::round carries the gate when the coordinator's severity check fired…`
9: the round verdict (record 0055) — after a posted approve the machine's checks step reads the runs at the reviewed head: a red check becomes a check finding (id check:<name>, severity blocking, the conclusion and URL) under its own checks_failed round note and the findings step runs as for a request_changes — never merge_ready and never the merge step at a red head — the finding riding the findings and re-review briefs by value and the dispositions matching it by id — and told apart by the machine-set check flag, so a reviewer's own check:… id is never misrouted into the briefs' checks rows[unit] src/core/ship/coordinator.test.ts::the round verdict — the checks step at the reviewed head (record 0055)::a red check at the approved head yields a check finding and a findings round…; src/core/ship/coordinator.test.ts::the round verdict — the checks step at the reviewed head (record 0055)::a reviewer's finding whose id starts with check: is a reviewer's row, never a check finding…
9: a pending check registers at the head and waits on checks-settled-<head> in the merge wait's chunks then reads again — the wait is only a head with no failed check's: a red check with another still pending is the round's finding at once, with no wait added — an unreadable GitHub reads as pending, a head still pending at the ask's end proceeds with the ending's facts naming it, a green head adds no wait, a head with no check reported and no required contexts waits one chunk of grace and never more, and a reviewed head the machine never learned skips the step[unit] src/core/ship/coordinator.test.ts::the round verdict — the checks step at the reviewed head (record 0055)::a pending check registers at the head…; src/core/ship/coordinator.test.ts::the round verdict — the checks step at the reviewed head (record 0055)::a red check with another still pending is the round's finding at once…; src/core/ship/coordinator.test.ts::the round verdict — the checks step at the reviewed head (record 0055)::a green head ends merge-ready with no wait added…; src/core/ship/coordinator.test.ts::the round verdict — the checks step at the reviewed head (record 0055)::a head still pending at the ask's end proceeds…
9: the decision table's expected cell (issue 2063) — a required check whose run does not exist yet (the repository's approve workflow at the verdict instant) reads as pending: the head waits on the settled event and ends merge-ready only once every expected check reports green; the checks route hands the round reader the pull request's own base for the required contexts and registers the head in the merge-wait book[unit] src/core/ship/coordinator.test.ts::the round verdict — the checks step at the reviewed head (record 0055)::green with the approve run not yet created…; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/checks — the round's checks read at the reviewed head (record 0055, item 9)::an expected check not yet reported registers the head…
9: an empty required-check launch gets one self-heal — the classifier carries every required context beside the missing subset, so an unrelated title check cannot hide that none reported; after one grace chunk the machine asks once to re-fire pull_request, the bot closes then reopens the pull request without moving the head, recording pending before each native state write and leaving the durable effect incomplete until an accepted close is restored open; unknown responses retain the original owner without replay; a successful effect appends checks restarted to the round card before the ordinary bounded wait resumes; the event is never re-fired twice[unit] src/core/ship/checkFindings.test.ts::classifyRoundChecks — the merge door's reading joined with the classifier's::carries every required context beside the unreported subset…; src/core/ship/coordinator.test.ts::the round verdict — the checks step at the reviewed head (record 0055)::no required check after one grace chunk re-fires the pull_request event once…; src/core/coordinator/driver.test.ts::the plan runner's driver — the entry checks resume a re-issued plan's unit (agent-ship item 10, issue 1689)::an empty required-check launch reasks pending effect reconciliation before recording the refire; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/checks — the round's checks read at the reviewed head (record 0055, item 9)::a refire ask closes and reopens the pull request through the one recovery seam…; src/execution/githubPulls.test.ts::githubPulls::frozen check retry and PR state writes::writes one PR state and accepts only the exact native target and requested state; src/core/coordinator/checkRecoveryEffect.test.ts::durable check recovery::restores a known accepted close after stop under the original effect and does not close twice; src/core/coordinator/checkRecoveryEffect.test.ts::durable check recovery::never cancels the reopen obligation after its close was accepted; src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::round appends the boundary to the unit's row and redraws the card…
9: the decision table's draft cell (issue 2063) — a draft pull request holds: the step waits for the ready event and continues once the head is ready; still a draft at the ask's end the unit ends held naming the draft and the person's next step, never an exit with no cause; a red check on a draft still buys its fix round; the checks route answers the draft fact beside the runs and registers the head so the ready event wakes the hold[unit] src/core/ship/coordinator.test.ts::the round verdict — the checks step at the reviewed head (record 0055)::a draft pull request holds…; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/checks — the round's checks read at the reviewed head (record 0055, item 9)::a draft pull request is answered as the head's own fact…
9: no path after an approve verdict leaves the unit without a named ending (issue 2063) — every checks answer (unreadable, none reported, green, queued, expected, red, red beside pending, suspected flake, draft), under merge: person and merge: runner alike, leads to a named ending or the round's own next step, and a unit row a seal finds with a thread and no ending prints the cause and the next step, never the bare unfinished[unit] src/core/ship/coordinator.test.ts::the round verdict — the checks step at the reviewed head (record 0055)::regression (issue 2063): no path after an approve verdict leaves the unit without a named ending…; src/core/runsService.test.ts::RunsService.stopRun — a hosted parent: soft refused, hard seals (record 0060)::a hard stop seals the parent \stopped_hard` with the units' state as its answer…`
9: the round cap bounds fix rounds, never the terminal steps (issue 2023) — an approval in the last round the cap allows proceeds to the checks step and the merge (merge: runner) or merge_ready (merge: person) exactly as an earlier round's; a red check at that approve's head, with no fix round left, ends round_cap with the report naming the red check, never an unended unit; only a findings verdict at the cap ends round_cap with the no-approval headline[unit] src/core/ship/coordinator.test.ts::the round cap bounds fix rounds, never the terminal steps (issue 2023)::*
9: a human-gated-only round parks the live unit with the finding as pending state before the round cap; a person's later unit-thread reply, or a pull-request comment from the requester's bound GitHub account in the verdict's GitHub timestamp second or later, resumes a fix round carrying the finding and answer, then re-review; an adopted attempt applies the same boundary and trusted-account rule and fixes before reviewing; an unbound or differently bound commenter cannot steer the requester's write-capable child; one actionable finding beside a human-gated one keeps the ordinary fix round; the bot's unit-end posts the parked question on the pull request beside the board's copy, best effort[unit] src/core/ship/coordinator.test.ts::the held ending — every finding the round would act on is human-gated (issue 1990)::*; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/merge — the runner's squash of a unit's pull request (item 9)::unit-end leaves the unit's ending on its board issue…
9: a segment wake atomically retains its indexed answer and clears the unit's idle state before resumed work; replay returns the same answer without clearing a later idle episode, while a non-segment answer keeps the unit idle. The resumed findings writer can then advance its exact publication binding and reach re-review[unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::a same-second GitHub answer to a parked human-gated finding resumes its segment without spending a renewal; deploy/cloudflare-memory/runLedger.test.ts::run ledger — the coordinator's unit rows (item 50)::the validating wake boundary accepts a first-segment resume…; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::ordinary findings publication retains segment*
9: the flake rule — a suspected flake (a timeout/stall on a shard whose named test files the changed paths never touch, judged conservatively) is re-run once before it becomes a finding, through the Actions rerun-failed-jobs retry or the check-run rerequest for a check no Actions run backs (the Depot CI legs); a second failure is the finding, a real failure beside a suspect spends no re-run, and an undispatched re-run (retried: false) spends the retry with the head read again at once, never a wait[unit] src/core/ship/coordinator.test.ts::the round verdict — the checks step at the reviewed head (record 0055)::an untouched-shard timeout re-runs once…; src/core/ship/coordinator.test.ts::the round verdict — the checks step at the reviewed head (record 0055)::a flake re-run the bot could not dispatch spends the retry without a wait…; src/execution/githubPulls.test.ts::githubPulls::frozen check retry and PR state writes::freezes native retry IDs after complete exact-head reads and verifies Actions URL locators against native run facts; src/execution/githubPulls.test.ts::githubPulls::frozen check retry and PR state writes::accepts only native 201 retry dispatch acknowledgments and refuses invalid IDs without calling GitHub; src/core/coordinator/checkRecoveryEffect.test.ts::durable check recovery::freezes native IDs and pending before each write, then replays only the recorded answer; src/core/coordinator/checkRecoveryEffect.test.ts::durable check recovery::retains a lost reply without treating unchanged native checks as proof or replaying; src/core/ship/checkFindings.test.ts::suspectedFlake — a timeout/stall on a shard the changed paths never touch::*; src/core/ship/checkFindings.test.ts::classifyRoundChecks — the merge door's reading joined with the classifier's::*
9: the transient re-run (issue 1932) — a round-0 coding child dead on a retryable model-call failure (failure: provider_transient or model_stream_incomplete off its record, riding read-record and the driver's parse) with nothing pushed (no_commits) re-runs round 0 once under fresh /a2 step names, the boundary the round outcome transient; a transient with an ordinary push recovers as any dead child, while a WIP salvage push aborts naming the resumable head; a second transient in the same round ends the unit transient, the report saying the task was not the problem; no_base, a plain none, or a record naming no transient keeps the abort[unit] src/core/ship/coordinator.test.ts::the transient re-run — round 0 dies on a provider transient with nothing pushed (issue 1932)::*; src/core/coordinator/driver.test.ts::*::a coding child dead on a provider transient with nothing pushed re-runs round 0 once…, src/core/coordinator/driver.test.ts::*::an incomplete local stream failure survives read-record and earns Ship's one empty-branch retry; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/read-record — the renewal's facts off the record (decision 0046)::answers a failed child's failure by name…
9: the bot's checks route reads the classified runs at the head naming the pull request whose changed paths judge the flake rule, falls back to the merge door's plain reading when no round reader is wired, registers a pending or unreported head in the merge-wait book, dispatches a retry ask's re-run, and holds its body to shape[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/checks — the round's checks read at the reviewed head (record 0055, item 9)::*
9 / 5: the review child's request carries the instance's level as its severity: directive — the default minor when the instance carries none, the instance's own level otherwise — so its verdict parser holds the approve to the hand-off's answer (agent-review.md item 5a)[unit] src/core/coordinator/briefs.test.ts::composeChild — the child a brief names::a review brief is a review child on the pull request…; src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::spawn with a review brief dispatches the review child into the unit's thread…
9: the hand-off resolves the level once (directive > user > channel > org) and writes it on the instance beside merge; absent, the default (minor, org) is written; review.addressSeverity is validated at load on the org block and every scope, refused by name under ship, and resolveAddressSeverity layers the scopes; config set --review.addressSeverity lands on the scope and refuses a value outside the ladder; severity:<level> parses and strips like the other directives[unit] src/core/coordinator/handOff.test.ts::the severity to address — resolved once, written on the instance beside…; src/config.test.ts::ship caps block (agent:ship pipeline)::review.addressSeverity: parsed at load…; src/core/commands/config.test.ts::config set --review.addressSeverity::*; src/directives.test.ts::severity:<level>::*
Skills scoped: address-review-findings → coding only, re-review-delta → review only; manifest check green[unit] skill-store scoping test; npm run skills:check
Contract (item 13): a unit is its heading to the next ###/## heading, verbatim, the closing thematic break dropped, its bullets parsed whole; an absent unit is undefined[unit] src/core/ship/contract.test.ts::parsePlanUnit — a unit is its…::*
Contract (item 13): the spec rows a unit names — path-prefixed, backticked, linked and bare spellings, comma/and lists, ranges, a parenthetical after the number, the bare spec item <n> attributed to the nearest preceding spec and to nothing past another file, each row once[unit] src/core/ship/contract.test.ts::specItemRefs — the spec rows a unit names…::*
Contract (item 13): a spec's numbered item is resolved verbatim with the validation rows naming it (a leading <n>:, item <n>, a list, a range) and their proof bindings; an absent item or an unreadable spec is said[unit] src/core/ship/contract.test.ts::parseSpecItem / parseValidationRows / itemNumbersNamed…::*
Contract (item 13): contractFromPlan carries exactly the named rows resolved, the rules, the guards and the rebase; a unit naming no rows receives none; an unknown unit throws naming the plan's units; the task compatibility builder is gone — a generated plan's contract is contractFromPlan over its one built unit, the title the first line cut to 80 characters, a heading in the request text kept as text[unit] src/core/ship/contract.test.ts::contractFromPlan — the typed contract for one unit::*, ::one contract builder — a generated plan's unit is built, never parsed back (agent-ship item 16)::*
Contract (item 13): the render is one block under ## Contract with the five sub-headings in a stable order and the same bytes for the same object; over the budget it drops Approach, then the agent rules, then the spec items' text, then the proof rows, each with a placeholder and a closing line, never a part that is not there; the rows' names, the unit's test scenarios and the guards survive every cut; the default budget holds a task contract[unit] src/core/ship/contract.test.ts::renderContract — one block under…::*
Contract (item 13): the first instruction orders the push as soon as the fast gates pass, hands relevant verification to CI with any fix a further commit, and points at the preset's rebase-before-every-push rule[unit] src/core/ship/contract.test.ts::renders the five sections in order under the one heading, the unit body verbatim, each spec row with its proof rows, the rules, the guards
Contract (item 13): the first instruction points at the coding preset's fast-gates paragraph (FAST_GATES_POINTER naming THE FAST GATES, the changed-set forms, the full runs CI's) instead of re-stating it — the rendered contract carries none of the gate commands and the preset prompt plus the contract carry the paragraph exactly once — and keeps the receipts (GATE_RECEIPTS): each command's exit line to the PR description's validation table as the row's proof, an unrun gate to the handoff's unproven list, and no "cheapest proving checks"[unit] src/core/ship/contract.test.ts::the first instruction points at the preset's fast gates instead of re-stating them…
Contract (item 13): the first instruction points at the preset's rebase-before-every-push rule (REBASE_POINTER naming REBASE BEFORE EVERY PUSH, always and not configurable) instead of re-stating it — the rendered contract carries none of the rebase steps and no conditional re-fetch, the preset prompt plus the contract carry the paragraph exactly once, and a base-less contract renders the same pointer[unit] src/core/ship/contract.test.ts::the first instruction points at the preset's rebase-before-every-push rule instead of re-stating it…
contract render (item 13): the block from the plan, the specs it names and the rules file (AGENTS.md, else CLAUDE.md, else none) under the root the plan path implies, the measured length in the JSON and on the receipt line; --root, --branch, --onto and --max-chars; an unreadable spec rendered as not found; a missing plan not_found, an unknown unit invalid_input naming the units, a malformed id refused by the schema without echoing it; CLI-only under contract:read, no chat baseline[unit] src/core/commands/contract.test.ts::contract.render::*, ::repoRootOfPlan — where the plan's specs and rules are read from::*
Contract (item 13): a coding run dispatched with a contract carries the rendered block as the last text part of its first user turn, the request's text first; the first USER turn takes the block and a transcript without one gets a user turn made of it; a run without a contract runs its messages as given[unit] src/core/dispatcher.test.ts::coding PR post-step (docs/reference/specs/pr-description.md)::a coding run dispatched with a contract carries the rendered block as its first user turn's last text part…, src/core/ship/codingChild.test.ts::withContractInFirstUserTurn — the unit's contract in the child's first turn (item 13)::*
Contract (item 13): a review run handed a contract carries the same rendered block in its system prompt right after the REVIEW TARGET block; a run without one carries no block[unit] src/core/reviewRound.test.ts::makeSystemComposer (head-pinned composition with an explicit AgentDef)::a unit contract block sits right after the REVIEW TARGET block…
Contract (item 13): the runner composes a coding child's contract from the plan at the base ref and a review child's from the same unit; a generated unit's from the ship run's record read back[unit] src/core/coordinator/briefs.test.ts::contractFor — the unit's contract from the repository at the base ref::*, ::composeChild — the child a brief names::*
Handoff (item 14): parseHandoff accepts the three lists trimmed, drops unknown keys and accepts empty lists; refuses a non-object, a missing or non-array list, a non-object entry, a blank, non-string or over-long field and too many entries, each as a string naming the path; the optional landed list stays absent when not given and is validated like the others when it is; isHandoffShape accepts a parsed handoff, its JSON round-trip, an over-long field and an absent landed list and refuses a missing required list, a non-array list, a non-object entry and a non-string field; isEmptyHandoff reads a landed row as not empty; redactHandoff redacts every string leaf, landed included, and never mutates its input[unit] src/core/ship/handoff.test.ts::parseHandoff — the validator the submit_handoff tool answers with::*, ::isHandoffShape — the structural check a stored record runs::*, ::redactHandoff — every string leaf through the redaction seam::*
12a: the quiet thread report is ONE line in the user's words — the merge-ready line with the round count and the pull request link, Waiting for you: with the human-gated rows, Stopped., Round cap reached: with the last review's non-zero severity counts — with the verdict, the skipped findings, the declined list and the remaining-gate sentence as verbose asides, no internal token on any quiet line, and the verbose copy byte for byte the full report[unit] src/core/ship/coordinator.test.ts::the quiet thread report — the ending is ONE line in the user's words (record 0066)::*
12a: the ending and round-outcome words — merge_ready reads merge-ready, round_cap reads round cap reached, wall_clock_cap reads out of budget, checks_failed reads checks failed, already_landed reads merged, no word carries an underscore, an unknown token prints as itself — and the card's unit lines, the idle why, the plan summary and the web unit surfaces print them[unit] src/core/pipelineStanding.test.ts::the user's words for endings and round outcomes (record 0066)::*; src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::unit-end writes the ending and the pull request on the row…, ::unit-end with an idle ending writes idle…; web/src/pages/unitPage.test.ts::UnitPage — the unit is the reading unit (item 28)::lays out both threads' runs in round order…; web/src/pages/runPage.test.ts::RunPage — history mode::a conductor's page lists the runs it spawned…
12a: the review child's quiet reply is one line — LGTM or Changes requested: with only the non-zero counts, most severe first, and the pull request link — while the verbose copy, an unposted verdict and an unitemized one keep the full render so the text always lands somewhere a person reads it[unit] src/core/reviewVerdict.test.ts::verdictCountsLine + the quiet channel reply — one line in the user's words (record 0066)::*
12: a unit whose scope already landed — round 0 with a handoff naming where it landed and a pr-check reading no commits over the base ends already_landed naming each landing (in the handoff's order) and the child's run, the round noted completed, the report opening ✅ Already on <base> with no compare link, no renewal line and no re-issue prompt, under a grant of zero and under one with renewals alike; without the handoff's say-so, with commits over the base or with the fact unread the round-0 abort stands; the same return applied twice changes nothing[unit] src/core/ship/coordinator.test.ts::the unit pipeline — every ending the ship pipeline has, on step returns::a unit whose scope already landed…
12: through the parse — the bot's aheadOfBase and the record's landed list reach the machine, the unit-end carries the already_landed report with the landing and no compare link, the dependents run on the base that carries it, and the plan finishes completed; the bot's plain pr-check compares the branch with the instance's base and answers aheadOfBase (zero or the count), leaves it out when the compare could not be read or the instance has no base, and never compares on a recover check; commitsOverBase GETs the compare and answers ahead_by, undefined on a non-2xx, a body without the count, a network failure or no credential, never a throw[unit] src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::a unit whose scope already landed ends already_landed through the parse…, src/channels/adminCoordinator.test.ts::pr-check none — the branch's commits over the base ride the answer (issue 1699)::*, src/execution/githubPulls.test.ts::githubPulls::commitsOverBase (the branch's commits over the base)::*
12: the coding post-step — a proven push with no description and no open pull request over a branch with no commits over the base is reported as nothing to open with no compare link; a count over zero, an unread compare or no reader keeps the compare-URL note[unit] src/core/codingPrPostStep.test.ts::runCodingPrPostStep (callable with explicit inputs)::no description + proven push + no open PR, and the branch has no commits over the base…
12, 14: the handoff's landed list renders under ### Already landed after the other lists on the board comment and as Landed: what — where ledger rows; submit_handoff carries it as an optional fourth list in its schema, forwarded and counted when given and absent when not; both coding prompts name it with its fields as the way a unit with nothing left to push ends done[unit] src/core/ship/handoff.test.ts::renderHandoffComment — the unit's board-issue comment::a landed list renders under…, src/core/ship/handoff.test.ts::renderHandoffLedgerRows — rows in the plan's follow-ups ledger shape::a landed row reads…, src/tools/submit.test.ts::submit_handoff tool::the landed list (issue 1699)…, src/agents/registry.test.ts::coding prompts: the unit handoff (agent-coding item 9)::the optional landed list (issue 1699)…
Handoff (item 14): the ledger rows are one row per entry in the ledger's three-column shape with disposition open and the unit and PR as the source (the unit alone without a PR), none for an empty handoff, cells escaped against a pipe, a backslash and a newline[unit] src/core/ship/handoff.test.ts::renderHandoffLedgerRows — rows in the plan's follow-ups ledger shape::*
Handoff (item 14): the board comment leads with the unit id and the PR link (or says there is no pull request), puts each non-empty list under its own heading, omits an empty list with its heading, ends with the ledger rows in a fenced block, renders the same bytes for the same object, flattens a newline inside a field, and renders nothing for an entirely empty handoff[unit] src/core/ship/handoff.test.ts::renderHandoffComment — the unit's board-issue comment::*
Handoff (item 14): a coding run's submitted handoff rides its run record, redacted; a run that submitted none carries no handoff key[unit] src/core/dispatcher.test.ts::coding PR post-step (docs/reference/specs/pr-description.md)::a submitted handoff rides the run record, redacted…
Handoff (item 14): the parent posts it — the runner's unit-end names the last coding child, and the board comment carries the rendered handoff under the ending's report for a run of this instance, the report alone for a run outside the instance, a missing run or a record without one; a unit without an issue leaves none; a failed comment never fails the step[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/merge — the runner's squash of a unit's pull request (item 9)::unit-end leaves the unit's ending on its board issue…, src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::a one-unit plan runs coding, then review to approve…
A child's handoff reaches its unit's board issue without a person writing it there[agent] (human-gated.) Run one unit of the program through the runner with the unit's board issue open; the coding child submits a handoff; the unit's board issue gains the runner's ending comment with **Handoff — U<n>** under the report, and runs get <id> on the child's run shows the same object.
Runner (item 15): the plan graph — every unit heading in order with the dependencies its bullet names (lists, to ranges, none), its slug and its branch; unitSlug, unitBranch and parsePlanBranch refusing every other shape; planIdOf from the file's name or a throw naming the path; planInstanceId names the plan; parseShipPlanRequest reads plan <path> [units …] and nothing else[unit] src/core/ship/coordinator.test.ts::the plan graph — units, their dependencies, their branches::*
Runner (item 15): the cursor — ready units in the plan's order once their in-play dependencies are done; a selection narrowing the plan with a dependency outside it counted satisfied; a failed unit blocking its dependents transitively while every other ready unit stays in play; an unknown unit, a unit started twice or before it is ready, and a cycle refused by name[unit] src/core/ship/coordinator.test.ts::the plan cursor — ready units in dependency order, a failure blocking its dependents::*
Runner (item 15): every ending on step returns — merge-ready on a plan branch through the merge step and off one for a person's merge; the findings round trip: request_changes enters the findings step (a coding spawn briefed with the review run under the review round's index, never a fix round), then the re-review briefed with the review run and the coding run that answered it, the declined disposition on the report; the findings step's busy answer waiting a chunk under the unit's clock and asking the same step again, and a busy answer under the reserve ending the unit wall_clock_cap with no coding run started; the round cap with its declined/unaddressed split; the wall-clock cap with the children's budgets clipped and the reservation; a stop naming its mode, honored around a posted verdict and never over one; the aborts (no pull request at round 0 with the child's words leading, a branch not created, a failed child, no new head unless every finding was declined); no verdict; an approval that did not post; a resume at review skipping round 0[unit] src/core/ship/coordinator.test.ts::the unit pipeline — every ending the ship pipeline has, on step returns::*
16: a recovery request uses its complete immutable admission ID for the Workflow and every child step. Every recovery request to the bot carries that ID and Workflow; a retired delayed create or a Workflow presenting another action cannot bind to the current claim or adopt its child. Historical receipts retain their original recorded identities.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::a retired delayed create cannot bind to a later action using the same source review
16: recovery checks refuse omitted or foreign action tags before external mutation; a recovery child retired during brief or event reads cannot dispatch. A mismatched Workflow/action pair fails before reading cached Workflow steps. Complete action and child names fit the platform bounds and reject ambiguous stage paths.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::requires the current action before rerunning recovery checks; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::refuses a recovery child retired while its brief and events are read; src/core/coordinator/driver.test.ts::runOriginalUnitRecovery::refuses a Workflow carrying another action before reading cached steps; src/core/coordinator/recoveryStep.test.ts::recovery execution identity::*
16: agent:ship recover unit <instanceId>:<unit> and agent:ship renew unit <instanceId>:<unit> are the explicit original-unit recovery grammars; Slack intake removes trailing app attribution before this grammar, and any residual trailing prose refuses. It runs before ordinary preflight and generated-plan hand-off, passes the resolved requester and thread to the deterministic recovery boundary, and reports the one durable recovery Workflow; a create whose commit cannot be determined reports the retained checkpoint as indeterminate rather than refused. Malformed recovery-shaped text and a refused recovery start no generated plan[unit] src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::recognizes only the explicit recover-unit grammar with a valid original unit key, ::refuses recovery-shaped malformed text instead of handing it to a replacement generated plan, ::runs explicit recovery before ordinary preflight and hands the resolved requester and thread to the deterministic coordinator operation, ::routes an explicit renewal as the requester without opening a replacement plan, ::reports an indeterminate Workflow start with the retained checkpoint instead of calling it refused, ::surfaces a deterministic recovery refusal and never falls through to generated-plan hand-off
16: a typed original-unit recovery request reaches Ship's deterministic recovery boundary when the operator is on; shadow mode still records its decision beside the recovery. An ended pipeline's ordinary continuation head gate does not take the recovery command. The recovery boundary still checks the original requester, thread, unit and budget before admitting a writer.[unit] src/core/dispatcher.test.ts::agent:ship (the hand-off to the plan runner)::sends an explicit MCP recover-unit command to the original unit with the operator enabled, ::keeps the shadow operator decision beside an MCP recover-unit command, src/core/dispatcher.test.ts::a unit-owned thread (record 0051's reply-as-event and gone-instance rules)::routes original-unit recovery past the ended pipeline continuation gate
16: when the operator is on and the preliminary coordinator unit-owner read fails, only a complete typed original-unit recovery or renewal command on a finished thread defers ownership to the deterministic original-unit boundary; normal agent, repository, profile and thread gates remain, and malformed recovery, ordinary work and an unreadable live owner still refuse. The boundary receives the actual requester and thread so a foreign requester cannot gain the original writer.[unit] src/core/dispatcher.test.ts::agent:ship (the hand-off to the plan runner)::admits typed original-unit recovery through a failed preliminary owner read without starting replacement work, ::does not turn a failed owner read into admission for malformed recovery or ordinary work, ::does not bypass an unreadable live thread owner for an original-unit command, ::passes an authorized but foreign requester to the original-unit identity check
16: an ended round-zero coding unit with no PR may continue on the same instance, unit, branch and grant only when the original requester, immutable task, complete priced child history, accepted push at the freshly verified original ref, unused lease and sole writer are proven. The durable claim admits one coding checkpoint; a replay cannot mint another. Missing publication proof, discarded private work, moved ref, foreign actor, unknown spend or exhausted time refuses before any writer. The new coding child receives the original request and prior run handoff, then must publish a PR and receive fresh review and checks.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — pre-PR same-unit coding::admits one verified original-ref coding continuation, ::refuses unverified work and moved heads before claiming, ::refuses missing publication proof, discarded private work, and pending branch publication, ::refuses a foreign requester and exhausted original budget; src/core/coordinator/driver.test.ts::runOriginalUnitRecovery::starts a recovered round-zero coding child on the original unit; src/core/ship/coordinator.test.ts::original-unit recovery accounting::opens a pre-PR coding checkpoint with the original spend and no PR
10, 16: a terminal legacy original unit without a publication binding reconstructs it only from one same-unit completed coding head and one exact-round review head, complete evidence listings, fresh open same-repository PR/base/ref/full-head agreement and exclusive ownership. Caller identity fields cannot supply authority; partial bindings, contradictory hints, foreign or ambiguous children, moved heads, active owners and lost CAS refuse without mutation. Repair and recovery claim are one CAS; failed Workflow admission restores the exact legacy row.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::legacy binding repair*
10: an ordinary authorized findings writer's completed push may advance only the exact publication binding carried by its durable coordinator tag, compared by fields rather than JSON key order. The current review boundary must authorize findings via request_changes, checks_failed after a preceding same-round approval with no intervening review verdict or new review start (same-round repeated failed-check and check-restart notes do not interrupt it), an approval with a findings gate, or a typed same-round dequeue before the child starts; a bare approval or orphan check outcome cannot credit a push. The original child key/requester/thread, fresh PR/ref/base/head and sole ownership must agree; head, binding and optional recovery checkpoint advance by one CAS before exact-head re-review. An earlier failed/interrupted attempt without a push does not make its completed retry ambiguous only when its identity and finish-before-retry ordering are proven. Complete listings include live attempts; any competing success, push, overlap or incomplete evidence refuses. The current segment and contiguous durable same-segment wake answers determine the exact /rN namespace, never a child's claimed prefix; stale/future resumes, missing/foreign/duplicate wakes and changed binding fields refuse without mutation.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::ordinary findings publication*, ::a superseded check credits a completed*
16: a requester invoking renew unit <instanceId>:<unit> after an expired findings lease and a completely proven no-work failed child spends one original durable grant renewal for a fresh original-caps lease on the same unit, review round, branch, PR and exact head; the claim retains historical spend, checks the cost cap, grant, complete child/review evidence and exclusive owner, and retries cannot admit a second writer or spend another renewal. Unavailable/moved remote ref, incomplete history, competing child/push/owner, spent grant or cap, or other stages refuse without a new writer.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::renews one expired no-work findings lease on the original unit and replays the same claim, ::refuses expired findings renewal with * before claiming another writer, ::refuses a different renewal message after the first claim without consuming another renewal, ::refuses a competing writer arriving during renewal revalidation, ::keeps a committed renewal spent if its Workflow fails before a child starts; src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::routes an explicit renewal as the requester without opening a replacement plan; src/core/dispatcher.test.ts::agent:ship (the hand-off to the plan runner)::routes an explicit MCP renewal to the same original unit without invoking the operator
16: one additional fixed 90-minute recovery clock for arbitrary terminal stages, once per explicitly authorized original, preserves identity and cumulative policy and refuses unsupported callers, durations, budgets, stages, owners or heads[gap] Not implemented; the extension follow-up linked from record 0051 owns the entitlement and boundary proofs. Binding/head tests and budget-diagnostic tests are not extension proofs.
16: historical recovery proves child stage order from priced records and requires each later child to start after every earlier round's completed transition in its segment. Spawn precedes a child's own started note, so that child may already be running or finished when the note is persisted. An explicitly started coding or findings stage needs its own pr_opened before a later review; retry, abort, stale or missing notes do not grant that transition. Review notes must likewise authorize the later stage. Legacy child records without a start note remain eligible only through complete priced and ordered evidence.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::historical admission::*
16: every priced historical child belongs to an authorized stage of the original unit. A retained start note binds its child; a narrowly evidenced terminal failed no-push attempt may lack that note, while an unrecorded orphan stage cannot spend or grant authority. Every optional child PR and pushed head names the original PR and branch. A rebase child between approved and later review rounds is a priced coding stage with its own note and terminal transition.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::historical admission::*
16: historical stages advance contiguously within a segment: review round two needs the findings or rebase child and its completed transition after review round one. A repeated same-round review attempt has no unique historical accounting boundary here and refuses cost-capped recovery; no new review transition is created to make that attempt eligible. An approval can authorize a findings child after merge-queue removal only when the typed dequeued note precedes that child. A bare approval or dequeue note without a findings child cannot skip to the next review.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::historical admission::refuses review round two after*, ::refuses same-round review retries without a durable accounting boundary, ::binds findings authorization to the posted review and typed transition*
16: a retained review note that authorizes rebase or findings agrees with that attempt's verdict and posted review at the original PR and reviewed head. A typed checks failure follows a posted approval from the same attempt; a typed queue dequeue follows that approval before findings starts. Every approval note carries exactly the findings gated by the instance's severity level and that level, or no gate when none qualify; a missing, incomplete, extra or wrong-level gate cannot authorize rebase or another review. A conflicting child cannot gain authority from the row's outcome word.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::historical admission::binds an approved review note to the posted review before rebase*, ::binds findings authorization to the posted review and typed transition*
16: recovered review spend is counted even when the review stops or ends without a verdict; an unknown child price makes cumulative spend unknown instead of leaving the admission total unchanged.[unit] src/core/ship/coordinator.test.ts::original-unit recovery accounting::charges a recovered review that ends with*
16: a zero-turn findings child stopped by the workspace attach drain wait in waiting_deploy may count as no work only with a complete final setup refusal, failed drain and attach spans, and no model, tool, artifact, push or PR activity. The unchanged owned PR head and original lease still gate the same-unit recovery.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::resumes original findings after a complete drain-wait setup refusal, ::refuses incomplete drain-wait no-work evidence*
16: unknown recovery budgets carry a stable exact reason through the deterministic response and the Ship refusal: missing/invalid historical caps or grant (including renewals above the grant maximum), start or ending time, configured cost cap with undurable spend, invalid or ungranted segment history, inconsistent round chronology, foreign wake origins, or a stopped-segment lease without its resume time. The original row must belong to the instance; segment time cannot postdate its review. No current default, inferred spend or fresh clock replaces missing evidence.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::attributes an unknown recovery budget*, ::historical admission::*; src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::preserves the exact budget refusal reason without starting a replacement plan
16: historical recovery audits the retained original children before admission: exact original keys, requester/repository/thread, persisted complete cross-thread evidence (the store filters before the unchanged 200-row bound by parent instance or the exact same-unit key prefix or bare <instance>:<unit> key, including contradictory parents, or either original thread; a bare same-unit key remains a candidate and fails the child identity audit; unrelated global history cannot fill that bound. A full relevant set still refuses. The Worker explicitly attests a complete valid set only after parsing every retained summary, including valid JSON with an invalid record shape that identity filters would omit; a missing attestation, cursor, malformed row or duplicate response refuses. A fresh uncached whole live-ledger read precedes history, includes foreign generations and threads, and retains contradictory identities rather than merging them away. Store/ledger unavailability is recovery_evidence_incomplete before reserving ownership or claiming recovery), finite ordered child times within their recorded segment and review boundaries, a child for each started round even when the child began before its persisted round-start note, unique child idempotency keys, and nonnegative per-model metered dollars. Missing prices remain unknown even when the current price table could reprice the tokens. Overlapping, duplicate-key, live, foreign, missing or truncated history cannot supply publication or budget authority, including original-unit children recorded outside the expected unit/review threads. Binding-less initial findings and a completed moved-head findings check are separate fixtures, not live acceptance.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::historical admission::*
16: a retained cost-capped original with complete priced child history carries its original grant and cumulative spend through the durable recovery claim, driver, and resumed pipeline; it admits only when spent dollars remain below the cap, refuses at or above it as recovery_cost_cap_exhausted, and ends before any further child when the cap is met or spend becomes unknown. Missing or mismatched carry refuses as recovery_budget_unknown, never resets the cap. Any earlier or current checks_failed note without retained typed check findings and dispositions returns recovery_checks_evidence_unavailable (check_findings_not_retained), even after a later review; an old approval or push proves no check disposition.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::historical admission::carries complete priced history into a cost-capped original unit, ::sums fractional child prices in the same order as the durable receipt, ::refuses a claimed cost-capped recovery when its original grant changes, ::refuses a proven spent cost cap without resetting the cumulative total, ::refuses a cost-capped terminal row whose historical review price is missing, ::refuses approve then checks-failed findings/pr-check when the historical check findings were not retained, ::binds findings authorization to the posted review and typed transition (failed checks without retained findings), src/core/ship/coordinator.test.ts::original-unit recovery accounting::refuses a capped recovery whose durable spend carry is missing, ::carries admission spend and stops before a further child when the original cost cap is exhausted or unknown, ::reconciles a completed findings push before ending at the original cost cap, src/core/coordinator/driver.test.ts::runOriginalUnitRecovery::passes the original cost cap and cumulative spend into the recovered review, ::does not dispatch a capped recovery when the plan row lacks spend carry
16: a spent original cost cap refuses with the retained spent dollars and cap, and Ship explains the limit and the next action without starting another unit. The advice to continue an existing pull request applies only while it is open; a merged pull request needs no recovery. A terminal no-verdict report at the cap, quiet or verbose and including recovered units, gives the same conditional advice instead of offering a restart of the exhausted unit.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::historical admission::refuses a proven spent cost cap without resetting the cumulative total, src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::explains a spent original cap without starting another plan, src/core/ship/coordinator.test.ts::the unit pipeline — every ending the ship pipeline has, on step returns::a capped no-verdict stop does not suggest restarting the exhausted unit, src/core/ship/coordinator.test.ts::original-unit recovery accounting::shows the separate PR action when a recovered no-verdict review spends the original cap
16: evidence reads cannot extend the original lease; a newly expired lease refuses before ownership reservation or the claim CAS.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::historical admission::refuses a lease that expires during evidence reads before claiming the row
10, 12: a typed merged or closed PR observation survives an incomplete findings ending, including an unaccepted push, a drained child and a later check after a foreign head. The quiet report is one outcome line naming the terminal PR; verbose retains the recorded local commit, missing outputs and authorized follow-up PR guidance for remaining work; it never invites another findings run on that terminal PR or parks it for a reply. A different or unreadable merged source head never proves the fix landed; even an equal head cannot bypass missing outputs. Open-PR completion and exact-head recovery gates remain unchanged.[unit] src/core/ship/coordinator.test.ts::completed findings recovery — typed completion plus independently verified remote head::keeps a typed merge terminal when incomplete unpushed findings are*, ::keeps incomplete unpushed findings terminal when the pull request closed, ::does not substitute an earlier child commit when terminal findings lack a final commit, ::does not call an older pull request head landed when it merged after the completed findings push failed
16: after posted H1 changes and a completed findings push to H2, repeated H2 review-start notes for the same round/key may recover read-only review on the original unit only if the sole terminal review child has complete zero-turn setup-attach refusal evidence, no verdict, post, head or push, no competing live child, and the open owned PR/ref remains exactly H2. Refresh complete child history, the exact remote ref and the open owned PR binding immediately before ownership reservation and claim; a child, push, closure, base change or head-identity change after earlier reads refuses even with a cached H2 PR observation. The final findings record must agree with the listing on H2 push, child identity and historical spend. A completed, priced, uniquely bound original round-zero coding child's sole accepted push to H1 before the H1 review is not a competing writer; any other same-ref push or live run still refuses. Preserve the H1 review, H2 publication, historical spend, lease and grant; actual work, unavailable evidence and a changed head refuse. A typed evidence_unavailable diagnostic distinguishes missing or changed claim-boundary history, unreadable ref or unverified PR binding from verified no-work.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::recovers an H2 review attach after the priced original round-zero child pushed H1, ::refuses H2 review attach despite priced original coding when there is a *, ::recovers a failed H2 review attach after repeated same-key starts and an H1 findings push, ::refuses H2 review-start recovery with *, ::refuses H2 review-start recovery when *, ::refuses final H2 findings with *
16: a failed review child ending in admitted may count as zero-work setup refusal only with complete contiguous final history, a single admitted state followed by adjacent workspace-attach start and previously-started memory-read end in either order, the same attach span's error and one setup_failed/system refusal. No model, tool, head, push, PR or verdict may exist; missing or mismatched evidence, foreign identity, or an altered owned head refuses before ownership claim. The original preparing/workspace pattern, historical priced spend, cap, lease, exact ref and single-owner claim remain intact.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::recovers a zero-work admitted review attach refusal with concurrent memory read on the original H2 head, ::recovers admitted review attach refusal when memory read ends before attach starts, ::refuses admitted review attach evidence with *
16: after that exact admitted attach refusal, a zero-work review may finish with only ordered, matching dispatch.refuse start/end and post.reply start/end cleanup pairs, or close spans opened before admission; post-refusal extra or malformed spans, model/tool work, or changed child identity refuse before claim. The complete final history, zero usage, original owner, exact PR/ref/head, historical spend cap, remaining lease and single-owner guards still apply.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::recovers admitted review attach refusal followed by ordered refusal and reply cleanup, ::refuses admitted review cleanup with *
16: a terminal findings retry preserves the original unit/ref/PR and accepts only a numeric /aN/pr-check boundary attributable to the single completed findings attempt. The terminal key and its exact completed attempt must be proven even at an unchanged head; check-shaped steps with malformed action suffixes or trailing path components are refused before continuation selection, and an invalid or unmatched check never falls back to another coding run. All attempts are inspected before selecting publication evidence: only earlier failed/interrupted no-push attempts with proven identity and finish-before-retry ordering may be ignored. A stale/malformed check key, moved head, foreign evidence, competing success or push (including to a different head), live/overlapping attempt or incomplete evidence starts nothing and leaves the row and owner unchanged. No unit-start, branch, round-zero or replacement PR is created.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::terminal findings retry continuation*
16: a failed findings child contributes $0 only when a complete persisted full record with explicit zero usage, server setup refusal and pre-work states ending in preparing proves no model span, tool, head, push, PR or artifact. Prior-thread context events published before preparing (including five at sequences 13–17 in a 59-event setup refusal) are setup evidence, not work; context published after preparing refuses. An aborted original findings round at the unchanged, freshly verified PR/ref/head may then resume the posted review's findings on the same unit, remaining lease and original grant, without treating the failed child as a completed fix, push or verdict. The optional lastPush hint may be absent on a bound open-PR row only when the durable publication's owned expected head and fresh PR/ref proof both equal the reviewed head; a defined mismatched hint still refuses. Missing publication, foreign owner/ref/head, stale PR, truncated or unpriced usage, any work, a rival, moved head, foreign requester or exhausted lease/cap refuses; priced children keep their historical meter dollars.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::accounts a complete server setup refusal as $0 and resumes the original same-head findings without work credit; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::resumes the original findings after a zero-work child when lastPush is absent but the bound PR and fresh ref match the reviewed head; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::refuses defined mismatched lastPush even when zero-work findings have no continuation hint; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::refuses model span as no-work setup evidence; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::refuses context after preparing as no-work setup evidence; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::refuses a missing final event stream even when the complete summary reports zero usage; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::refuses a setup-only recovery after moved head without changing the original row
16: original-unit recovery reconstructs only the claimed original row. A posted request_changes continues through findings and re-review under <original instance>:<unit>/recovery/...; one uniquely attributable completed findings child is consumed whether or not it changed the head, and only its proven push may advance the durable publication head before re-review; the separate unfinished-patch gate below may consume an ended failed child only to restart findings. A terminal original findings/pr-check may resume directly at read-only re-review only when that record proves the current head, exact original key/ref/thread and ordering; when its step-threw unit-end carried no optional headSha, the existing publication binding remains the reviewed-head authority rather than requiring an invented lastPush; every other moved head refuses. no_verdict starts one read-only review. Neither path runs unit-start, branch, round zero, or a generated unit; actual child admission and post-attach setup recheck owner, exact PR/ref/base/head and the absolute end of the exact remaining lease for coding and review. The durable recovery boundary is carried across resume and restart, constrains dispatch and hard-stops the child. The physical recovery Workflow is only a new checkpoint, never a claimed resumption of the terminal Workflow: lifetime review rounds, renewals already spent, and enforceable remaining limits carry forward. A cost-capped human-gated recovery idle retains only proven unspent original renewals, and its recovered merge-ready report names how many were spent. An uncapped legacy recovery checkpoint retains zero renewal capacity even when the plan grants renewals. Checkpoint-local attempt counters use its distinct step namespace; cost-capped rows carry complete historical spend and the original grant, while rows whose cumulative spend is unavailable refuse. Elapsed-category reports explicitly exclude unavailable earlier activity. Lifecycle and restart-death events use the recovery Workflow only as transport while retaining original identity; canonical ownership reads active recovery claims independently of terminal parent hosting. Every recovery mutation names the exact recovery Workflow; ambiguous CAS responses are reconciled before cleanup. Human-only and draft outcomes settle as typed terminal holds, queued or later replies never start replacement work, failed reply persistence tells the sender, and terminal settlement records the evidence as consumed and replays without releasing a successor owner[unit] src/core/coordinator/driver.test.ts::runOriginalUnitRecovery::*; src/core/ship/coordinator.test.ts::original-unit recovery accounting::names spent original renewals in a recovered merge-ready report; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::*; src/core/ship/coordinator.test.ts::original-unit recovery accounting::labels elapsed categories as checkpoint-local instead of claiming the legacy unit's missing lifetime history; src/core/coordinator/contract.test.ts::sendRunFinished — the event a terminal record sends::uses a recovery checkpoint only as transport while the payload retains the original instance; src/core/runnerOwnership.test.ts::canonical runner pull owner::reads an active original recovery independently of parent hosting or process state
16: a salvage-pushed, incomplete or failed findings child may resume on the original unit only when its exact child identity, review boundary, observed PR/ref/head and sole salvage push agree. The separate unfinished-patch path below may preserve earlier same-ref pushes, but never credits them as the moved target. A failed child’s salvaged head moves the review target but never credits its push or bypasses the missing findings outputs; a competing writer, stale authority or moving ref refuses unchanged. Missing dispositions and description remain required before re-review. The child may start before its coding-start note is persisted, but must start after the prior review and finish after that note and before the coding boundary and unit ending.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::salvage recovery*, ::an ended failed findings child with one salvaged head resumes the original findings round without push credit, ::an ended failed child cannot claim recovery with*
16: an ended findings child whose immutable H0 publication lease was rejected after the requester moved the same PR/ref to H1 never earns push credit or redirects the required findings into review. Before any teardown, its unpushed H2 patch is stored privately with verified bytes, immutable source, original base and moved target heads, child identity and artifact key; an unreadable observation may reach salvage only through a verified owned checkout; its proven H2 remains on the final run record even if generic observation misses that checkout again, and a missing artifact is an explicit gap, never a claim that work was saved. A terminal same-identity continuation rechecks the sole owner, posted original findings, exact PR/ref/base/H1 and the patch artifact, then CAS-claims a new findings lease against H1 within the original caps. An earlier child push to Hx does not disqualify the H0-based patch, but a push to H1 does. The live unit ends at a still-foreign H1 even when Hx was pushed, so terminal findings recovery can apply the patch before any review. If its live PR check already adopted H1 as an uncredited target, recovery still anchors the posted review and child tag at H0, verifies the same immutable PR owner and ref, and accepts only that H1 binding; a third target or missing patch refuses. In-flight/uncertain writes, competing children, changed owners or identity, a second head move, absent work, and stale CAS all refuse without starting a writer. The next coding child receives the private patch and still must submit every disposition, an updated description and an exact pushed head before review.[unit] src/core/ship/coordinator.test.ts::completed findings recovery — typed completion plus independently verified remote head::a superseded unpushed findings child cannot restart review before its required fixes are redone, ::an earlier intermediate push cannot restart review of a foreign head before findings recovery, ::a completed findings child with an earlier push cannot apply its dispositions to a foreign head, src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::a saved unpushed findings child restarts required findings at the requester-moved head without push credit, ::restores unfinished findings after an earlier child push without crediting the requester head, ::restores H0-based findings after the live check already adopted H1 without push credit, ::refuses a saved patch when the durable target is neither H0 nor its verified H1, ::refuses an adopted H1 target when its H0-based patch is missing, ::refuses an unfinished patch when the child already pushed the requester head, ::refuses unfinished findings after an earlier child push with *, ::refuses unpushed findings recovery with * without changing the original row, src/core/codingPrPostStep.test.ts::salvageBudgetPush — a ship coding child pushes what it has at the budget (push-before-abort, agent-ship.md item 8)::saves a private exact-base patch when a seeded checkout loses its immutable PR push lease, ::refuses to stage work when an unreadable observation resolves to another branch, ::restores saved findings only after byte and exact-head verification, ::restores saved findings in a verified cold clone instead of guessing the seed path; src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::records a verified seeded patch and source head when cold workspace observation cannot find the checkout; `src/core/runEventLines.test.ts::parseRunEventLines::reads a saved findings patch event only with its exact identity key``
16: a terminal findings child with one canonical, fully bound settlement recording a created local H3, a fixed pre-effect cold 409 rejection, verified private bundle key/size/SHA, no accepted push or pending Door effect and complete priced history may claim one original-caps findings continuation only while its posted review, owned PR/ref/base/head and freshly read remote ref remain at H2, its settlement base. The bundle's H3 may be a sibling of H2: the new child checks exact bytes, one source head and a prerequisite trusted by H2, imports only into a local recovery ref of its clean bound checkout, then reconciles onto H2 through the normal Door. Neither import nor settlement credits H3 as published or force-updates the PR; a foreign/stale binding, moved H2, missing/corrupt object, mismatched digest/head/prerequisite, pending effect, rival, exhausted cap/lease or repeat claim refuses. Existing text-patch recovery is unchanged.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::claims one verified saved bundle at the same H2 without crediting its sibling H3 as published, ::refuses saved bundle with *; src/core/codingPrPostStep.test.ts::salvageBudgetPush — a ship coding child pushes what it has at the budget (push-before-abort, agent-ship.md item 8)::verifies a real sibling bundle against H2 and imports H3 locally without checking it out or pushing, ::rejects a source outside the trusted prerequisite even after local import, without publishing it, ::rejects absent, corrupt or mismatched checkpoint bytes, source heads and prerequisites before import; src/core/coordinator/contract.test.ts::saved findings recovery variant::admits only the exact bound bundle key, base, target and single source shape while retaining legacy patches; src/core/publicationSettlement.test.ts::publication settlement::records a fixed cold 409 refusal phase as rejected, never an accepted or pending effect
16: an ended original unit may recover at a verified review target newer than its last credited push. Read-only recovery requires a posted review at the target; unfinished-patch findings recovery instead requires the posted original H0 review, its child tag and verified H0-based patch for the same PR/ref and current H1 target. Without matching evidence, recovery refuses without changing the row. The target move never credits an unproven push.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::recovers a reviewed target advanced without crediting a push, ::refuses an advanced target without its own review or a verified original-head patch
16: a recovery claim refuses an approval submitted after the claimed changes-requested trigger, even when a different reviewer posted it. A completed findings push reaches an exact remote-head read and durable publication update before an exhausted recovery cost cap ends the unit, even when dispositions or the updated description are missing; those missing outputs never authorize review or merge-ready success. A stale remote head cannot become the publication head.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::post-approval recovery revalidates the claimed trigger before findings admission, src/core/coordinator/driver.test.ts::runOriginalUnitRecovery::verifies an incomplete findings push through the publication recovery route before the cost cap ends the original unit, src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::advances the recovery publication only to its completed findings child's exact head before re-review, src/core/ship/coordinator.test.ts::original-unit recovery accounting::checks a matching remote head before ending incomplete findings at the cost cap, ::checks a stale remote head before ending incomplete findings at the cost cap, ::preserves missing outputs at a terminal*source after a cost-capped findings push
16: the durable recovery listing asks SQLite only for rows that may carry an active recovery. Terminal valid history is excluded before application parsing; malformed rows are retained as fail-closed candidates, so corruption cannot be mistaken for “unowned” while valid active rows remain recoverable on the next complete read[unit] deploy/cloudflare-memory/runLedger.test.ts::run ledger — the coordinator's unit rows (item 50)::lists only active recovery rows in SQL, ignores terminal history, and fails closed on malformed candidate JSON; src/core/runnerOwnership.test.ts::canonical runner pull owner::retains canonical refusal*; src/core/runnerOwnership.test.ts::canonical runner pull owner::retains transport uncertainty instead of treating it as no owner
Runner (items 10 and 15): the recover path rewrites before it opens — the identity rewrite runs with the instance's repo, base, branch and requester over an empty start state before the create, and an unreadable rewrite is github_unavailable (asked again), never an open over unverified identities[unit] src/channels/adminCoordinator.test.ts::pr-check recover — the answer says why nothing was recovered, and GitHub being down is never none::the recover path runs the identity rewrite before it opens…
15: a review child whose typed sandbox fleet capacity failure occurred during setup before any model turn keeps its failed record and round count. The original unit reads the adopted PR again; only the same open PR with its live branch at the exact pinned full head earns a fresh child step under the same review round and remaining lease. A moved, missing or unreadable head dispatches no replacement; an exhausted lease reports that review never reached the PR because sandbox capacity kept refusing placement. An ordinary failed review without that typed fact remains no-verdict.[unit] src/core/ship/coordinator.test.ts::the unit pipeline — every ending the ship pipeline has, on step returns::a capacity failure before review starts rechecks the original PR head, then retries inside the same round, ::does not redispatch a capacity-blocked review when the PR head moved or cannot be verified, ::says capacity kept review from reaching the PR when the original lease cannot fit another wait
Runner (item 15): round zero's dead-child recover — a failed child's abort repeats the bot's reason for recovering nothing (no_commits, no_base, or neither claim on a bare none); the bot answers none with no_commits on GitHub's empty-branch 422 and no_base without a create, any other create failure is github_unavailable, and the driver keeps unrecovered through the parse. A findings run must complete: interrupted or failed findings work stops without a review even when it recorded an attempted push, and its report claims no unverified remote save.[unit] src/core/ship/coordinator.test.ts::the unit pipeline — every ending the ship pipeline has, on step returns::aborts: a round 0 that opened no pull request…, src/core/ship/coordinator.test.ts::the unit pipeline — the event, the timeout and the confirmation (the durable half)::a findings run that did not complete remains resumable…, src/channels/adminCoordinator.test.ts::pr-check recover — the answer says why nothing was recovered, and GitHub being down is never none::*, src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::a failed coding child whose recover pr-check answers none with a reason…
Runner (item 15): the wait is sliced — every wait one chunk, never the child's whole budget; a live answer waits the next chunk; the slices before the budget plus the margin sum to exactly that, the last the remainder; past it an overdue child is asked about every chunk, never in a zero-length wait; a finished record ends the wait as the event would; a busy wait is a chunk[unit] src/core/ship/coordinator.test.ts::the unit pipeline — the event, the timeout and the confirmation (the durable half)::the wait is sliced…
15, the deploy-roll signals beside the finish (run-history item 47a): each wait chunk arms the finish wait under the step's own name and the two companions under <step>/interrupted and <step>/resumed, all typed by the child's run id for one chunk; a child-interrupted event settles the wait like the finish — one chunk, the confirming read-record, the unit ending with the child's own reason, never the budget clip; a child-resumed event settles nothing — it is consumed, the next resumed wait is armed under <step>/resumed/2, and the chunk still times out into a read-record that finds the child live and waits again[unit] src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::a deploy roll's child-interrupted event settles the wait beside run-finished…, ::a deploy roll's child-resumed event keeps the wait…
15 (issues 1903, 1876): a child whose container is replaced mid-round resumes and its pipeline continues — a read-record answering restartedAs moves the wait onto the restarted successor and the successor's finish carries the round on, the unit never ending over a resume that succeeded; the bot's read-record answers an interrupted child's LIVE restart (same instance and idempotency key in the thread) as the child still running under the successor's id; a restart that follows a lost workspace is announced child_resumed, and only a request that cannot restart settles the parent as child_interrupted[unit] src/core/ship/coordinator.test.ts::the unit pipeline — the event, the timeout and the confirmation (the durable half)::a child whose container was replaced mid-round RESUMES and the pipeline continues…; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/read-record — an interrupted child (issues 1903/1876)::*; src/core/dispatch/reattach.test.ts::abandonLostWorkspace: the resumed run closes saying why, and hands its request back::a coordinator's child whose request RESTARTS publishes child_resumed…, ::a coordinator's child whose request CANNOT restart publishes child_interrupted…
15 (issue 1876): the interrupted ending names the actual cause from the ledger that saw it — the replaced resident container, the bot restart and the sandbox fault each get their own sentence, a record naming none claims none, and the boot gap alone claims the bot restart; the cause is classified off the interruption's own recorded words and answered on read-record as interruption[unit] src/core/ship/coordinator.test.ts::the unit pipeline — the event, the timeout and the confirmation (the durable half)::the interrupted ending names the actual cause from the ledger that saw it…, ::interruptionCauseOfWords classifies the ledger's own words…; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/read-record — an interrupted child (issues 1903/1876)::an interrupted child with NO restarted successor answers its facts with the cause off its own events…
15 (issue 2081): after a whole-process death leaves only a restarting close, fresh read-record dependencies expire it at its persisted deadline with the original container-replacement cause; the driver ends the unit interrupted on that note and never wall_clock_cap, while a successor found before or after expiry remains live[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/read-record — an interrupted child (issues 1903/1876)::a whole-process death leaves only the restarting record…, ::a successor wins before the restart deadline and when it appears before a later read after expiry…; src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::a fresh process's read-record expiry ends the unit on the recorded container-replacement interruption…
Runner (item 15): the durable half — a duplicate run finished advances the state once and a stray return changes nothing; a timeout is confirmed by read-record like an event; a live answer waits again under a new step name for the next chunk; interrupted ends with the ship-restart note naming the pull request when one exists; alreadySpawned proceeds to the wait without a second child; busy waits for the live run's end and asks the same step again; a refused spawn ends with the gate's name and a failed one as an abort; the merge's pending poll is bounded and a refused merge names its reason; an approve on a plan branch with no known head is a refused merge, never a person's merge-ready; a reopened pull request is adopted and none aborts; every step name is <unit>/<round>/<kind> without a colon[unit] src/core/ship/coordinator.test.ts::the unit pipeline — the event, the timeout and the confirmation (the durable half)::*
Runner (item 15): a pull request already merged — the pre-check under <unit>/pr-check ends the unit merged with by: other before any branch or child, the report saying it was already merged and when and never that the runner merged, the cursor marking it done and its dependents ready, the same return applied twice changing nothing; none or open proceed to the branch with nothing adopted; a merge landing during a round ends the unit the same way at the round's pr-check with the round noted completed, after a fix round too[unit] src/core/ship/coordinator.test.ts::the unit pipeline — a pull request already merged: a re-issued plan, or a merge that lands during a round::a unit whose pull request merged before the attempt…, ::the pre-check answering none, or open without the branch's tip beside it, proceeds to the branch and round 0 as before…, ::a merge that lands during a round…
2, 16 (record 0054): a preflight refusal is a Refusal — eleven ship_preflight_* codes, one per gate, the result carrying it so the dispatcher stamps the code and cause on the dispatch.refuse span and outcome; card and reply text byte-identical[unit] src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::refused at the preflight (ship allowed, coding not)…, src/core/ship/preflight.test.ts::*
16 (record 0054): the no-repo refusal guesses the one resident the request's repo token is near — the corrected line and the evidence ride the sentence, a tie proposes nothing — and only a request that resolved no repository pays for the one registry listing[unit] src/core/ship/preflight.test.ts::shipPreflight — the entry cases (agent-ship item 10) and the auto-merge fact (item 9)::the no-repo refusal guesses the one resident…
16 (record 0054): a hand-off refusal carries its plan_* Refusal on the outcome, rendered through the seam with the sentence unchanged; the code and cause land on the span[gap] the sentences are pinned by src/core/coordinator/handOff.test.ts::*; no test yet asserts the plan_* code on the span per site
16: no switch — ship.coordinator is refused at load by name whatever its value, pointing at the migration notes; any other unknown ship key is refused by name[unit] src/config.test.ts::ship caps block (agent:ship pipeline)::ship.coordinator is refused at load by name, whatever its value…
16: the rounds cap the hand-off writes on the instance (caps.maxRounds, item 8) bounds the loop's fix rounds alone, never a unit's terminal steps — an approval in the last allowed round still runs the checks step and the merge or merge_ready (item 9, issue 2023)[unit] src/core/ship/coordinator.test.ts::the round cap bounds fix rounds, never the terminal steps (issue 2023)::an approval at the cap proceeds to the checks step and the merge exactly as an earlier round's does…, ::an approval at the cap under merge: person ends merge_ready through the checks step…
16: the door guard — an operator-bound work request stays generated with merge: person even when its text spells a plan; the run records agentSource: operator and the decision event; a validated typed agent:ship plan … still hands off merge: runner[unit] src/core/dispatcher.test.ts::agent:ship (the hand-off to the plan runner)::an operator-bound work request with plan-shaped text stays a generated person-merged task, ::an operator-bound ship on a task hands off merge: person…, ::a seeded plan request in the same pull-request thread stays seeded…
16: a bounded main-agent work brief records the question, an explicit finding kind, a source URL, and query/result/time window for analysis; the atomic (main thread, act id) store claim links one original Ship instance/unit and rejects rival ownership; a child given that stored brief receives its attributed facts as data, not authority. A main task without private worker routing, a fresh private-audience check and a live main-run fence is refused before any claim or Workflow.[unit] src/core/coordinator/contract.test.ts::main-agent work brief::*; src/core/coordinator/handOff.test.ts::main-agent work hand-off::*; src/core/coordinator/briefs.test.ts::composeChild — the child a brief names::a generated unit carries its attributed query result and time window as data into round zero; src/core/coordinator/instanceStore.test.ts::main-agent task claims::*; deploy/cloudflare-memory/runLedger.test.ts::run ledger — main-agent task claims::*; src/core/coordinator/mainStart.test.ts::main-agent private worker start::*; [gap] deployed end-to-end worker proof
16: an initial Workflow create has distinct known-not-attempted, confirmed-created or existing, and attempted-unresolved outcomes. A typed pre-create authorization denial is not attempted; a saved instance starts unreconciled until its exact create is confirmed. A duplicate without attributable prior work stays unreconciled after the Workflow ends and cannot advance to a new plan attempt. For a private linked task, a duplicate or a same-id status read without attributable unit work keeps the saved admission pending; only a confirmed create establishes it. A lost reply retains the original durable act, instance, unit and ownership; the same-id status read establishes Workflow existence but cannot attribute an unreconciled create, while an unavailable status never licenses a new identity or a non-start claim. Main-agent work_start returns the saved work id with an explicit pending observation, and a replay binds that same act even with changed tool text. The public Ship parent remains hosted and unfinished through a pending create; boot keeps that owner past its deadline while Workflow status cannot be read.[unit] src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::initial Workflow admission keeps its identity and owner when the reply is lost; src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::a lost reply with visible same-id Workflow status stays pending until its create is attributable; src/core/coordinator/handOff.test.ts::main-agent work hand-off::an unreconciled private duplicate stays pending on same-id retry and later status reads; src/core/coordinator/mainStart.test.ts::main-agent private worker start::a lost create reply leaves one saved act and stays pending when same-id status alone cannot attribute private work; src/core/coordinator/instancesClient.test.ts::createInstanceViaShim — the bot's request for a coordinator instance::separates a pre-request configuration refusal from a sent request with an unreadable answer; src/core/coordinator/instancesRoute.test.ts::readCreateInstanceAnswer — the shim's answer as the bot reads it::typed pre-create denials are not attempted; created, duplicate, failed and unreadable replies keep their distinct outcomes; src/core/coordinator/instancesClient.test.ts::createInstanceViaShim — the bot's request for a coordinator instance::reads a typed authorization denial as not attempted but leaves an unknown 503 unresolved; src/core/coordinator/instanceStore.test.ts::*::confirms only the exact saved create and preserves its unit rows; deploy/cloudflare-memory/runLedger.test.ts::run ledger — the coordinator instance record (item 49)::confirms only the exact unreconciled create and leaves its unit rows intact; src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::an unreconciled duplicate cannot become a new plan attempt after its Workflow ends; src/core/dispatch/ship.test.ts::runShipBranch — the host key and the hosted marker (record 0060)::a lost create reply keeps the same hosted parent live with its saved instance while admission is pending; src/core/boot.test.ts::reclaimRuns — the hosted parent's classification (record 0060)::a hosted parent past its deadline keeps its owner when Workflow status is unreadable after restart; [gap] deployed private conversation receipt
16: a main-agent-linked unit may start only with a reconstructible private worker thread and IO; its child replies, progress and reports stay private to the main agent, with no fallback to the requester’s Slack thread. Ordinary run and unit pages expose only projected unit facts, not the stored work brief or a private ending; a private held or idle ending never comments its report on a pull request.[unit] src/core/coordinator/handOff.test.ts::main-agent work hand-off::holds a main task before any claim or Workflow until private worker routing exists; [unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::a plain fix hand-off binds its generated child to private IO before any spawn; [unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::keeps a private worker's held report out of hosted events and pull request comments; [unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::keeps a private worker's idle report out of hosted events and pull request comments; [gap] deployed main-thread report proof
16: plane stop <instance> (record 0064's runner_stop move) — a destructive runs:write command that stops the instance and its live children in one move: the stop mark first, the hosted parent hard-stopped, then only children with the exact parent instance id in a unit or review thread — never the parent or an unrelated same-thread run; an unconfirmed mark leaves the parent unavailable and unfinished; matching children are still attempted and the report names the partial outcomes; an instance the caller's predicate does not admit is unknown; a process without the run-history stores refuses by name; the report renders one line per thing stopped[unit] src/core/planeService.test.ts::plane stop — the runner_stop move (record 0064; issue 1924)::*, src/core/commands/plane.test.ts::plane.stop — the runner_stop move in one command (record 0064)::*
Hand-off (item 16): a plan request reads the plan at the base ref and writes the instance under the plan's id with the requester, thread, card, caps and run id and one row per unit in the plan's order; a selection narrows the rows and keeps a dependency outside it on the row; a task is a generated one-unit plan under plan-<generated id> marked plan: { id } with no path, its U1 on plan/<id>/u1; accepted inline media and its Slack staging source are one stable seed event after that row and before create, a create retry appends none twice, the first coding child stages it at attachments/0-<name> with a catalogue row while replay stages no second file, an over-cap seed is capped before Worker transport and records its dropped count, and no media leaves no event; a resume at review is that unit with the pull request, head and url on its row and a reply saying the review resumes with no coding round first; the refusals before anything is written — a plan the repository lacks, a name that is not a plan id, no base, no units, a unit the plan lacks — name the reason and create nothing; a null store refuses by name; a record left by an attempt whose create failed — no such instance on the platform — is replaced under the same id and the reply says so, a later attempt's leftover with the merged units skipped and the attempt kept; a latest attempt still running refuses naming the instance and its status, writes nothing and asks for no instance, as do a status not read as ended and a shim that could not say; an ended attempt is resumed as the next under plan-<plan-id>-<n> with the attempt on the record, the merged units skipped, a dependency on a merged unit kept on the row, the reply naming what is left and what was merged, a third re-issue finding the latest attempt, every named unit merged a refusal; a duplicate or attempted create with an unreadable reply retains the saved instance and reports pending status; same-id status proves existence but does not attribute an unreconciled create, while a known pre-request refusal or confirmed create failure says no Workflow was started[unit] src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::*, src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::a Slack task carrying one PNG hands its accepted bytes and staging source to the generated unit as one durable seed event, src/core/coordinator/instanceStore.test.ts::WorkerCoordinatorInstanceStore — the wire::caps an attachment event before the Worker's request-body fence, so accepted over-cap media becomes a dropped-count row instead of HTTP 413, src/channels/adminCoordinator.test.ts::the fold — a unit's thread events reach the pipeline's next step (record 0051's fold rule)::a ship-request PNG reaches the coding child's attachment staging and catalogue once; replay folds no second file, src/core/dispatcher.test.ts::inbound staging (record 0033)::a coordinator coding child stages a folded accepted PNG at its zero-based path, appends that path to the prompt and records it in the asset catalogue
Hand-off (item 16): a terse operator-bound Fix it. preserves the actor-stamped requester's original question, explicit issue URL and earlier general reconciliation as bounded attributed data on the original generated unit; its task identity remains the requester's words and the evidence does not select a repository or branch; selecting its issue turn uses the same quote- and code-stripped requester text as target resolution and parses whole GitHub URL tokens, so a later quoted example or a nested URL on another host cannot replace the real issue or claim its next assistant reply; an inherited target whose source cannot be reread starts no worker[unit] src/core/dispatch/operator.test.ts::requester-authored repository inheritance for a plain fix::keeps the real issue and its reconciliation when a later requester turn quotes another issue, src/core/dispatch/operator.test.ts::requester-authored repository inheritance for a plain fix::keeps the real issue when a later addressed turn only cites a nested GitHub URL, src/core/dispatch/operator.test.ts::requester-authored repository inheritance for a plain fix::still detects conflicting GitHub links inside prose wrappers, src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::attaches actor-stamped general research to a thread-sourced work unit after repo preflight, src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::persists the prior requester question, issue and reconciliation on the one generated unit without changing the task identity, src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::refuses a terse inherited target when its durable source turns cannot be read again, src/core/coordinator/briefs.test.ts::contractFor — the unit's contract from the repository at the base ref::*
Hand-off (item 16): a generated coding task stores its exact request, requester, thread, repository, source run/link and digest on the first instance and unit before any child starts. Reissue retains the first task; if the first unit write was interrupted, it restores the task and original admission run/link from the first instance even when the reissue came from another run. A child checks the digest and admission binding from the durable checkpoint; a missing, changed or mismatched checkpoint refuses recovery or coding instead of reconstructing a task from run history, a bounded transcript, or a generic fallback. A legacy bound PR may be reviewed without a task, but the PR alone cannot supply a fresh coding contract. A PR-only resume can start review, but its missing PR at pre-check cannot start a round-zero coding contract.[unit] src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::a task request is a generated plan of one unit…, src/core/coordinator/briefs.test.ts::contractFor — the unit's contract from the repository at the base ref::refuses a generated child whose authenticated task checkpoint is missing or altered, src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::refuses reissue when the original generated task checkpoint is absent, ::replaces an absent first Workflow after its instance was written without a unit, src/core/coordinator/briefs.test.ts::contractFor — the unit's contract from the repository at the base ref::a legacy bound pull request may be reviewed, but a missing task cannot brief coding
Hand-off attachment consumption (item 16): the first coding spawn carries the seed's image/document bytes, marks them consumed only after registration, and a second coding spawn sees none; a failure before registration leaves them for retry; a review spawn receives none; a replayed spawn folds no event twice[unit] src/channels/adminCoordinator.test.ts::the fold — a unit's thread events reach the pipeline's next step (record 0051's fold rule)::*
Hand-off (item 16): the bot's request to its shim POSTs the id with the coordinator bearer and reads the answer; a duplicate and a failure come back as said; an unreachable shim is attempted but unanswered; missing base URL or coordinator bearer is known not attempted and sends nothing; typed created, duplicate, failed and status replies must name the requested instance, while mismatches and unreadable replies remain unresolved[unit] src/core/coordinator/instancesClient.test.ts::createInstanceViaShim — the bot's request for a coordinator instance::*, ::fetchInstanceStatusViaShim — an earlier attempt's instance, as the platform has it::*, src/core/coordinator/instancesRoute.test.ts::readCreateInstanceAnswer — the shim's answer as the bot reads it::*, ::the instance status route — the pure halves both ways::*
Hand-off (item 16): through the ship branch — a task becomes a generated one-unit plan instance carrying the requester, thread, card, caps (the profile's minutes) and run id, the shim is asked, the reply says where it runs, the run ends completed with the ship profile on its record and nothing runs in the bot; a resume rides the row; a shim refusal is replied with ⚠️ and the run still ends completed; no instance store refuses before the shim is asked; a deployment without PUBLIC_BASE_URL, or without a coordinator entry in the token map, is refused naming the missing one; a hand-off that threw records failed and replies nothing[unit] src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::*, src/core/dispatcher.test.ts::agent:ship (the hand-off to the plan runner)::a deployment without the runner's prerequisites is refused naming the missing one…
16: the thread choice keys on the unit count; while any unit idles the parent card names it, and with the flag on the stop, renewal and interrupted notes direct the person to reply in that unit thread; a woken segment names the senders folded into it, and requester or stopper authority anywhere in that batch survives newer collaborator context[unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::unit-end with an idle ending writes idle {why, at, renewalsLeft, from, runId, spendUsd, handoff, wakes: 0} and no ending…, ::unit-wake folds every sender before deciding…, ::unit-wake reopens a stopped segment for any folded stopper…, src/core/ship/renewal.test.ts::renderRenewal — the card's words, as the record's trace has them::*, src/core/ship/coordinator.test.ts::the idle ending — an idling kind maps to \idle` when ship.idleDays is above zero (record 0051)::*`
16: the host key and the hosted marker (record 0060) — the run is created hosted with its label, claimed under the host key with the thread, hosted and the label in the ledger metadata and no session; one pipeline per thread: a live host-key row refuses a second ship by name with nothing handed to the runner and the live row untouched; an untracked answer that is not thread-live hands off as before[unit] src/core/dispatch/ship.test.ts::runShipBranch — the host key and the hosted marker (record 0060)::*
16: the hand-off keeps the run live (record 0060) — after a tracked, taken hand-off the registry row is unfinished, the ledger row is live with state.hosting (the instance and the deadline of the caps plus one hour) and the stream carries a second run_meta { instanceId }, while the card still closes ✅ and the ack still lands; a refused hand-off still finishes completed and leaves no live host-keyed row, so the next ship request in the thread claims the host key; an untracked hand-off finishes as today; no dispatch of any agent but the ship branch creates a hosted run[unit] src/core/dispatch/ship.test.ts::runShipBranch — the host key and the hosted marker (record 0060)::after a tracked, taken hand-off the parent stays live…, src/core/dispatch/ship.test.ts::runShipBranch — the host key and the hosted marker (record 0060)::a hand-off refused after the host-key claim leaves no live row…, src/core/dispatcher.test.ts::the hosted marker's only producer (record 0060)::no dispatch of any agent but the ship branch creates a run with meta.hosted…
16: after a process restart, one ledger admission rehosts the interrupted parent beside every resumed child of its instance, even past the parent's old deadline; the parent card is rewritten once from the ledger's unit, pull request, round and live-child facts, and no re-issue instruction is emitted while a child is live[unit] src/core/boot.test.ts::reclaimRuns — the hosted parent's classification (record 0060)::*, src/core/resumeLaunch.test.ts::launchResumes — the rehost branch (record 0060)::*
16: the hosted parent's hard stop stops the runner (record 0060; issue 1924) — the seal writes the stop mark on the instance row and the runner honours it: a stop between units spawns nothing more and ends the remaining units stopped, a stop during a child ends the unit stopped when the child ends, even when that child observes a foreign PR head or drains after a merge; no recovery can restart review after the stop, and a stop first seen once every unit has ended changes nothing, and a parent seals only after confirmed instance stop and, when tracked, its original writer's terminal acknowledgment; an unconfirmed stop leaves it unfinished; the plan and read-record answers carry the mark, the spawn route refuses over it before any child is dispatched, and the mark rides the store seam's markStopped on every implementation[unit] src/core/coordinator/driver.test.ts::the plan runner's driver — the hosted parent's hard stop stops the runner (record 0060, issue 1924)::*, src/core/ship/coordinator.test.ts::completed findings recovery — typed completion plus independently verified remote head::honors a parent hard stop before foreign-head findings recovery, src/core/ship/coordinator.test.ts::completed findings recovery — typed completion plus independently verified remote head::honors a parent hard stop when a superseded findings child finishes draining, src/channels/adminCoordinator.test.ts::the runner's routes read the hard stop's mark (record 0060; issue 1924)::*, src/core/runsService.test.ts::RunsService.stopRun — a hosted parent: soft refused, hard seals (record 0060)::a hard stop writes the stop mark on the instance row and the answer names the stopped runner, src/core/coordinator/instanceStore.test.ts::InMemoryCoordinatorInstanceStore::markStopped writes the stop mark on the instance row and get reads it back…
16: the plan is read whole — a unit whose heading lies past the model tool's file clip is found and seeded; a plan the read still cut at PLAN_MAX_CHARS is refused naming the bound with nothing created; the brief asks for the plan up to the same bound and refuses a cut plan by name; referenced specs and the selected rules file are read to the same bound and a cut is refused naming file and bound before building the contract, unlike a missing file; readFile honours a caller's maxChars on both implementations[unit] src/core/coordinator/handOff.test.ts::handOffToCoordinator — the plan is read whole, never through the tool clip (item 16)::a unit whose heading lies past the tool clip is found: the seed asks for the plan up to PLAN_MAX_CHARS, so a plan longer than the clip keeps every unit, src/core/coordinator/handOff.test.ts::handOffToCoordinator — the plan is read whole, never through the tool clip (item 16)::a plan longer than PLAN_MAX_CHARS is refused naming the bound, with nothing created: its later units would be lost, and a short parse is never a plan, src/core/coordinator/briefs.test.ts::contractFor — the unit's contract from the repository at the base ref::the plan is asked for whole, up to PLAN_MAX_CHARS, and a plan the read still cut is refused by name — a unit briefed from a clipped plan would be briefed short, src/core/coordinator/briefs.test.ts::contractFor — the unit's contract from the repository at the base ref::reads past the default file clip to keep a late spec item and its proof row, ::keeps rules at the end of AGENTS.md past the default file clip, ::refuses a cut spec with its path and bound instead of mistaking missing late content for a missing item, ::refuses cut AGENTS.md rules instead of silently using partial rules, ::refuses cut CLAUDE.md rules instead of silently using partial rules, src/execution/githubApi.test.ts::*::readFile: a directory answer and a non-file type are 400s that name the other tool; >1 MB (encoding none) re-reads raw; binary is a note; long text is clipped
16: a unit that writes a decision record carries its runner-reserved number — an explicit record marker wins, otherwise only a positive non-negated instruction is a record ask; main, open pull requests, durable unit/run rows and the atomic reservation ledger define unavailable numbers; concurrent admissions and bot processes get consecutive numbers, a restart cannot reuse a persisted claim, a re-issue keeps its number, and the coding brief says record: NNNN; if the durable coordinator store is unavailable, ship and direct coding admissions share one typed refusal and issue no number[unit] src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::two record-writing tasks admitted together carry consecutive reservations on their unit rows, and re-issuing the same task keeps its number; src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::an unavailable durable decision-record store refuses admission without issuing a number or writing a unit; src/core/dispatcher.test.ts::coding PR post-step (docs/reference/specs/pr-description.md)::an unavailable durable decision-record store refuses a direct coding admission with the typed store refusal; src/core/coordinator/briefs.test.ts::composeChild — the child a brief names::a contract brief is a coding child on the unit's branch whose prompt names the unit and whose contract is the unit's; a task unit's prompt is the task itself; src/core/decisionRecordReservation.test.ts::DecisionRecordAllocator::after a process restart, the durable reservation ledger advances past the first task's number; src/core/decisionRecordReservation.test.ts::DecisionRecordAllocator::an unavailable durable reservation store refuses without issuing a process-local number; src/core/decisionRecordReservation.test.ts::DecisionRecordAllocator::a throwing durable coordinator store becomes the typed unavailable reservation; src/core/decisionRecordReservation.test.ts::DecisionRecordAllocator::recognizes positive record-writing instructions, excludes negations, and gives an explicit marker priority; deploy/cloudflare-memory/runLedger.test.ts::run ledger — durable decision-record reservations (agent-ship item 16)::advances past reservations persisted on unit and run rows, and reuses a task key after a process restart; src/execution/githubPulls.test.ts::githubPulls::fetchDecisionRecordClaims::*
16, live: a production agent:ship task after the release that removed the loop runs on the runner and nothing else[agent] (human-gated.) agent:ship in <owner/repo>: <throwaway task> in Slack on the deployed bot → the card says 🧭 Handed to the plan runner plan-<generated id>, the coding child runs in the thread as a run of its own, the review child follows at the pull request's head, and the unit ends merge_ready with the report in the thread; runs get <run id> shows the parent's record with one ship_round per boundary and no model.turn of its own. Then, on a deployment without the coordinator bearer: the same request is refused naming the missing coordinator entry in SWITCHBOARD_INGRESS_TOKENS.
5, 7, 15, live: a plan of two dependent units through the runner, the bot killed once during the coding child, the review requesting changes once: the child re-attaches; the findings step's run continues the unit thread's coding session and answers the review with the coding run's own reasons; round two's review runs in the unit's thread; both children's records replay complete; the merge is the runner's under plan:merge[agent] (human-gated.) agent:ship plan <path>.md units U<a>, U<b> in Slack on the deployment; expect one ↳ *ship* unit … thread per unit in the channel and no review thread, the findings posted as the requester's agent:coding … message in the unit thread after the changes-requested verdict with the coding run answering there, the re-review in the unit thread carrying the prior findings and the matched dispositions, runs get on both children showing complete streams across the bot's restart, and the plan's summary naming both units merged.
10, live: a resume at review runs on the runner[agent] (human-gated.) agent:ship <url of an open ship pull request> in its thread → the card says the review loop resumes at its next review round, no coding child starts, the review child pins the pull request's head and the unit ends merge_ready or with the fix round the verdict asked for.
Cap run: maxRounds=1 for the test thread → the unit's cap report with the declined/unaddressed split[agent] (human-gated.) Set ship.maxRounds=1 for the test channel, run a task the reviewer will not approve on the first pass → the unit's thread gets the cap report listing declined and unaddressed findings separately and the card closes ⚠️.
17: the cut — one boundary per round index at its earliest entry; a unit with two rounds lists coding 0, review 1, coding 1, review 2 with round and thread; a review thread not yet opened lists the coding thread alone, a unit with one thread cuts that thread's runs by agent; a person's run belongs to the round in flight, a run before the first round and the pipeline's record are left out, a live run lists with its round; no thread or no rounds lists nothing; same-millisecond starts order coding before review, then by id[unit] src/core/unitRuns.test.ts::roundBoundaries — where each round of one thread begins::*, src/core/unitRuns.test.ts::unitRunsOf — a unit's runs cut at its round boundaries, in time order::*
17: the service reads the unit row and both threads, live and finished, and answers the view with the threads, the boundaries and the runs in round order; a review thread not yet opened and a unit not started; an unknown unit, a malformed key and a process without the coordinator's records are not_found[unit] src/core/runsService.test.ts::RunsService.listUnitRuns — a unit's runs in round order::*
17: runs unit <key> on every surface — the listing, its terminal and chat renderings, unit not found, a malformed key naming unit, chat a surface for it[unit] src/core/commands/runs.test.ts::runs unit / runs children / runs search — the unit is the reading unit::runs unit answers the unit's runs in round order…, src/core/commands/runs.test.ts::runs unit / runs children / runs search — the unit is the reading unit::an unknown unit is not_found…, src/core/commandConformance.test.ts::*
17: a unit's key is <instance>:<unit>, the head of every child's idempotency key; what is not one is refused[unit] src/core/coordinator/contract.test.ts::a unit's key — the instance and the unit, the prefix of every child's idempotency key::*
17: the view carries the row's readable facts and its instance's — title, branch, pull request, issue, where the threads open, the ending; the repository, plan, attempt and parent record — and never the requester's ids; a unit not started carries its facts with no runs[unit] src/core/runsService.test.ts::RunsService.listUnitRuns — a unit's runs in round order::the view carries the row's readable facts…, src/core/runsService.test.ts::RunsService.listUnitRuns — a unit's runs in round order::a unit not started lists nothing…
17: listInstanceUnits answers an instance's unit rows as facts in the plan's order for a reader its requester's channel admits and nothing for another channel's reader, an unknown instance, none, or a process without the records[unit] src/core/runsService.test.ts::RunsService.listUnitRuns — a unit's runs in round order::listInstanceUnits answers the instance's unit rows…
17: the parent's record names its instance in its last run_meta.instanceId and is nobody's child; finish publishes the answer, finishes the registry row and seals the one record — the run's own events in seq order — through the ledger under the metadata's thread, releasing the host key for the next agent:ship[unit] src/channels/adminCoordinator.test.ts::*::*finish writes the parent's record*
17: the routes write to the hosted parent through hostPublish — increasing seq, the write-through mirrors the events, hosting.until moves forward with every write; a run live under another generation answers 409 not_host on every route and nothing is written; an untracked pipeline publishes nothing, finish writes no record and the routes answer as before; a body naming a run id other than the instance's publishes nothing and answers not_found[unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::hostPublish through the routes…, src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::a run live under another generation answers 409 not_host…, src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::an untracked pipeline (no ledger row anywhere) publishes nothing…, src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::a route body naming a run id other than the instance's…
17: ship_unit is a run event — a record with one and two run_meta events passes isRunRecord, and the friction analyzer ignores it as activity[unit] src/core/runEvents.test.ts::the hosted parent's record shape (record 0060)::*, src/core/runFriction.test.ts::analyzeRunFriction — ship_round is a side fact, invisible to friction::the diagnosis is identical with or without ship_unit facts…
17: the unit page — the route's seed and 404, the page's layout, folds, edges and the parent record's unit list[unit] the item 28 rows of live-view.md: src/channels/liveView.test.ts::the unit page and what a run is the parent of (item 28)::*, web/src/pages/unitPage.test.ts::UnitPage — the unit is the reading unit (item 28)::*, web/src/pages/runPage.test.ts::RunPage — history mode::a conductor's page lists the runs it spawned…
17, live: a real unit read from one route in round order[agent] (human-gated.) After a ship on the deployed bot, runs unit <instance>:<unit> from the CLI (the instance id is the card's ship-<run id> or plan-<plan>-<attempt>; the unit task or U<n>), or GET /api/runs.unit?unit=… — expect coding 0, then each review round and findings step as the card drew them, every run's id opening its page.
18: the join — a finding the newest review raised with no disposition after it is open, with the review's words and where it was raised and last seen; a disposition with no review after it is awaiting re-review; fixed and conceded when the newest later review did not list the id, a review listing no findings (or carrying no array) settling both; re-raised when it did, naming the kind answered and taking the later review's words under the same id; the latest disposition decides and a second run's set replaces the first[unit] src/core/findingsLedger.test.ts::ledgerOf — one row per finding id, its status a function of the recorded sequence::a finding the newest review raised with no disposition after it is open…, ::a disposition recorded with no review after it is awaiting re-review…, ::fixed: disposition fixed and the newest later review did not re-raise the id…, ::re-raised: a later review lists the id again after a disposition…, ::the latest disposition decides…
18: the edges — a finding absent from the newest review with no disposition is not re-raised, never open or fixed; an id a disposition names that no review issued is its own unknown id row with the disposition alone; a disposition before the first raise answers nothing; a review listing no findings yields no rows and a run without a verdict or dispositions, or still live, contributes nothing; the runs are ordered by finishedAt whatever order they arrive in, rounds ride onto raised, last seen and the disposition, rows keep first-raised order with unknown ids last, and a review's head is the reviewed head, else the verdict's[unit] src/core/findingsLedger.test.ts::ledgerOf — one row per finding id, its status a function of the recorded sequence::a finding raised at an older head that the newest review did not list…, ::an id a disposition names that no review issued is a row of its own…, ::a disposition that predates the review that first raised the id answers nothing…, ::a review that lists no findings and no dispositions yields no rows…, ::orders the runs by finishedAt itself…, ::rows keep the order ids were first raised in across rounds…
18: the reference — owner/repo#N and the pull request URL both name the repository and the number; a bare number, a repository alone, a zero and an issue URL are refused, the pattern agreeing[unit] src/core/findingsLedger.test.ts::parsePullRequestRef — \owner/repo#N` or the pull request's GitHub URL::*`
18: the read — the records naming the pull request joined with the unit's runs, each with its round, a review whose post was skipped reached through its thread, the unit key and the url named, the runs oldest first; without the coordinator's records the ledger is the records alone with no unit and no rounds; only runs the reader may see enter the join (a private review left out changes the rows); not_found for a pull request no run names, a reader outside every run naming it, none, while another repository's same number is its own ledger; the pull request rides down to the store as its own filter beside the predicate[unit] src/core/runsService.test.ts::RunsService.listFindings — a pull request's findings ledger from the records (agent-ship item 18)::*
18: runs findings on every surface — both spellings of the reference, titles and notes wrapped on the JSON surface and unwrapped in the terminal and chat renderings (a header with the tally, one line per finding, no padded columns in chat, a re-raise naming the kind it answered), no runs name this pull request, a malformed reference naming pr and never echoing it, a reader outside the private review reading the public runs' ledger, chat a surface for it; findings needs run history and claim observations need the durable ledger[unit] src/core/commands/runs.test.ts::runs findings — a pull request's findings ledger on every surface (agent-ship item 18)::*, src/core/commands/runs.test.ts::runs.* registrations::registers run commands with the declared actions and chat opt-outs, src/core/commandConformance.test.ts::*, src/core/commandCapabilities.test.ts::enabledWhen on the core catalogue — which capability each command needs::runs claims needs a ledger, findings needs history, and live reads stay on
18: the page — the unit page's Findings block and its seed, the run page's Findings link and its seed[unit] the item 28 Findings rows of live-view.md: web/src/pages/unitPage.test.ts::UnitPage — the Findings block: the pull request's ledger beside the runs (agent-ship item 18)::*, web/src/pages/runPage.test.ts::RunPage — history mode: the Findings link (agent-ship item 18)::*, src/channels/liveView.test.ts::the unit page and what a run is the parent of (item 28)::the unit page's seed carries the pull request's findings ledger…
18, live: a shipped pull request's ledger read from one route[agent] (human-gated.) After a ship with at least one findings step on the deployed bot, runs findings <owner/repo#N> from the CLI or GET /api/runs.findings?pr=… — expect one row per finding id the reviews issued, each disposition the coding run recorded and, after the re-review, fixed/conceded/re-raised matching the posted reviews; every run id opening its page.
19: the fold binds rounds by adjacency, takes no stage from a companion, is total over both unions (pinned at compile time and by the runtime tables), keeps idle and continued units open and reopens them at the next started, lists an older record's rounds as unnamed, enters a unit adopted at review with no round 0, and moves since only on a stage change[unit] src/core/pipelineStanding.test.ts::the fold binds rounds by adjacency, ignores a companion's state and is total over the unions::*
19: the round route accepts every ShipRoundOutcome — a continued and an idle boundary answer 200 with at and land on the unit's row[unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::the round route accepts continued and idle
19: runs get prints the standing under the meta block and carries it as JSON; a record without the field prints no block[unit] src/core/commands/runs.test.ts::runs.get / runs.events / runs.friction::runs.get prints the pipeline's standing under the meta block and carries it as JSON
19: the fold's final stages agree with the card's ending words over the sampled records[agent] (human-gated.) npx tsx scripts/pipeline-standing-diff.ts <records.json> over the finished hosted records the MCP lists plus their unit rows — expect the disagreement count posted on record 0065's receipts issue before the words reach a reader.
20: unavailable ownership evidence becomes the target's existing per-PR error line before any mutation; the report retains other PR results and redacts the failure through the shared error boundary[unit] src/core/pullSweep.test.ts::the sweep — one line per pull request, in user words::an unavailable owner lookup is a per-PR error and leaves that target untouched while other results finish, ::a git failure that quotes a credential surfaces redacted…
20: the resolver's decision table — clean and unchanged carries the approval with no re-review, clean but changed gets a delta re-review, and stale-but-clean or unknown is never rebased; for an unowned command sweep, one conflict buys the one model round under the lease and spend bounds and a later conflict ends in one line with the hand-rebase remedy; for a runner-owned pull request, every conflict returns to that runner's coding rebase round, each round carved from the run's remaining lease, while changed clean patches return to review — no model-round flag is spent once for the pull request's lifetime and no runner-owned path says “rebase it by hand”[unit] src/core/pullSweep.test.ts::decideSweep — the resolver's decision table (record 0071, mechanism two)::*; src/core/pullSweep.test.ts::the sweep — one line per pull request, in user words::*; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/merge — the runner's squash of a unit's pull request (item 9)::the runner's rebase route maps the shared resolver's clean carry, changed patch and conflict outcomes; src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::two successive base moves on an approved pull request are rebased by the runner and end merged without a person's command
20: rung one is git alone — rerere replays a resolution made once on a later rebase of the same hunks with no model round, an unchanged patch reads all = from git range-diff while a base edit inside the hunk's context reads changed (and a changed patch whose every line contains = still reads changed), a conflict aborts and names the file leaving the checkout as it was, a non-conflict failure — a dirty checkout, a --continue making no progress — throws instead of reporting a conflict, and the lease push is refused when the remote moved under the sweep[unit] src/execution/gitRebase.test.ts::gitRebase — rung one is git alone::*
20: one shared repository throughput queue serializes separate command, authenticated watch and runner services; different repositories remain concurrent. Queue release never grants write authority or releases an uncertain per-PR effect.[unit] src/core/pullSweep.test.ts::the per-repository bound — one rebase in flight::separate %s services serialize different PRs through one shared repository queue; ::separate services can rebase different repositories concurrently; ::a failed sweep releases shared throughput without granting the next service any write authority; ::two sweeps of the same repository never overlap: the second queues behind the first; src/core/coordinator/maintenanceSweep.test.ts::durable maintenance sweep integration::separate maintenance services share throughput and preserve each durable PR owner; ::shared throughput never releases a PR whose first native write remains unknown
20: the command — pulls rebase binds one named pull request (number, #N, owner/name#N, URL) or the repository's sweep, resolves the repository from the reference, the option or the thread, and answers the service's lines on every surface; without a wired service it answers unavailable; its conformance rows (every surface, the pulls:write admission, the blast radius, the routing fixtures) are the registry-driven suite's[unit] src/core/commands/pulls.test.ts::pulls rebase — the sweep command::*; src/core/commandConformance.test.ts::command conformance — pulls.rebase::*; src/core/authz/policy.test.ts::POLICY coverage::pulls:write command [has-grant(pulls:write)]::*
20: the production wiring resolves every dep (issue 2067) — the listing keeps the plan branches on the base repository with each pull request's facts read fresh and the approval read at the head — a genuine APPROVED state or the bot's own identity-checked LGTM: review, an unknown identity counting only APPROVED; the carry posts the bot's LGTM: review pinned to the new head; the delta re-review and the one bounded model round go through dispatch() as the requester on the sweep's own thread (a refused dispatch answers started: false by reason, a started round is spent once across requesters); the anchors rewrite this repository's blob permalinks to the pushed head and touch nothing else; and the wired command answers its lines on chat, the CLI and MCP, never unavailable[unit] src/core/pullSweepWiring.test.ts::buildPullSweepDeps — the listing::*; src/core/pullSweepWiring.test.ts::buildPullSweepDeps — the effects::*; src/core/pullSweepWiring.test.ts::the wired command — no \unavailable` on any surface::; src/execution/githubPulls.test.ts::githubPulls::listOpenPullRequests and fetchPullRequestTitleBody (the sweep's listing, item 20)::`
20: rung one end to end against real git — a DIRTY pull request from a fixture walks the whole walk through the sweep service in a temp directory (clone, rebase, range-diff, lease push, the anchors and the carry, the checkout removed); a conflict comes back named with origin untouched and the model round left to the service; a failed clone throws git's own words; the credential rides the clone's config as a header, never the URL, and a failing clone's user-facing line carries no token[unit] src/execution/sweepCheckout.test.ts::createSweepGit — rung one in a throwaway clone, real git::*; src/core/pullSweep.test.ts::the sweep — one line per pull request, in user words::a git failure that quotes a credential surfaces redacted…
21: the watch's setting resolves repo over org over off; a DIRTY push-to-base wakes exactly one resolver round under the per-repository cap while a second DIRTY pull request queues; the spend cap stands the DIRTY and the card names pulls rebase; a merged fact, whoever merged, hands the merge to the waiting unit and drops the book's entry; a stale-but-clean or still-recomputing pull request is never rebased[unit] src/core/mergeWatch.test.ts::the watch setting's scopes (record 0071: off by default, org on, repo override over either)::*; src/core/mergeWatch.test.ts::a DIRTY push-to-base wakes the resolver under the caps (record 0071, mechanism three)::*; src/core/mergeWatch.test.ts::the spend cap: the DIRTY stands and the card names the sweep (record 0071)::*; src/core/mergeWatch.test.ts::a merged fact, whoever merged, ends the unit \merged` by other (record 0071)::*`
21: the push intake verifies the webhook's signature, reads only a branch push, hands exactly the repo and branch to the watch and answers every outcome; without a wired watch the push is acknowledged[unit] src/core/coordinator/checksIntake.test.ts::the push-to-base intake — the merge watch's trigger (record 0071, mechanism three)::*
21: a live runner is the sole rebase owner for its approved pull request, independent of the watch setting — the merge door's typed conflict re-enters that runner, and a command sweep on the owned pull request defers without touching git or spending its fix path; only a pull request no live pipeline owns can reach the sweep's hand-rebase remedy[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/merge — the runner's squash of a unit's pull request (item 9)::a conflicting pull request returns the runner's typed rebase outcome before checks are read — zero checks stays pending only on a mergeable pull request; src/core/pullSweep.test.ts::the sweep — one line per pull request, in user words::a pull request owned by a live pipeline runner defers to that runner instead of spending the sweep's fix path
21: with the watch on the waiting unit owns its thread — an addressed reply is the unit's turn, never the router's; off, merge_ready stays an ended kind[unit] src/core/dispatch/thread.test.ts::ownerOf and instanceOf — the thread's owner (record 0051's owner rule)::a watching unit owns its thread (record 0071, mechanism three): a merge-ready wait leaves the row without an ending, so an addressed reply is the unit's turn and never the router's
20, live: record 0071's cascade proof against real DIRTY pull requests[agent] (human-gated.) On the deployed bot: (1) let a live ship runner approve its pull request, then move the base so the head conflicts; (2) expect the same runner to open a coding rebase round, and expect pulls rebase <owner/name>#<n> to answer that the pipeline runner owns the rebase without touching the branch; (3) after its re-review approves, move the base into a second conflict; (4) expect a second coding rebase round under the same runner and, after approval, a merge with no person's command; (5) separately pick a DIRTY pull request no live runner owns and run pulls rebase; expect clean unchanged patches to carry approval, changed patches to request review, a conflict to buy the sweep's one bounded fix round, and only a later unowned conflict to name the hand-rebase remedy.
10: a matching seeded-sandbox checkout retains existing-PR publication authority when the resident is drained, while a foreign or stale seed blocks it. An authorized exact-ref leased push with a bounded Git config option through the configured Git door and Git's remote-tracking output records a typed full-head receipt at its successful tool result, before post-step salvage, and retains it across restart only after the result and receipt commit atomically. A rejected, unavailable or skipped commit leaves no receipt in restart state or the push projection, including when a tracked run loses its ledger connection mid-run; a local-only projection or later ordinary state writes cannot grant that authority. Both harness gates refuse subsequent tools from push authorization until attribution settles, so an asynchronous source-head read cannot race a later ref mutation; the push’s own secondary permission requests remain allowed. A completed findings child missing pushed can reconcile a configured Git-door git -c … push only from a complete unique successful bash call/result pair, exact old lease and source/destination, matching configured door origin, full child head, matching dispatch tag, persisted priced record and fresh exclusive PR/ref ownership; missing, truncated, ambiguous, competing, stale, foreign or unpriced evidence refuses. The original instance, unit, ref, PR, caps and lease remain unchanged; historical budget reconstruction is separate[unit] src/core/publicationPush.test.ts::leased publication push evidence::*; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::missing push receipt*; src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::a gated findings push records its receipt only after an atomic commit*; src/core/dispatch/runLoop.test.ts::a resume with the answer in hand…::a successful gated push receipt survives restart*; src/core/harness/pi/relay.test.ts::authorizeToolCall — the gate::records exact leased push authorization only after the gate allows the owned destination; src/core/harness/opencode/bridge.test.ts::the gate's honest cannot, the compaction row, the budget stop, the unknown kind::records the exact leased push authorization only for an allowed owned shell ask; src/core/harness/pi/relay.test.ts::authorizeToolCall — the gate::refuses subsequent tools until push attribution settles, then allows a retry; src/core/harness/opencode/bridge.test.ts::the gate's honest cannot, the compaction row, the budget stop, the unknown kind::refuses subsequent tools during push attribution without denying the push's secondary permission

Recovery continuation contract ​

An original coding recovery spends only its admitted remaining lease. At the completed round-zero renewal checkpoint, with no PR and no earlier terminal/held result, it settles aborted with typed recoveryStop: continuation_not_admitted; it cannot emit continued or idle because an original grant has renewals remaining. Preserve prior already-landed and typed-hold decisions, grant identity, cumulative spend and renewal count. Current coding admission requires complete accounting. Legacy review recovery without accounting reports the original allowance as unverified, without inventing a count, denominator or grant source. The typed refusal bypasses automatic idle mapping even with a positive idle policy. Existing human-gate parking is distinct and still settles through the driver's held projection; this change does not claim that older parking metadata supplies complete historical accounting. Existing interrupted/failed/transient child endings and bounded retries within the same admitted lease remain distinct. The executed action consumes its original source as before; this refusal grants no new admission. The typed cause survives the strict Ship outcome projection, comparison and persistence decoder.

CriterionValidation
Coding recovery with pushed progress and an unspent original allowance ends aborted without opening another segment; accounting stays unchanged and the report names the carried count.[unit] src/core/ship/coordinator.test.ts::original-unit recovery accounting::coding recovery cannot renew its admitted checkpoint despite pushed progress
The recovery-continuation refusal is typed, strictly bound to aborted, survives serialization and remains part of immutable outcome comparison.[unit] src/core/coordinator/shipOutcome.test.ts::typed Ship outcome::retains and strictly binds a recovery continuation refusal

The production driver against the bot contract is proven by [unit] src/core/coordinator/driver.test.ts::runOriginalUnitRecovery::settles a completed no-PR coding recovery without asking for another segment: the bot contract rejects continued/idle recovery settlement, the driver sends one aborted outcome with the exact Workflow claim and typed refusal, and starts no further child. The real admin route binds that cause to the exact coding claim and permits only its immutable completed-receipt replay: [unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — pre-PR same-unit coding::settles and replays the coding recovery refusal only under its exact claim; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — pre-PR same-unit coding::refuses a recovery-only outcome on an ordinary unit. Invalid producer facts fail closed rather than dropping this cause: [unit] src/core/coordinator/shipOutcome.test.ts::typed Ship outcome::fails closed when a recovery refusal contradicts its terminal facts.

Legacy review reporting: [unit] src/core/coordinator/driver.test.ts::runOriginalUnitRecovery::reports legacy review recovery accounting as unverified without inventing an original grant. A blocked coding deviation stays held without acquiring a human gate: [unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — pre-PR same-unit coding::persists a recovered coding deviation as a blocked hold without inventing a human gate.

Report accounting reads the carried ordinary segment count as well: segment two reports one spent renewal, rather than resetting the displayed count to zero. Proof: [unit] src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::a segment that ends*opens the next*.

Unit lifetime settlement ​

Ordinary sequencing and exact original-unit recovery enter the same full unit lifetime. A stop observed before execution settles without starting a child. Recovery terminal settlement, including its failure path, carries the current exact Workflow claim and a claim-specific step identity. Child action namespaces remain the existing recovery namespace in this slice. A recovered human-gated review returns its already-settled terminal hold rather than entering the ordinary idle wait. The seeded dependency sequencer retains its existing order and blocked-unit behavior.

Every unit ending, including an unrun stopped/blocked unit, an indexed idle expiry or stop, and a best-effort thrown-step report, requires HTTP200 with typed ok:true inside its durable step. A refusal is never cached as successful settlement. HTTP4xx without a recognized transient code, or HTTP200/ok:false settlement refusals, become Cloudflare NonRetryableError inside the durable callback. Recognized transient codes, transport failures and HTTP5xx retain bounded retries; exhaustion fails execution, and a failed best-effort report preserves the original exception. Settlement storage unavailability returns HTTP503; stale ownership, conflicting history and exhausted history capacity return HTTP409 without clearing the claim or releasing its owner. A required private-log refusal can precede or follow commitment: it never proves success and keeps the original delivery identity unconfirmed. The bot refuses changes to any committed terminal report even when a retry uses another delivery id or loses the error class. Attributable terminal execution is reconciled through the existing outbox and exact original report receipts (orchestration-plane.md). An unconfirmed native mutation remains retained; this reconciliation never guesses acceptance or replays it.

CriterionValidation
A generated request drives exactly one independent unit through the shared full lifetime, including stop and indexed idle, without a graph-selection reread. An exact-PR unit follows that same entry; malformed generated dependency selections refuse before work. Seeded plans retain boundary rereads for appended units.[unit] src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::drives a generated *; src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::keeps the complete indexed idle lifetime on direct generated execution; src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::refuses generated *; src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::a selection that grows mid-walk*
Recovery settlement distinguishes retryable store unavailability from durable conflicts and capacity refusals, preserving the original claim and owner.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::preserves recovery ownership when settlement returns *
An unavailable reread cannot establish a permanent settlement conflict. Legacy recovery receipt replay keeps unavailable storage retryable and rejects a definite stale CAS.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::preserves recovery ownership when settlement returns unreadable; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::keeps legacy recovery replay retryable only when storage is *
An owner-bound recorded negative terminal ending settles the seeded cursor and blocks its dependents; a changed requester or admission cannot be adopted. Reconciliation does not rewrite the recorded ending, accept the rejected proposal or claim successful private delivery. Positive endings and unresolved private reports require confirmed report reconciliation.[unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::reconciles a concurrent terminal ending only under the *
Stopped or blocked traversal preserves the recorded terminal result, including a previously recorded step failure. A freshly thrown step still fails its execution.[unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::keeps recorded terminal endings while settling a *; src/core/coordinator/driver.test.ts::the plan runner's driver — a step that throws inside the walk becomes the unit's ending (issue 2100)::a step whose retries*
A lost settlement response requires exact terminal-row equality, including legacy ending kind. A report replay requires current owner validation even when its status receipt already exists; refusal retains publication ownership.[unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::rejects a different legacy terminal result after a lost settlement response; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::refuses a committed recovery report when its owner becomes *
Status reads reject a mixed owner snapshot before first or cached acknowledgement. A cached private report still requires its current Main-task claim, and refusal appends no further private reply.[unit] src/core/coordinator/unitStatus.test.ts::committed coordinator status context::refuses a mixed owner snapshot before the *; src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::refuses private report replay when its Main claim is *
A recovered unit observes a pre-start stop, starts nothing and settles under its exact Workflow identity; refusal also keeps that identity on the failure path.[unit] src/core/coordinator/driver.test.ts::runOriginalUnitRecovery::a pre-start stop*; src/core/coordinator/driver.test.ts::runOriginalUnitRecovery::a refused stopped recovery*
A terminal recovery hold does not park or dispatch again; the driver summary agrees with the real admin route's persisted held row and consumed recovery receipt.[unit] src/core/coordinator/driver.test.ts::runOriginalUnitRecovery::keeps spent original renewals out of a recovered human-gate balance; src/core/coordinator/driver.test.ts::runOriginalUnitRecovery::keeps an uncapped recovered checkpoint at zero renewal capacity; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::salvage recovery keeps*behind the findings and exact-head review gates
Unrun settlement and its best-effort failure report reject unsuccessful acknowledgements; deterministic refusals stop retries through the production step adapter and private-log retry keeps the original delivery unconfirmed.[unit] src/core/coordinator/driver.test.ts::the plan runner's driver — the hosted parent's hard stop stops the runner (record 0060, issue 1924)::an unrun ending*; src/core/coordinator/driver.test.ts::the plan runner's driver — the hosted parent's hard stop stops the runner (record 0060, issue 1924)::an unrun private report*
A committed unprojected terminal report cannot be replaced by a new delivery id.[unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::keeps an unprojected terminal report immutable across a different delivery id
An early recovery cap starts no child and keeps an ambiguous private-report write unconfirmed.[unit] src/core/coordinator/driver.test.ts::runOriginalUnitRecovery::an early recovered deadline ending preserves an ambiguous private report failure
Indexed idle expiry cannot complete on a refused terminal write.[unit] src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::an indexed idle expiry*
16: retrying a PR-bound ended generated task retains its original unit and publication; a hand-off cannot transfer the PR to a replacement attempt and does not create another Workflow. Original-unit recovery supplies the execution path.[unit] src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::refuses to discard a PR first recorded in a later attempt, ::retains a recovered legacy unit's saved push and record under its original owner, ::refuses to transfer an originally adopted PR into a replacement attempt

Original continuation admission ​

CriterionProof
A bare original-unit recover or renew request preserves its operation and named key under either operator setting; implicit continuation carries the same adapter event to the original recovery journal without a replacement writer.[unit] src/core/dispatcher.test.ts::a unit-owned thread (record 0051's reply-as-event and gone-instance rules)::plain continuation delegates to exact original-unit recovery without replacing its owner; src/core/dispatcher.test.ts::a unit-owned thread (record 0051's reply-as-event and gone-instance rules)::bare * preserves its named original unit and intent (*)
A replay must retain its recover/renew intent after terminal settlement. Digest-bound admission fields use the unit's shared decoder and agree with the journal's identity, source run, Workflow and head; malformed retained payloads refuse completion.[unit] src/core/coordinator/recoveryHistory.test.ts::recovery history store::replay reads recover or renew intent only from complete digest-bound admission fields; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::post-approval recovery runs full review, typed fix and exact-head re-review through the real driver with * CI
Failed or unanswered initial Workflow admission retains the exact unconfirmed owner; an answer naming another Workflow cannot establish that this one started.[unit] src/core/coordinator/handOff.test.ts::handOffToCoordinator — the ship request as a plan runner instance (item 16)::a refused initial Workflow create retains its exact unconfirmed admission; ::initial Workflow admission keeps its identity and owner when the reply is lost; ::an answer naming another Workflow retains the pending owner instead of claiming this one started

A complete final pre-model attach refusal carries zero model spend even when its summary retains a resolved input PR/head. The first retained run metadata must match that input target; model/tool activity, incomplete events or a foreign target remain unknown. Prior child spend, cumulative allowance, stage authorization and original owner remain unchanged. Proof: [unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::prices the complete admitted…; ::refuses admitted coding setup evidence…; ::prices a complete…; ::refuses zero-spend setup evidence…; ::prices an admitted review input target without crediting work or resetting earlier spend; ::refuses new attach work…; ::prices ordered review refusal cleanup followed by closure of its original outer scopes at zero. This price receipt does not credit a push or authorize revival of a completed PR.

CriterionProof
Draft adoption and verified rebase publication use the canonical atomic owner CAS. A thrown or unavailable acknowledgement confirms only the exact proposed whole row; local claim loss cannot undo a committed durable binding.[unit] src/channels/adminCoordinator.test.ts::original committed head adoption — create-only draft PR::confirms the exact adoption binding…; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/merge — the runner's squash of a unit's pull request (item 9)::confirms the exact rebase binding…
An explicit agent:ship adopt unit <instanceId>:<unit> audit may create a new original-owner audit for a legacy ended coding unit whose complete canonical native history proves its first attachment, accepted original spawn and unique successful typed publication chain. Ordinary adoption still requires its prior accepted settlement. The audit checks only the immutable first-head…final-head range using fresh requester/App identities, exact pairs and coauthor rules; its first head is excluded from the range and the PR base stays unchanged. Both owner stores recompute the bounded full-event digest and recheck the exact canonical snapshot before an audited whole-unit CAS. Standing attribution synchronously matches the immutable typed-authority projection; same-count changed authoritative history refuses. The audit retains the original accepted spawn after later effects advance. Forged, stale, foreign, missing or conflicting evidence, a moved head, bad identities, rival ownership and corrupt workspace custody refuse. The audit is immutable through the existing claimed→posting→bound action, preserving its one-create/lost-response rule. Old producer settlement, histories, budgets, exact workspace generation/fence/revision and private bytes are never rewritten or acknowledged by adoption.[unit] src/channels/adminCoordinator.test.ts::original committed head adoption — create-only draft PR::audits only the original native range into one new action without rewriting old custody; ::keeps an audited lost create response under the same original action without reposting; ::refuses moved native head and failing pinned identities before an audit claim or create; ::owner CAS refuses a forged or stale historical audit…; ::keeps a committed historical audit immutable through posting; ::does not let ordinary unit writes manufacture an adoption audit; ::retains audited original ownership after the unit advances to review; ::refuses standing ownership when authoritative history changes at the same count and heads; ::rechecks canonical bytes after the asynchronous audit digest before owner CAS; ::freezes a read-only audit count with its snapshot while digest preparation awaits; src/execution/identityRewrite.test.ts::read-only pinned native range identity audit::*; deploy/cloudflare-memory/runLedger.test.ts::historical original native adoption owner CAS::validates canonical SQLite history before claiming, keeps the audit immutable and refuses stale or forged proof; src/core/dispatch/ship.test.ts::runShipBranch — the agent:ship fork hands every admitted request to the plan runner::routes explicit original-head adoption with the real requester and no new plan
CriterionProof
Check retries freeze authenticated native IDs, record pending before each call, retain unknown results without replay, and restore a positively accepted close under its original effect even after stop. A refused restoration remains owned.[unit] src/core/coordinator/checkRecoveryEffect.test.ts::durable check recovery::*; src/execution/githubPulls.test.ts::githubPulls::frozen check retry and PR state writes::*

| Native retry planning distinguishes a complete exact-head read with named checks already pending or green from unavailable/missing retry evidence. The former consumes no mutation; incomplete identity and missing adapters grant no admission. Pending effect reconciliation reasks without finalizing the Workflow; a lost durable completion ACK can retry only the known original result journal, never the native call. | [unit] src/execution/githubPulls.test.ts::githubPulls::frozen check retry and PR state writes::distinguishes named checks already pending or green from unavailable retry evidence; src/core/coordinator/checkRecoveryEffect.test.ts::durable check recovery::*; src/core/coordinator/driver.test.ts::the plan runner's driver — the entry checks resume a re-issued plan's unit (agent-ship item 10, issue 1689)::an empty required-check launch reasks pending effect reconciliation before recording the refire |

| Recovery PR creation consumes a confirmed native effect ordinal before the next child. Terminal recovery credits only the original exact native ref receipt; malformed producer metadata cannot authorize publication. | [unit] src/core/coordinator/driver.test.ts::Workflow effect receipt ordinals::consumes a recovered pull creation ordinal before the next review child; src/channels/adminCoordinator.test.ts::*::recovers a dead child's exact original native ref receipt even without a salvage checkpoint; src/channels/adminCoordinator.test.ts::*::refuses malformed original native receipt metadata before dead-child publication |

CriterionProof
Recovery identity rewriting fixes each commit's original author date as its committer date, so a crash before begin reconstructs the same frozen native ref proposal rather than a rival SHA.[unit] src/execution/identityRewrite.test.ts::rewriteRunCommits — the run's commits carry only the allowed identities (record 0062)::reproduces a frozen ref proposal after a crash before begin by preserving deterministic commit dates
A definitive native recovery refusal settles its original bounded cell and consumes the same ordinal on original/replay requests, without another POST or publication credit. An unstarted create is cancelled after definite ref refusal; uncertain writes remain retained.[unit] src/core/coordinator/recoverPublicationEffect.test.ts::dead child publication fence::settles a definite create refusal and replays its consumed ordinal without another POST; ::cancels the unstarted create after a definite ref refusal and settles without publication; src/channels/adminCoordinator.test.ts::dead coding recovery durable create::returns a consumable ordinal for a definite recovery refusal on both original and replay requests
A saved human question may carry only the original owner's explicit primary-action label and reply text; nonblank values are bounded to 64 and 4096 characters, legacy questions remain valid, and no action is inferred from verdict or prose[unit] src/core/coordinator/humanGate.test.ts::original human question assignment::accepts explicit bounded primary actions and keeps legacy questions valid without inferring an action
Changing the recorded primary action changes the complete question guard; an old observed action is stale, while the current complete question accepts the original owner's reply[unit] src/core/coordinator/questionAnswer.test.ts::Original question answer admission::rejects an old primary action after the original owner changes it and accepts only the complete current question

| An exact pending recovery action observed after its old unit snapshot rereads that canonical unit before replay. Same-request concurrency retains one claim/start and its unadmitted restore; a conflicting claim, changed bound head or unavailable reread refuses without another admission | [unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::post-approval recovery admits one concurrent claim and restores an unadmitted claim without consuming the review, ::post-approval recovery refuses a delayed action read after * without another admission |