Skip to content

feat(harness): publish exact trees through typed push - #2203

Closed
coreplane-switchboard[bot] wants to merge 3 commits into
mainfrom
feat/typed-push-effect
Closed

coreplane-switchboard[bot] wants to merge 3 commits into
mainfrom
feat/typed-push-effect

Conversation

@coreplane-switchboard

Copy link
Copy Markdown
Contributor

Adds a runner-owned typed push that rebases, gates and lease-publishes one exact clean tree. Ship readiness now trusts only the runner receipt, closing shell-shape and stale-gate ambiguity.

Why: U1 implements accepted record 0074. It establishes the port and receipt ledger that U2–U4 build on.

Where to look

  1. Effect lifecycle and typed outcomes Persists the envelope, reuses same-id outcomes, fences exact facts and records a closed success or refusal. ⚠ A stale or shadow result must never authorize a production write.
  2. Production git resolution and rebase Binds the command to the admitted repository, branch and base before runner-authored git operations. ⚠ Incorrect resolution could publish to the wrong ref or repository.
  3. Run-bound effect wiring Builds shadow or production effects from current run facts and the existing repository authorization decision. ⚠ This is the boundary between child intent and runner authority.
  4. One shell refusal in effects-on Routes every recognized child push to one structural refusal while shadow retains the temporary legacy judge.
  5. Receipt-only ship readiness Requires a matching runner publication receipt; remote observations, legacy pushes and salvage do not qualify. ⚠ A false negative pauses review; a false positive reviews unproven work.
  6. Earlier-tree and cutover regressions Proves mutations invalidate gate evidence, failed gates do not publish and shadow observations do not survive cutover as receipts.
  7. Harness contract Specifies the typed tool, shadow/on rollout, one shell rule and directive-run branch ownership in one living row.

Feedback wanted: Please focus on exact-tree fencing around gate execution and whether the shadow-to-on receipt migration is sufficiently fail-closed.

Risk: 1,368 changed lines across the port, adapter, rollout and readiness. A split was considered, but would leave two authoritative publishers; rollback is harness.effects=shadow or reverting this PR.

Verified: 724 focused tests, scoped tsc, Prettier, hygiene, specs and docs checks passed; user-message, vocabulary and title gates passed. The deployed live receipt remains human-gated.

Decisions (3)
  • Push is one composite effect. Separating rebase, gates and publication would reopen a mutation window between proof and write. The command therefore owns the full lifecycle and records gate receipts against the final tree and clean state.
  • Shadow success is not a production receipt. A run may resume after the deployment changes from shadow to on. Production deliberately ignores a prior shadow success with the same effectId and performs the fenced write instead of treating observation as publication.
  • Salvage remains preservation only. A mechanical checkpoint proves the runner retained work, not that the child requested publication. Ship readiness and renewal progress therefore consume only by: runner.
Validation (8 criteria)
Criterion Proof
Effect-port lifecycle, exact-tree fencing, retry and refusal npx vitest run on 14 touched suites passed: 14 files, 724 tests, including runEffects and runEffectsGit.
Effects-on refuses the shell-shape table by one rule src/core/harness/pi/toolRules.test.ts passed, including aliases, config, substitutions, compounds, force, bulk, tags and mirror cases.
Ship readiness requires the matching runner receipt src/core/ship/coordinator.test.ts passed, including empty, salvage-only and matching-runner receipt variants.
Changed TypeScript remains sound NODE_OPTIONS=--max-old-space-size=6144 npx tsc --noEmit -p tsconfig.json passed.
Changed files are formatted npx prettier --check over git diff --name-only origin/main...HEAD passed.
Living specs and generated docs resolve npm run specs:check and npm run docs:check passed.
Public-tree and user-surface consistency npm run hygiene:check, vocabulary:check and user-message:check passed.
PR title follows the changelog contract npm run check:pr-title -- "feat(harness): publish exact trees through typed push" passed.
For agents

