fix(ship): wait on operator-owned checks - #2204
coreplane-switchboard[bot] wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Changes requested: Operator-check classification is too broad, and the one required thread report can be lost after a transient delivery failure.
Warning
Changes requested · head 29e2c72 · 2 findings: 1 major, 1 minor
| Severity | Finding | Where |
|---|---|---|
| major | F1 Generic deploy text suppresses valid coding fix rounds | src/core/ship/checkFindings.ts:44 |
| minor | F2 A failed thread post is permanently marked as reported | src/channels/adminCoordinator.ts:2127 |
Full review
F1 (major, high confidence): OPERATOR_COMMAND matches any occurrence of deploy, including ordinary repository-owned CI output such as “deploy test failed” or a failing command named deploy:check. Those failures would be parked as operator preconditions instead of opening the required coding fix round. Require evidence of an operator instruction rather than a generic word—for example, a specific imperative pattern or structured check ownership.
F2 (minor, high confidence): The route persists the blocked_by_operator_check boundary before attempting the thread reply, then swallows reply failures and returns success. The pipeline consequently sets operatorReported and never retries, so a transient Slack failure—or an unavailable ioFor—permanently loses the required requester-facing report. Track delivery separately and retry/reconcile until it succeeds, while retaining the once-per-head guarantee.
There was a problem hiding this comment.
Changes requested: Repository CI can be misclassified as operator-owned, and the once-per-head report lacks durable delivery idempotency.
Warning
Changes requested · head 29e2c72 · 2 findings: 1 major, 1 minor
| Severity | Finding | Where |
|---|---|---|
| major | F1 Spec contradiction — docs/reference/specs/agent-ship.md item 9: repository CI mentioning deploy is parked as operator-owned | src/core/ship/checkFindings.ts:44 |
| minor | F2 Spec contradiction — docs/reference/specs/agent-ship.md item 9: operator report delivery is not once-only | src/channels/adminCoordinator.ts:2094 |
Full review
F1 — Major, high confidence. OPERATOR_COMMAND matches any standalone deploy, including paths such as deploy/cloudflare-memory/sessionLog.test.ts. A repository-owned CI failure whose output names such a test is therefore marked operatorPrecondition; the approved unit waits instead of opening the required coding fix round. Restrict this to evidence of an actual operator instruction, rather than a bare keyword, and add a regression test for repository CI output containing a deploy/… path.
F2 — Minor, high confidence. The handler persists the round and posts to Slack without a durable delivery or deduplication marker. If the HTTP response is lost after the Slack post, the workflow retry appends another boundary and posts the sentence again; if reply() fails, the exception is swallowed and the sentence is never retried. Persist a stable per-head report identity and track delivery separately so retries can complete an undelivered post without duplicating a delivered one.
29e2c72 to
0833d34
Compare
|
Re-review requested at 0833d34 — both round-1 findings addressed: a red check is classified by ownership first (the repository's own CI is never an operator precondition, whatever its output says) and only an external app's check with an operator-instruction shape is parked, never a bare keyword; the once-per-head operator report has a stable identity with delivery tracked separately from the boundary, retried by catch-up when undelivered and never posted twice. CI green at this head. |
There was a problem hiding this comment.
Changes requested: Required external checks are misclassified as repository-owned, so the operator-blocking path is bypassed.
Warning
Changes requested · head 0833d34 · 1 finding: 1 major
| Severity | Finding | Where |
|---|---|---|
| major | F1 Required external checks bypass operator blocking | src/core/ship/checkFindings.ts:64 |
Full review
F1 — required.has(run.name) treats every required context as repository-owned. An external operator check configured as required—such as production impact requesting a secret deployment—therefore becomes an ordinary CI failure and incorrectly spawns a coding child. Required status only identifies a merge gate, not ownership. Remove it as an ownership signal and classify ownership from the check’s app plus the repository’s declared CI policy.
There was a problem hiding this comment.
Changes requested: Required external checks can still be routed to a coding child instead of the operator hold.
Warning
Changes requested · head 0833d34 · 1 finding: 1 major
| Severity | Finding | Where |
|---|---|---|
| major | F1 Required external checks are misclassified as child-owned | src/core/ship/checkFindings.ts:64 |
Full review
F1: required.has(run.name) overrides the creating App and operator-action output. If the external production impact check is branch-required, it is classified as child-owned, spawning a coding fix round instead of reporting the operator action and waiting. Restrict repository ownership to known repository CI identities, and add a regression test where the external operator check is also required.
0833d34 to
f74d19b
Compare
|
Re-review requested at f74d19b — the round-2 finding addressed: required status is no longer an ownership signal; a red check is repository-owned only when its creating App is the repository's own CI, an external app's check is an operator precondition when its output carries an operator instruction, and a branch-required external check holds instead of spawning a child (the #2182 fixture). CI at this head was red only on the known memoryWorkerDiagnostics flake (#2119), retried. |
There was a problem hiding this comment.
Changes requested: The implementation is coherent, but two persisted behavior contracts were left stale.
Warning
Changes requested · head f74d19b · 2 findings: 2 minor
| Severity | Finding | Where |
|---|---|---|
| minor | F1 Spec contradiction — agent-ship.md item 19: operator-blocked rounds now map to approved | docs/reference/specs/agent-ship.md:33 |
| minor | F2 Spec contradiction — run-history.md item 50: persisted operator-report fields are omitted | docs/reference/specs/run-history.md:151 |
Full review
F1: Item 19’s exhaustive standing table omits blocked_by_operator_check, while ROUND_STAGE now maps it to approved. Add that mapping so the behavioral contract matches the fold.
F2: Item 50’s documented CoordinatorUnit shape omits the new persisted operatorCheckReports and rounds[].reportHead fields. Document them and bind the new validation test explicitly so the durable row contract cannot drift.
There was a problem hiding this comment.
Changes requested: Operator-check classification misses common imperative formatting, and the ship spec still contradicts the new ownership rule.
Warning
Changes requested · head f74d19b · 2 findings: 2 minor
| Severity | Finding | Where |
|---|---|---|
| minor | F1 Operator instructions in Markdown lists are misclassified as child-owned | src/core/ship/checkFindings.ts:44 |
| minor | F2 Spec contradiction — agent-ship.md item 9: required/App ownership disagrees with the new policy | docs/reference/specs/agent-ship.md:20 |
Full review
F1: INSTRUCTION_START only recognizes an imperative at the literal start of a line or after punctuation. Common check output such as - Run deploy secrets … or To fix this, run deploy secrets … is therefore treated as child-owned, causing a pointless coding child to start for an operator-only precondition. Normalize Markdown prefixes or detect the imperative clause more flexibly, with regression tests for list and preface forms.
F2: Item 9 still says required contexts and GitHub Actions are repository-owned, while the new row and implementation say branch-required status conveys no ownership and recognize only policy-matched Depot ci / * checks. Update the behavioral paragraph so the ownership contract is unambiguous.
|
Parked for the morning after three fix rounds (29e2c72 → 0833d34 → f74d19b; CI green at this head). Round 3's two arms each left two minors and called the implementation coherent: three are spec drift — agent-ship item 19's standing table omits |
f74d19b to
09547c5
Compare
Co-Authored-By: coreplane-switchboard[bot] <318072483+coreplane-switchboard[bot]@users.noreply.github.com>
09547c5 to
383080c
Compare
|
Re-review requested at 383080c — the four minors are addressed and the category is renamed. The ending is now |
There was a problem hiding this comment.
Changes requested: Two localized gaps remain in operator-instruction classification and persisted head validation.
Warning
Changes requested · head 383080c · 2 findings: 2 minor
| Severity | Finding | Where |
|---|---|---|
| minor | F1 Operator instruction detection misses colon-prefaced commands | src/core/ship/checkFindings.ts:43 |
| minor | F2 Spec contradiction — run-history.md item 50: abbreviated heads are accepted as full commits | src/core/coordinator/contract.ts:544 |
Full review
F1: INSTRUCTION_START recognizes only the exact To fix this, … comma form. An external check saying To fix this: run deploy secrets … is treated as child-owned, spawning a pointless coding round. Accept common clause separators such as : and add a regression test.
F2: COMMIT_HEAD accepts 7–40 hexadecimal characters, so both operatorCheckReports and rounds[].reportHead accept abbreviated hashes. Run-history item 50 promises full commit heads and rejection of malformed heads. Require the full 40-character shape and test that a 7-character hash is refused.
There was a problem hiding this comment.
Changes requested: Two classifier and persistence edge cases contradict the newly documented operator-check contract.
Warning
Changes requested · head 383080c · 2 findings: 2 minor
| Severity | Finding | Where |
|---|---|---|
| minor | F1 Spec contradiction — agent-ship.md item 9: direct deploy imperatives spawn a coding child | src/core/ship/checkFindings.ts:45 |
| minor | F2 Spec contradiction — run-history.md item 50: abbreviated report heads pass validation | src/core/coordinator/contract.ts:544 |
Full review
F1: An external check saying “Deploy the worker, then re-run this check” is an operator imperative, but the deploy matcher requires the sentence to begin with run or execute. It is therefore treated as repository-fixable and spawns a pointless coding child. Recognize direct deploy … imperatives and add a regression test.
F2: COMMIT_HEAD accepts 7–40 hexadecimal characters, while the new persisted ledger contract requires a full commit head. A seven-character prefix can collide and cause one head’s boundary or report delivery to be mistaken for another’s. Require the full SHA shape and test that abbreviated heads are rejected.
Held, not merging: this does not fire on the incident it was written forVerified against the diff at The real check output has no imperative. Issue #2182 quotes what Polylane actually posted:
That is a statement of fact. It contains no The positive fixture invents the sentence that makes it pass. Diff line 583 composes the check output as the real paragraph plus an appended imperative that Polylane never wrote:
And the negative fixture is the real shape. Diff line 629 encodes Second defect. Why this is held rather than given another round. The classifier keys on imperative text, and the incident proves the text we must catch carries no imperative. That is a design question, not four more minors: how a red check's ownership and its remedy are determined when the check only states a fact. That design is being worked separately, including whether the checks step is the right home at all and whether the ladder is this repository, then another repository, then a person. Reverting costs nothing: this was never deployed, and the ending name exists only inside this diff, so no durable row carries it. Two notes for whoever picks it up: a partial revert that keeps the loop regression must retarget the spec proof rows it adds to agent-ship item 9 or |
|
Closing rather than leaving open. This does not fire on the incident it was written for: the real Polylane check states a fact with no imperative, its positive test fixture appends a sentence Polylane never posted, and its negative fixture is the real shape, so the suite asserts the opposite of #2182's ask. It also treats only Depot as repository CI, which would park every github-actions failure in coreplanelabs/infrastructure as external. Nothing here was deployed and the new ending name exists only inside this diff, so closing costs nothing and reopening is one click. The corrected ownership-and-remedy design is being written separately, with the escalation running from this repository to another repository to a person. Evidence is in the comment above; issue #2182 stays open. |
The merge door now separates repository CI failures from operator preconditions before opening a fix round. Operator-owned failures wait at the approved head, avoiding no-op children and round-cap loops.
Why: Issue #2182 showed an external production check consuming fix rounds for a missing secret no child could supply. Record 0055 puts this decision in the post-approval checks step.
Where to look
Feedback wanted: Please scrutinize the App/action ownership boundary and whether the same-head report suppression remains correct across every checks-settled wake.
Risk: Misclassification could either waste a coding round or park a code-fixable failure. The blast radius is ship's post-approval checks step; reverting this commit restores the prior all-failures-open-findings behavior.
Verified: 326 focused Vitest tests, scoped TypeScript, Prettier, hygiene, and specs checks passed after rebasing onto origin/main; CI remains the full gate.
Decisions (2)
Validation (6 criteria)
npx vitest runover five focused files passed;a red Depot shard…reaches the findings spawn.an operator-owned red check…passed, including a repeated red read with exactly one blocked round note.NODE_OPTIONS=--max-old-space-size=6144 npx tsc --noEmit -p tsconfig.jsonand scopednpx prettier --check …passed.npm run hygiene:checkandnpm run specs:checkpassed.For agents
Rebased onto origin/main at 9f6516d before the push. Focused repro:
npx vitest run src/core/ship/checkFindings.test.ts src/core/ship/coordinator.test.ts src/core/pipelineStanding.test.ts src/channels/adminCoordinator.test.ts src/execution/githubPulls.test.ts.Requested by @justinhelmer in slack:C0BRRHKFLCB
🤖 Generated with Claude Code