fix(core): keep recovery off the person's plate - #2168
Conversation
There was a problem hiding this comment.
Changes requested: The held-review continuation still drops the person's answer, and the new ratchet misses ordinary imperative run strings.
Warning
Changes requested · head 1ab02ba · 2 findings: 1 major, 1 minor
| Severity | Finding | Where |
|---|---|---|
| major | F1 Spec contradiction — agent-ship.md item 9: held replies are claimed as continuation but their answer is discarded | docs/reference/specs/agent-ship.md:104 |
| minor | F2 The imperative run check only recognizes an allowlist of command prefixes | scripts/user-message-check.mjs:41 |
Full review
F1 — A human-gated review still ends the unit as held. When the person replies with the requested receipt, dispatcher.ts replaces that reply with the pipeline’s original task, so the answer never reaches the resumed review. This also contradicts agent-ship item 9. Park the unit with the finding as pending state and resume using the person’s answer.
F2 — The run pattern accepts imperatives such as Run the command again. and template forms like Run ${command} because it only recognizes selected command prefixes. Match imperative run at a clause boundary regardless of the following command, and add failing fixtures for generic and interpolated forms.
There was a problem hiding this comment.
Changes requested: The held-review continuation still drops the person's answer, and the new ratchet misses ordinary imperative run strings.
Warning
Changes requested · head 1ab02ba · 2 findings: 1 major, 1 minor
| Severity | Finding | Where |
|---|---|---|
| major | F1 Spec contradiction — agent-ship.md item 9: held replies are claimed as continuation but their answer is discarded | docs/reference/specs/agent-ship.md:104 |
| minor | F2 The imperative run check only recognizes an allowlist of command prefixes | scripts/user-message-check.mjs:41 |
Full review
F1 — A human-gated review still ends the unit as held. When the person replies with the requested receipt, dispatcher.ts replaces that reply with the pipeline’s original task, so the answer never reaches the resumed review. Park the unit with the finding as pending state and resume using the person’s answer.
F2 — The run pattern accepts imperatives such as Run the command again. and template forms like Run ${command} because it only recognizes selected command prefixes. Match imperative run at a clause boundary regardless of what follows, and add failing fixtures for generic and interpolated forms.
f46a42b to
b6d6967
Compare
There was a problem hiding this comment.
Changes requested: The human-answer path still fails at the round cap, and both prior findings remain partially unresolved.
Warning
Changes requested · head b6d6967 · 3 findings: 1 major, 2 minor
| Severity | Finding | Where |
|---|---|---|
| minor | F1 Spec contradiction — agent-ship.md item 9: the normative behavior still says human-gated rounds end held | docs/reference/specs/agent-ship.md:20 |
| minor | F2 The imperative run check still misses ordinary run instructions | scripts/user-message-check.mjs:41 |
| major | F3 A human answer at the round cap cannot reach its mandatory re-review | src/core/ship/coordinator.ts:2408 |
Full review
F3: With maxRounds=1, the human answer opens the findings child, but its successful result calls the ordinary nextReview(), which immediately ends at round_cap. Exempt this required post-answer review from the ordinary cap, as post-rebase review already is, and cover answer → fix → re-review → merge-ready at maxRounds=1.
F1: The implementation now preserves the answer, but item 9’s normative paragraph still says an all-human-gated round ends held and must be re-issued. Update it to match the live parked-unit behavior already described in validation row 104.
F2: The new expression still misses prohibited imperatives. It is case-sensitive at the start, so run the command again passes, and its broad verb exclusion lets Run the formatter when CI is green pass because the clause contains “is.” Detect the imperative run without exempting unrelated predicates later in the sentence, with regression fixtures for both forms.
85a3249 to
0725f0f
Compare
There was a problem hiding this comment.
Changes requested: The three prior findings are resolved, but the latest fix masks rather than repairs the budget-ending ordering regression.
Warning
Changes requested · head 0725f0f · 1 finding: 1 minor
| Severity | Finding | Where |
|---|---|---|
| minor | F4 Budget endings no longer close on the established workspace state | src/core/harness/windDown.ts:313 |
Full review
F4: established() still appends the bug sentence to the workspace sentence, so the answer ends on the bug rather than the established tree state. The latest commit changes the conformance expectation to match this regression instead of correcting it. Compose the ending as: budget stop, bug statement, then uncommitted/unpushed counts and torn-down workspace state.
3eb079e to
a8f227a
Compare
There was a problem hiding this comment.
LGTM: F4 is resolved: budget endings again close on the measured workspace state, with the cross-harness contract updated consistently.
Note
Approved · head a8f227a · no findings
Full review
F4 is resolved: budget endings close on measured workspace facts. Test-guard’s MCP view, boot, tool-rules, wind-down, coordinator, renewal, and MCP-connect signals are message-focused refactors with verification intact.
There was a problem hiding this comment.
Changes requested: The new consistency ratchet has two ordinary bypasses that contradict its promised coverage.
Warning
Changes requested · head a8f227a · 2 findings: 2 minor
| Severity | Finding | Where |
|---|---|---|
| minor | F1 Spec contradiction — routing-and-config.md item 33: untyped question properties bypass the ratchet | scripts/user-message-check.mjs:102 |
| minor | F2 Spec contradiction — routing-and-config.md item 33: run-page strings outside Vue text nodes are unscanned | scripts/user-message-check.mjs:138 |
Full review
F1 — shapeOf exempts every literal assigned to a property named question, even without a kind: "question" discriminator. For example, { question: "Re-send the request" } passes the ratchet despite being untyped. Remove this broad exemption or require the typed discriminator promised by item 33, with a regression fixture.
F2 — Web scanning uses extractTemplateText, which deliberately omits static attributes, bindings, and all web/src/**/*.ts copy. A recovery imperative in an aria-label, title, placeholder, or rendered TypeScript model therefore passes despite item 33 covering run-page strings. Expand the scanner’s web surfaces and add fixtures for these locations.
a8f227a to
6c9e991
Compare
There was a problem hiding this comment.
Changes requested: The new invariant still leaves user-facing recovery imperatives in covered command and authorization paths.
Warning
Changes requested · head 6c9e991 · 1 finding: 1 minor
| Severity | Finding | Where |
|---|---|---|
| minor | F1 Spec contradiction — docs/reference/specs/routing-and-config.md item 33: recovery refusals still tell the person to act | src/core/dispatch/authorize.ts:171 |
Full review
F1: The user-boundary branches still say “raise your own boundary,” “drop your overrides,” “ask … to raise,” “send the message again,” or “spawn it,” while src/core/commands/repo.ts:773 still says “re-run the command.” These are user-facing recovery imperatives that the new scanner misses, so ordinary boundary refusals and transient resident failures violate item 33. Rewrite these as state/outcome statements and extend the guard with regression fixtures for the missed forms.
The test-guard warnings are refactors with verification intact: the renamed tests retain their assertions, and the coordinator assertion-count reduction removes obsolete wording checks while asserting the replacement behavior.
6c9e991 to
80861a5
Compare
There was a problem hiding this comment.
LGTM: F1 is fixed: recovery refusals now narrate the unchanged system outcome, and the expanded ratchet and regressions cover the missed imperative forms.
Note
Approved · head 80861a5 · no findings
Full review
F1 is resolved. The test-guard title changes preserve their original verification or expand it to the rewritten outcomes.
80861a5 to
c9c7bd1
Compare
There was a problem hiding this comment.
LGTM: The stale CI expectations now match the rebased production wording, and all prior check failures are resolved.
Note
Approved · head c9c7bd1 · no findings
Full review
The prior CI failures are resolved. Test-title changes are wording-only refactors or add assertions; verification remains intact.
There was a problem hiding this comment.
Changes requested: The new guard still misses a production recovery delegation covered by the spec.
Warning
Changes requested · head c9c7bd1 · 1 finding: 1 minor
| Severity | Finding | Where |
|---|---|---|
| minor | F1 Spec contradiction — docs/reference/specs/routing-and-config.md item 33: “start … by hand” is not detected | scripts/user-message-check.mjs:43 |
Full review
F1 — The by hand pattern only recognizes a limited verb list. It misses the production spawn_identity refusal in src/core/dispatch/spawn.ts:299, which says the requester “starts that work by hand.” This lets delegated recovery pass despite item 33. Expand the detector to cover start/starts or all relevant “by hand” recovery forms, and add this exact message as a regression fixture.
2af2e25 to
e4f8037
Compare
There was a problem hiding this comment.
Changes requested: The new ratchet still omits central CLI/chat rendering sources promised by routing-and-config item 33.
Warning
Changes requested · head e4f8037 · 1 finding: 1 minor
| Severity | Finding | Where |
|---|---|---|
| minor | F1 Spec contradiction — docs/reference/specs/routing-and-config.md item 33: CLI chat adapter strings are not scanned | scripts/user-message-check.mjs:56 |
Full review
F1: surfaceFor() claims to cover CLI chat answers, but its allowlist excludes central renderers such as src/core/commandChat.ts, src/core/commandRegistry.ts, src/core/commandSurface.ts, and src/cli.ts. An imperative added to chatErrorLine() or another generic command reply would therefore bypass the ratchet. Include these user-facing command surfaces and add a fixture proving an imperative in the chat adapter fails.
e4f8037 to
db162a2
Compare
There was a problem hiding this comment.
Changes requested: The prior CLI-surface coverage finding is fixed, but the newly discovered CLI imperative is baselined instead of removed.
Warning
Changes requested · head db162a2 · 1 finding: 1 minor
| Severity | Finding | Where |
|---|---|---|
| minor | F2 Spec contradiction — docs/reference/specs/routing-and-config.md item 33: CLI baseline preserves delegated recovery | src/cli.ts:439 |
Full review
F2 (minor, high confidence) — The newly scanned CLI path emits “set SWITCHBOARD_CONFIG … or run from a checkout,” which directly instructs the person. Adding this hit to the baseline lets the check pass while item 33 promises that no user-facing message delegates recovery. Rewrite the error as a system outcome or bug statement rather than baselining it.
19a9cb5 to
739b256
Compare
There was a problem hiding this comment.
LGTM: F2 is fixed: the CLI refusal now reports the stopped command, its baseline entry is gone, and exact regression assertions cover the outcome.
Note
Approved · head 739b256 · no findings
Full review
F2 is resolved: the CLI refusal now states the system outcome, its baseline entry is removed, and exact regression assertions preserve coverage.
There was a problem hiding this comment.
Changes requested: The new user-message ratchet can miss prohibited imperatives assembled from ordinary rendered string forms.
Warning
Changes requested · head 739b256 · 1 finding: 1 minor
| Severity | Finding | Where |
|---|---|---|
| minor | F1 Spec contradiction — docs/reference/specs/routing-and-config.md item 33: fragmented rendered messages bypass the guard | scripts/user-message-check.mjs:129 |
Full review
F1 — Minor, high confidence. The extractor scans each string fragment separately. As a result, "Re-" + "send the request.", a multiline `Run\nthe command again`, and Vue {{ "Run the command again" }} all produce zero hits while rendering prohibited recovery imperatives. Reconstruct static concatenations, preserve whitespace across multiline templates, inspect literal Vue interpolations, and add regression fixtures for these forms.
The unlicensed test-title changes are refactors; their updated assertions preserve the original verification.
Co-Authored-By: coreplane-switchboard[bot] <318072483+coreplane-switchboard[bot]@users.noreply.github.com>
739b256 to
7eab812
Compare
There was a problem hiding this comment.
Changes requested: Two scanner gaps contradict item 33's promised handling of literal composition and typed confirmation offers.
Warning
Changes requested · head 7eab812 · 2 findings: 2 minor
| Severity | Finding | Where |
|---|---|---|
| minor | F1 Spec contradiction — routing-and-config.md item 33: parenthesized literal concatenations evade composition | scripts/user-message-check.mjs:136 |
| minor | F2 Spec contradiction — routing-and-config.md item 33: valid typed confirmation forms are not exempt | scripts/user-message-check.mjs:113 |
Full review
F1 — Minor, high confidence. renderedString() does not unwrap parenthesized operands, so "Re-" + ("send the request") is visited as two harmless fragments and bypasses the guard. Unwrap ParenthesizedExpression and similar transparent TypeScript wrappers before composition, with a regression fixture.
F2 — Minor, high confidence. shapeOf() recognizes ConfirmationOffer only on an enclosing variable declaration. A function returning ConfirmationOffer, an as ConfirmationOffer expression, or a satisfies ConfirmationOffer expression is still a typed confirmation but is incorrectly scanned as a statement. Recognize these contextual type forms and add fixtures for them.
The unlicensed test-guard checks are wording-only retitles; their verification remains intact.
User-facing failures now report system outcomes or explicit bug states instead of assigning recovery to a person. The ratcheted scanner composes split strings across render forms, closing bypasses in Slack, CLI and run-page copy.
Why: #2141 exposed failure copy that pushed pipeline recovery onto people. This closes its last review gap: source fragments now match the rendered message, while stacked #2167 remains the owner of human-answer continuation.
Where to look
Feedback wanted: Judge the interpolation-hole boundary: split tokens must compose, while dynamic noun phrases such as “3 runs in flight” must remain non-imperative.
Risk: This 134-file, +1,119/−485 sweep changes user copy and an auth-gate refusal. Bad matching can block CI or miss delegation; rollback is one commit. Human-answer orchestration was split to #2167; scanner and copy remain together as mutual proof.
Verified: Final-head focused tests, TypeScript, ESLint, Prettier, hygiene, specs and user-message ratchet pass; final-head CI is pending.
Decisions (6)
${count} runinto an imperative, but disappear inside split tokens such asRe-${part}send.Validation (7 criteria)
For agents
Force-with-lease pushed one coherent commit at 7eab812 after rebasing onto origin/main 9f64917 (1.260.3). F1 adds static composition in scripts/user-message-check.mjs and exact fixtures in src/userMessageCheck.test.ts; the baseline file is unchanged. The checker initially exposed noun-phrase false positives such as
${count} run; interpolation markers now remain at whitespace boundaries but are omitted inside split authored tokens. Reproduce withnpx vitest run src/userMessageCheck.test.ts && npm run user-message:check.Requested by @justinhelmer in slack:C0BRRHKFLCB
🤖 Generated with Claude Code