Branch was created from and rebased onto origin/main at 9f6516d before push. Review the new effect lifecycle first, then runLoop wiring and coordinator readiness. No generated files were hand-edited. Focused repro: npx vitest run src/core/runEffects.test.ts src/core/runEffectsGit.test.ts src/core/harness/pi/toolRules.test.ts src/core/ship/coordinator.test.ts.
Requested by @justinhelmer in slack:C0BRRHKFLCB

🤖 Generated with Claude Code

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: The typed push path can abort valid shadow-mode ship runs and does not safely recover or restore publication receipts.

Warning

Changes requested · head 57dfbc9 · 4 findings: 4 major

Severity Finding Where
major F1 Shadow mode rejects every successful legacy ship publication src/core/ship/coordinator.ts:1776
major F2 Ambiguous push success is recorded as a permanent refusal without reconciliation src/core/runEffects.ts:223
major F3 Resume restores the oldest successful publication receipt src/core/dispatch/runLoop.ts:991
major F4 The changed-set gate ignores repository-declared verification commands src/core/runEffectsGit.ts:91
Full review

F1 — High confidence. harness.effects defaults to shadow, where legacy pushes remain authoritative and typed receipts intentionally do not publish pushed_head by: "runner". This unconditional check rejects the resulting by: "push" record, so a successful ship coding round opens its PR and then aborts before review. Keep accepting matching legacy publication evidence while shadow mode is active, or carry the rollout mode into the coordinator decision.

F2 — High confidence. If git push updates the remote but its response is lost, perform() throws and the code immediately persists transport_refused. Every later call with that effectId returns the refusal without checking the destination, leaving a successfully published branch without its receipt. Reconcile the destination ref and SHA after an ambiguous transport failure before recording a refusal.

F3 — High confidence. Object.values(...).find() restores the first successful push receipt, not the latest. After a run publishes more than once and resumes, the stale SHA fails the PR post-step’s publication comparison; because a publication exists, the legacy remote-equality fallback is also disabled. Restore the newest applicable receipt by occurredAt or persisted sequence.

F4 — High confidence. The adapter claims to run repository-declared changed-set gates but instead assumes root-level Vitest, TypeScript, Prettier, hygiene, and specs commands. An onboarded Python repository—or a Node monorepo whose tests and tsconfig live in a workspace—cannot publish when effects are enabled, regardless of its configured resident verification commands. Resolve the repository’s declared changed-set gate configuration and execute that closed command set instead of synthesizing Switchboard-specific commands.

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: The typed push path can lose or misclassify effect receipts, and its new runner publication records fail the durable record validator.

Warning

Changes requested · head 57dfbc9 · 5 findings: 4 major, 1 minor

Severity Finding Where
major F1 Spec contradiction — run-history.md item 2: runner push records are rejected src/core/runRecord.ts:1043
major F2 Spec contradiction — harness.md item 12a: the envelope is not persisted before resolve src/core/dispatch/runLoop.ts:1015
major F3 Ambiguous push success is made a terminal refusal without reconciliation src/core/runEffects.ts:223
major F4 An effectId can be reused for a different command and receive the old receipt src/core/runEffects.ts:164
minor F5 Resume restores the first publication receipt instead of the current one src/core/dispatch/runLoop.ts:991
Full review

F1 — pushed_head events with by: "runner" are now emitted and folded into RunRecord.pushed, but the structural validator accepts only "push" and "salvage". Such records become unreadable through validated stores and conflict with run-history item 2. Accept "runner" and add a round-trip validator regression.

F2 — persistEnvelope only mutates memory and queues the void ledgerRun.setState() call; it proceeds immediately, and runs without a ledger proceed with no durable write at all. A crash between publication and the queued state flush loses the envelope needed to reconcile the effect, contrary to harness item 12a’s persist-before-resolve guarantee. Publication must fail closed until durable persistence is acknowledged.

F3 — Any exception from perform, including a successful git push followed by a lost response or failed ls-remote, becomes a recorded transport_refused. That refusal is authoritative on retry, so the remotely published branch can never be reconciled into a receipt and downstream PR/ship handling aborts. Reconcile refs/heads/<branch> against the intended SHA after ambiguous transport failure before recording a refusal.

F4 — The envelope is overwritten before the prior result is read, and the prior result is validated only by effectId and effect kind. Reusing an ID for a different head, base, or branch therefore returns the old success as if the new command ran. Persist the first envelope immutably and reject any retry whose complete command differs.

F5 — On resume, Object.values(effectState.results).find(...) selects the earliest successful publication. If a run published, made more changes, published again, and restarted before post-processing, the stale receipt fails the current-head check and suppresses PR handling despite the later valid receipt. Restore the latest receipt matching the admitted branch and current head.

@justinhelmer

Copy link
Copy Markdown
Contributor

Re-review requested at 52d3d1d — both arms' findings at 57dfbc9 addressed in one round: shadow mode keeps today's publication evidence for ship readiness and only on demands the runner receipt (the 32 driver-suite cases pass again, plus a case where on refuses a legacy-only publication); an ambiguous transport failure reconciles refs/heads/<branch> against the intended sha before any refusal is recorded; the first envelope is immutable and a retry whose command differs is refused as effect_id_reused; publication fails closed until the envelope's durable write is acknowledged (harness item 12a); the record validator accepts by: runner with a round-trip regression (run-history item 2); the changed-set gate runs the repository's declared verification commands; resume restores the newest receipt for the admitted branch and current head; the lint escape in src/tools/push.ts is gone. CI green.

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: The typed push path remains bypassable from child bash and can persist false terminal refusals around ambiguous publication outcomes.

Warning

Changes requested · head 52d3d1d · 3 findings: 3 major

Severity Finding Where
major F1 Spec contradiction — docs/reference/specs/routing-and-config.md item 34: child bash can still publish outside the typed effect src/core/harness/pi/toolRules.ts:216
major F2 A receipt write failure can turn a successful push into a durable refusal src/core/runEffects.ts:224
major F3 A failed reconciliation read permanently poisons the effect id src/core/runEffects.ts:229
Full review

F1 — High confidence. Effects-on still blocks pushes by parsing shell text, while writable resident worktrees retain GitHub credentials. A command such as G=git; "$G" push origin HEAD:<branch> bypasses potentialChildPush() and publishes directly, violating the typed seam. Remove publication credentials from child-executable commands and expose credentialed publication only through a runner-owned operation.

F2 — High confidence. The inner try includes both perform() and succeed(), so a recordResult() failure after a successful push is treated as a transport failure. If reconciliation finds the pushed SHA but the second receipt write also fails, the outer catch can subsequently persist transport_refused, leaving the remote updated with a durable refusal. Catch only publication transport failures; receipt-persistence failures must remain unrecorded and propagate for recovery.

F3 — High confidence. Reconciliation errors are converted to undefined, making an unavailable remote indistinguishable from a confirmed mismatch. The resulting transport_refused receipt becomes authoritative for that stable effect ID, so every retry returns the refusal without reconciling again—even if the original push succeeded. Persist refusal only after a successful reconciliation proves the destination differs; leave failed reconciliation retryable.

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: The typed push cutover has four paths that can strand or erase an otherwise valid publication.

Warning

Changes requested · head 52d3d1d · 4 findings: 4 major

Severity Finding Where
major F1 Spec contradiction — docs/reference/specs/harness.md item 12a: coding runs without a ledger receive no typed push seam src/core/dispatch/runLoop.ts:1038
major F2 Default-bound coding runs cannot publish a newly created feature branch src/core/runEffectsGit.ts:110
major F3 An unavailable reconciliation is cached forever as a transport refusal src/core/runEffects.ts:229
major F4 Reusing an effect id overwrites its successful receipt src/core/runEffects.ts:184
Full review

F1: With harness.effects: on and run history disabled, ledgerRun is absent, so the push tool receives no effects capability while shell pushes are still refused. Every publication becomes impossible. Either reject this configuration or provide the durable effect store that item 12a promises for every coding harness.

F2: A coding run initially attached to the default branch commonly creates a feature branch before publishing. Passing that initial branch as admittedBranch makes authorization reject the new branch, even though the legacy guard explicitly permits any non-protected branch in this case. Preserve strict matching for explicitly named and PR-bound refs, but allow a default-bound run’s validated run-owned branch.

F3: If git push may have succeeded but the following ls-remote reconciliation also fails transiently, the catch converts the unavailable read into a durable transport_refused. The same effectId then returns that refusal forever without reconciling again, potentially leaving a published remote ref with no receipt. Distinguish an unavailable reconciliation from a confirmed mismatch and keep the effect retryable under the same ID.

F4: A different command reusing an ID calls refuse(), whose recordResult() replaces the result stored under that ID. If the original command already succeeded, this erases its authoritative receipt; subsequent correct retries return the reuse refusal, and restart recovery can no longer find the publication. Return or audit the reuse refusal without replacing the original result.

@coreplane-switchboard

Copy link
Copy Markdown
Contributor Author

Re-review requested at a70626b — all six majors from the second review pair are addressed in one architectural round: effects-on removes the child’s publication credential, receipt persistence is outside transport handling, unavailable reconciliation stays retryable, effect-id reuse preserves the standing result, effects-on requires durable history, and default-bound branch authority is distinct from named/PR refs. CI is green at this head; dispositions and the refreshed description are submitted.

@justinhelmer

Copy link
Copy Markdown
Contributor

Re-review requested at a70626b — the round-2 findings at 52d3d1d addressed: under harness.effects: on the child's git holds no publication credential (the runner's effect port alone can write refs), so any shell spelling of a push fails at the remote and the text rule is a courtesy message, not the seam; only publication transport failures are caught, a receipt-persistence failure propagates; an unavailable reconciliation is a retryable typed outcome, never a refusal; an effect-id reuse refusal does not replace the original receipt; on without run history is rejected at config/startup; a default-bound run may publish its validated run-owned branch. CI green at this head.

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: Effects-on is not fail-closed for cold executors and can be enabled without the durable ledger it requires.

Warning

Changes requested · head a70626b · 2 findings: 2 major

Severity Finding Where
major F1 Spec contradiction — docs/reference/specs/routing-and-config.md item 34: cold children retain write credentials src/execution/factory.ts:1171
major F2 Spec contradiction — docs/reference/specs/routing-and-config.md item 34: effects-on accepts configurations with no durable effect ledger src/config/validate.ts:326
Full review

F1 (major, high confidence): perThreadCheckout() still derives a write-scoped credential from the coding profile, and the local, E2B, and Cloudflare executors expose it to child commands. An effects-on run that falls back from a resident—or uses a non-resident repository—can therefore bypass the advisory bash detector with an obfuscated real push. These executors also lack publishGit, so the typed push cannot succeed. Effects-on must provide read-only child credentials and a runner-owned publication transport for every supported backend, or refuse unsupported backends before execution.

F2 (major, high confidence): Validation only checks that a runHistory block exists. store: file deliberately creates no RunLedger, and a Worker configuration without its bearer also degrades to no ledger. In either case effects-on passes startup validation but cannot persist envelopes through setStateDurable; resident children simultaneously lose shell publication, making publishing impossible. Validate the effective durable ledger capability—including store type and credential availability—at deployment/startup rather than checking section presence.

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: Effects-on publication is not yet fail-closed across fallback, startup, and restart paths.

Warning

Changes requested · head a70626b · 4 findings: 3 major, 1 minor

Severity Finding Where
major F1 Spec contradiction — docs/reference/specs/routing-and-config.md item 34: effects-on cold sandboxes bypass runner-only publication src/execution/factory.ts:582
major F2 Spec contradiction — docs/reference/specs/routing-and-config.md item 34: effects-on accepts run history that produces no durable ledger src/config/validate.ts:326
major F3 Default-bound runner receipts are not restored after restart src/core/dispatch/runLoop.ts:1029
minor F4 Effect implementation paths are outside spec coverage src/core/runEffects.ts:1
Full review

F1 (high confidence): When a repository is not onboarded or resident attachment fails, this path creates the ordinary per-thread executor without an effects-only credential mode. The child therefore retains the legacy ability to push, while that executor lacks both publishGit and the resident-provided verification commands. Under harness.effects: on, either provision an equivalent read-scoped typed-effect backend or fail closed instead of falling back.

F2 (high confidence): Checking only for a runHistory section does not prove a durable ledger exists. For example, a configured worker store whose bearer is absent is downgraded to history-off during startup, leaving effects-on runs without a durable effect capability after shell publication has been disabled. Startup and deploy validation must require the effective ledger, including its credential, rather than merely the config block.

F3 (high confidence): publicationBranch comes from the resident’s original sticky binding. A default-bound run can legitimately create and publish a feature branch while that binding remains the default branch. If the process dies after persisting the effect result but before emitting its runner publication event, resume compares the feature-branch receipt against the default binding, fails to restore it, and can record only legacy push evidence that effects-on ship readiness rejects. Restore against the current checked-out branch and head, while retaining strict authority checks for named bindings.

F4 (high confidence): specs:coverage reports src/core/runEffects.ts, src/core/runEffectsGit.ts, and src/tools/push.ts as uncovered. Add these implementation paths to the appropriate changed spec headers so future changed-spec and contradiction checks include the typed-effect behavior.

@justinhelmer

Copy link
Copy Markdown
Contributor

Parked for the morning after three review rounds (57dfbc952d3d1da70626b; CI green at this head). Round 3's two arms both requested changes, and three majors have survived since round 1 in one form or another: (1) under harness.effects: on the cold executors (local, E2B, Cloudflare sandbox; the fallback when a repository is not onboarded or resident attach fails) still hand the child a write-scoped credential, so an obfuscated shell push bypasses the seam there (routing-and-config item 34; factory.ts); (2) validation accepts on with a run-history block that produces no durable ledger (store: file, or a Worker store whose bearer is absent), so publication becomes impossible after shell publication is disabled (validate.ts); (3) after a restart a default-bound run's feature-branch receipt is compared against the default binding and not restored (runLoop.ts); plus a minor: the effect implementation paths are outside spec coverage. These are one design question — where the write credential lives for every executor kind and what on must be able to assume durably — not another patch round, so this unit waits for a person: (a) a design pass on the credential boundary per executor (resident, cold sandbox, local) and the durable-ledger precondition, then one implementation round; or (b) narrow U1 to residents only (on refuses cold executors) and land it. No further rounds are posted tonight.

@justinhelmer

Copy link
Copy Markdown
Contributor

Re-review requested at b205d535 — rebased on current main and all three majors addressed, with the design question they raised settled.

The seam stays one shared implementation; there is no second behaviour for residents. What is new is a capability each executor declares: it keeps every repository write credential out of the child's environment, and it gives the runner a way to publish that workspace's work. The resident declares it and satisfies it today. An executor that does not declare it cannot serve a write run while harness.effects: on — the run is refused before any work starts, naming the executor and the reason, rather than falling back to the credentialed shell. It is expressed as a declared capability rather than a list of executor names, so a later sandbox adapter flips one declaration and no logic.

Also in this round: on is refused at config validation and at startup when the effective durable ledger is absent, naming which part is missing; and resume restores the newest publication receipt matching the run's own published ref and current head, so a default-bound run that created and published a feature branch no longer loses it.

Stated cost, which belongs in the record of this change: until a sandbox adapter exists, a repository that is not onboarded, or one whose resident attach fails, cannot do write work under on and is told so by name.

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: The typed push path cannot recover one successful publication failure mode, and shadow mode does not satisfy its no-ledger contract.

Warning

Changes requested · head b205d53 · 2 findings: 1 major, 1 minor

Severity Finding Where
major F1 A lost receipt turns a successful publication into a permanent stale-head refusal src/core/runEffects.ts:210
minor F2 Spec contradiction — docs/reference/specs/harness.md item 12a: shadow mode records nothing without a ledger src/core/dispatch/runLoop.ts:1047
Full review

F1 — If publication succeeds but recordResult fails, the workspace and remote are already at the rebased commit while the durable envelope still contains the pre-rebase expectedHead. Retrying the same effect then hits stale_head before reconciliation and durably records a refusal, leaving a real push without the runner receipt required for review. Recover an envelope with no result by reconciling its destination before the stale-head check, and add a retry regression after receipt persistence failure.

F2 — effects is bound only when ledgerRun exists. With the default shadow configuration and no run-history store, the advertised push tool therefore returns “runner effects are unavailable” and records none of the resolve/authorize/fence/gate decision promised by harness item 12a. Bind a non-durable recording implementation for shadow mode, or update the configuration contract to require a ledger there too.

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: Typed publication cannot succeed on the resident and its two recovery paths can persist false refusals.

Warning

Changes requested · head b205d53 · 3 findings: 3 major

Severity Finding Where
major F1 Spec contradiction — harness.md item 12a: clean resident worktrees are always refused as dirty src/core/runEffectsGit.ts:74
major F2 Spec contradiction — harness.md item 12a: receipt-write recovery records stale_head after a successful push src/core/runEffects.ts:210
major F3 Spec contradiction — harness.md item 12a: unavailable reconciliation becomes a durable transport refusal src/core/runEffectsGit.ts:84
Full review

F1 — High confidence. ResidentExecutor.exec() renders successful empty output as "(no output)", but facts() recognizes only "" as a clean status. Therefore every clean resident checkout is classified as dirty and effects-on publication stops before rebase, gates, or push. Preserve raw empty output for this probe or use a status command with an explicit machine-readable clean marker.

F2 — High confidence. Publication rebases the checkout before pushing. If the push succeeds but receipt persistence fails, retrying the same envelope compares the rebased HEAD with the original pre-rebase expectedHead and permanently records stale_head. Reconcile an unrecorded effect against the prepared/published SHA before applying the initial-head guard, which requires durably retaining that prepared intent.

F3 — High confidence. Executor.exec() represents ordinary git failures as exit N: text, so a network or authentication failure from git ls-remote becomes undefined, indistinguishable from a successful missing/mismatched ref. After an ambiguous push, that result is persisted as transport_refused, preventing another attempt even when the push may have landed. Reconciliation must distinguish a successful ref read from command failure and return reconciliation_unavailable for the latter.

justinhelmer and others added 3 commits September 22, 2026 18:08
Co-Authored-By: coreplane-switchboard[bot] <318072483+coreplane-switchboard[bot]@users.noreply.github.com>
Co-Authored-By: coreplane-switchboard[bot] <318072483+coreplane-switchboard[bot]@users.noreply.github.com>
Co-Authored-By: coreplane-switchboard[bot] <318072483+coreplane-switchboard[bot]@users.noreply.github.com>
@justinhelmer

Copy link
Copy Markdown
Contributor

Re-review requested at f30141bb. The round-4 findings are addressed, and the first one meant the feature never published at all.

The worktree probe no longer depends on empty output. Executor.exec() renders a successful empty command as (no output) on the resident, the sandbox and e2b, while facts() was testing for an empty string, so every clean checkout was classified dirty and publication stopped before rebase, gates and push. That is also why it passed the suite: the recording and fake executors returned an empty string where the real ones do not, so the tests proved the opposite of production. Both halves are fixed, and there is now a conformance assertion that every executor renders a successful empty command identically, so the next divergence of this kind fails a test rather than shipping.

Also in this round: the prepared intent is retained durably and an envelope with no result reconciles its destination before the initial-head guard, so a push that succeeded while receipt persistence failed recovers its receipt instead of durably recording stale_head; a failed ls-remote is distinguished from a ref that is genuinely absent or different and yields a retryable reconciliation_unavailable rather than a permanent transport_refused; and shadow mode's recording behaviour now matches what harness item 12a promises it records.

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: The typed publication path still trusts child-mutable git configuration, and restart recovery can lose a durable feature-branch receipt.

Warning

Changes requested · head f30141b · 2 findings: 2 major

Severity Finding Where
major F1 Privileged publication follows the child-controlled origin deploy/cloudflare-resident/worker.ts:7214
major F2 Spec contradiction — docs/reference/specs/harness.md item 12a: resume can ignore the durable published ref src/core/dispatch/runLoop.ts:1042
Full review

F1 (major, high confidence): The privileged route pushes to origin, whose configuration remains writable by the child. A background process or repository gate can change origin after validation, causing root-owned Git to follow an unvalidated endpoint or execute a child-controlled local receive-pack command with access to the one-shot credential. Pass the validated canonical endpoint through publishGit and push directly to it without consulting worktree remote configuration.

F2 (major, high confidence): The effect receipt becomes durable before the pushedBranch state patch. If the process dies between those writes, a resumed default-bound run selects the original binding, such as main, and rejects the durable receipt for its published feature branch. Retrying the effect returns the prior result without restoring runnerPublication, so ship coordination can abort despite a successful publication. Derive candidate branches directly from durable successful receipts and match them against the current head.

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: Effects-on removes the credential that the existing automatic checkpoint path still needs, so interrupted coding work can be lost.

Warning

Changes requested · head f30141b · 1 finding: 1 major

Severity Finding Where
major F1 Effects-on disables automatic salvage checkpoints src/core/dispatch/runLoop.ts:843
Full review

F1 — High confidence. With harness.effects: on, resident worktrees receive only a read-scoped GitHub credential, but budget, completion, provider-failure, and compaction salvage still call salvageBudgetPush, which performs a legacy git push. That push cannot authenticate, so the checkpoint fails and resident cleanup can discard the interrupted work. Route mechanical salvage through a runner-owned publication capability and add effects-on regression coverage for these checkpoint paths.

@justinhelmer

Copy link
Copy Markdown
Contributor

Parked after six review rounds, for a person to decide

Heads: 57dfbc9f52d3d1d1a70626b2b205d535f30141bb. Twelve review verdicts, not one approval. CI is green at this head.

The three findings still open are not the same kind of thing as the earlier rounds, and that is why this stops here rather than taking a seventh.

1. Privileged publication follows a child-controlled remote. deploy/cloudflare-resident/worker.ts pushes to origin, whose configuration the child can still write. A background process or a repository gate can change origin after validation, so root-owned git can follow an unvalidated endpoint, or run a child-controlled local receive-pack, while holding the one-shot credential. This is the credential boundary the record exists to create, breached from the other side: we took the credential out of the child's hands and left the child able to steer where the privileged process points it.

2. Turning the seam on disables the safety net. Under harness.effects: on the resident gets a read-scoped credential, but budget, completion, provider-failure and compaction salvage all still call salvageBudgetPush, which performs a legacy git push. That push cannot authenticate, the checkpoint fails, and resident cleanup can discard the interrupted work. So enabling the feature removes the mechanism that today rescues an interrupted child — the exact loss this whole line of work exists to prevent.

3. A resumed run can reject its own durable receipt. The effect receipt becomes durable before the pushedBranch state patch. A process death between those two writes leaves a resumed default-bound run selecting its original binding, rejecting the receipt for the feature branch it really published, and a retry returns the prior result without restoring the publication.

The reading that matters

Rounds one through four were implementation defects and each was fixed. Round five was the feature not working at all: a clean worktree was classified dirty, so typed push never published, and the suite passed because the fake executors did not match the real one's output. Round six is a different class again: two of the three findings are consequences of enabling the seam rather than mistakes inside it. Salvage and the privileged remote are both things the design has to answer for, and neither was in the plan's unit one.

That is a scope question, not a code question, so it belongs to a person. The choices as I see them: extend unit one to cover the salvage path and the canonical remote, which makes it materially bigger than the plan describes; or split those two out as their own unit and land what is here once the security finding is closed, accepting that on cannot be switched until the salvage unit lands; or reconsider whether the unit boundary in the plan was drawn in the right place, given that six rounds kept discovering the seam's edges rather than its middle.

No further rounds are being posted. Issue #2197 is being addressed separately and independently by #2221, which works under today's default configuration and does not depend on this.

@justinhelmer

Copy link
Copy Markdown
Contributor

Closed to be redrawn: publication is privileged

Six heads, twelve review verdicts, thirty findings, no approval. Closing rather than landing and splitting, on the maintainer's decision, with the reason stated plainly: the goal is to land a change that is collapsed, elegant, clean and understandable, not to arrive there incrementally. Nothing here is lost — the branch stands, and the thirty findings below become the acceptance criteria of the redrawn unit rather than defects to be rediscovered by it.

Why this one did not converge

Rounds one to four were ordinary implementation defects, each fixed. Round five was the feature not working at all: facts() called a worktree clean on empty output while Executor.exec() renders a successful empty command as (no output), so every clean checkout was classified dirty and typed push never published — and the suite passed because the fake executors returned what the real ones do not. Round six was a different class again: two of its three majors were consequences of enabling the seam rather than mistakes inside it. That is the signal that the unit boundary was drawn in the wrong place.

The unit was scoped as "push owns the exact tree and publication receipt". What the findings keep saying is that the real subject is larger and simpler to state: publication is privileged. Everything that reached the remote had to be in scope from the start.

What the redrawn unit must cover

  1. The typed push, as this attempt built it.
  2. Salvage through the same seam. Under the flag the resident holds a read-scoped credential while budget, completion, provider-failure and compaction salvage still perform a legacy git push, which cannot authenticate, so enabling the feature disables the mechanism that rescues interrupted work. Salvage is a privileged publication and must travel the same path, not remain a second publisher.
  3. The remote's provenance. Privileged publication currently follows origin, whose configuration the child can still write, so root-owned git can be pointed at an unvalidated endpoint or a child-controlled receive-pack while holding the one-shot credential. The canonical endpoint must be carried, never re-read from child-writable configuration.
  4. Ordering of receipt and state. The effect receipt becomes durable before the pushedBranch patch, so a process death between them leaves a resumed run rejecting the receipt for the branch it really published.

One coherent change, one flag, and no intermediate state in which the flag cannot be switched. The invariant behind all of it: no code path publishes except through the seam, enforced rather than described.

Acceptance criteria, from the thirty findings raised here

Twenty-seven major, three minor, deduplicated by subject.

Credential boundary — cold executors must not retain write credentials; child bash must not publish outside the typed effect by any spelling; a clean resident worktree must be recognised as clean through the real executor's output contract; privileged publication must not follow a child-writable remote.

Ambiguity and recovery — an ambiguous push success must reconcile before any refusal is recorded; an unavailable reconciliation must stay retryable rather than becoming a durable transport refusal; a failed reconciliation read must not poison the effect id; a receipt-write failure after a successful push must not become a durable refusal or a stale_head; resume must restore the newest receipt matching the run's own published ref and head, never the oldest.

Identity and persistence — the envelope must be persisted before resolve; an effect id reused for a different command must be refused without overwriting the original receipt; runner push records must pass the durable record validator; the effect implementation paths must be inside spec coverage.

Configuration — the flag must be refused where no durable ledger exists; shadow must record what the contract says it records; shadow must not reject a successful legacy publication; a default-bound run must be able to publish its own validated feature branch.

Gates — the changed-set gate must resolve the repository's declared verification commands rather than assuming this repository's script names, which is issue #2211 and is the same defect seen from the runner side.

Related

Issue #2197, which unit one was meant to fix, is addressed independently by #2221 and works under today's default configuration, so it does not wait on this. Issue #2211 covers the gate-resolution finding on the child side. The plan's unit one is to be rewritten rather than amended.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant