Skip to content

fix(core): keep recovery off the person's plate - #2168

Merged
justinhelmer merged 1 commit into
mainfrom
plan/fix-issue-2141-a-user-fa-dd8be2/u1
Sep 22, 2026
Merged

justinhelmer merged 1 commit into
mainfrom
plan/fix-issue-2141-a-user-fa-dd8be2/u1

Conversation

@coreplane-switchboard

@coreplane-switchboard coreplane-switchboard Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

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

  1. User-message contract Defines allowed message shapes, composite-string scanning, the ratcheted baseline and exact proof bindings.
  2. Delegated-recovery signatures Matches “by hand” and clause-boundary recovery imperatives without a command-prefix or verb allowlist. ⚠ A false negative delegates recovery; a false positive blocks CI.
  3. Rendered-string composition Composes literal concatenations, split templates and joined arrays before scanning one rendered message. ⚠ Interpolation holes must not turn noun phrases into imperatives.
  4. Composition regressions Pins all three bypass forms and a composed non-imperative, including a dynamic run-count phrase.
  5. Missing-config outcome Reports that the command stopped and explains config-path resolution without directing manual recovery.
  6. Boundary refusal outcomes Centralizes the unchanged system state reported after each profile boundary refusal. ⚠ This wording sits on the profile authorization gate.
  7. Budget-ending fact order Names unrecoverable budget gaps before established tree facts so answers close on measured workspace state.

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)
  • Scan central renderers explicitly. A prefix-only scan covered command implementations but missed generic adapters and renderers. An explicit file set makes CLI and chat coverage reviewable while the baseline exposes existing copy immediately.
  • Retire the CLI baseline hit. Baselining “run from a checkout” contradicted the invariant. The refusal now states that the command stopped, names config-path selection and reduces the ratchet from nine hits to eight.
  • Treat “by hand” as delegation by itself. A verb allowlist missed “starts that work by hand” and would keep missing new verbs. Matching the phrase is deliberately conservative because recovery should never be assigned to the person.
  • Require source-typed exceptions. Question punctuation and a property named question are not type evidence. Only a ConfirmationOffer annotation or kind: question discriminator proves an allowed interaction shape.
  • Compose renderer string forms. Scanning fragments separately missed concatenations, templates and joined arrays. Composite expressions emit once; interpolation markers remain at word boundaries to avoid turning ${count} run into an imperative, but disappear inside split tokens such as Re-${part}send.
  • Close budget endings on measured facts. Appending the bug after tree facts broke the harness conversation contract. The gap now precedes facts so counts and workspace fate remain the final established state.
Validation (7 criteria)
Criterion Proof
Composite renderer forms cannot bypass the recovery scanner. npx vitest run src/userMessageCheck.test.ts — 13/13 passed at 7eab812, including concatenation, template, joined-array and non-imperative fixtures.
The expanded scan still matches its shrink-only baseline. npm run user-message:check — passed at 7eab812; 6 files and 8 existing hits remain baselined, with no baseline change.
The root TypeScript project remains valid after rebasing onto 1.260.3. NODE_OPTIONS=--max-old-space-size=6144 npx tsc --noEmit -p tsconfig.json — passed at 7eab812.
Changed code and docs match repository style. npx prettier --check on the three final fix files and npx eslint on the checker and test — passed at 7eab812.
Spec proofs and public-hygiene records remain current. npm run hygiene:check and npm run specs:check — passed at 7eab812; 51 specs and 5,680 proof references checked.
The broader changed-test set passed before this final review fix. npx vitest run with 47 changed shard-3 files — 2,448/2,448 passed at prior head 739b256.
Repository CI passed before this final review fix; the new head awaits CI. GitHub checks at 739b256: 28 success, 1 optional docs deploy skipped. Final head 7eab812 was just pushed.
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 with npx vitest run src/userMessageCheck.test.ts && npm run user-message:check.
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 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.

@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 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.

@coreplane-switchboard
coreplane-switchboard Bot force-pushed the plan/fix-issue-2141-a-user-fa-dd8be2/u1 branch 4 times, most recently from f46a42b to b6d6967 Compare September 21, 2026 22:49

@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 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.

@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 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.

@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.

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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Auto-approved: coreplane-switchboard[bot] reviewed this PR and posted an LGTM verdict (see its review). This repository opted in through its REVIEW_BOT_LOGIN and REVIEW_BOT_ID variables.

@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 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.

@coreplane-switchboard
coreplane-switchboard Bot force-pushed the plan/fix-issue-2141-a-user-fa-dd8be2/u1 branch from a8f227a to 6c9e991 Compare September 22, 2026 00:06

@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 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.

@coreplane-switchboard
coreplane-switchboard Bot force-pushed the plan/fix-issue-2141-a-user-fa-dd8be2/u1 branch from 6c9e991 to 80861a5 Compare September 22, 2026 00:27

@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.

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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Auto-approved: coreplane-switchboard[bot] reviewed this PR and posted an LGTM verdict (see its review). This repository opted in through its REVIEW_BOT_LOGIN and REVIEW_BOT_ID variables.

@coreplane-switchboard
coreplane-switchboard Bot force-pushed the plan/fix-issue-2141-a-user-fa-dd8be2/u1 branch from 80861a5 to c9c7bd1 Compare September 22, 2026 00:43

@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.

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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Auto-approved: coreplane-switchboard[bot] reviewed this PR and posted an LGTM verdict (see its review). This repository opted in through its REVIEW_BOT_LOGIN and REVIEW_BOT_ID variables.

@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 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.

@coreplane-switchboard
coreplane-switchboard Bot force-pushed the plan/fix-issue-2141-a-user-fa-dd8be2/u1 branch 2 times, most recently from 2af2e25 to e4f8037 Compare September 22, 2026 01:11

@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 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.

@coreplane-switchboard
coreplane-switchboard Bot force-pushed the plan/fix-issue-2141-a-user-fa-dd8be2/u1 branch from e4f8037 to db162a2 Compare September 22, 2026 01:35

@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 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.

@coreplane-switchboard
coreplane-switchboard Bot force-pushed the plan/fix-issue-2141-a-user-fa-dd8be2/u1 branch 2 times, most recently from 19a9cb5 to 739b256 Compare September 22, 2026 01:49

@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.

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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Auto-approved: coreplane-switchboard[bot] reviewed this PR and posted an LGTM verdict (see its review). This repository opted in through its REVIEW_BOT_LOGIN and REVIEW_BOT_ID variables.

@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 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>
@coreplane-switchboard
coreplane-switchboard Bot force-pushed the plan/fix-issue-2141-a-user-fa-dd8be2/u1 branch from 739b256 to 7eab812 Compare September 22, 2026 05:33
@justinhelmer
justinhelmer merged commit b43af4a into main Sep 22, 2026
29 checks passed
@justinhelmer
justinhelmer deleted the plan/fix-issue-2141-a-user-fa-dd8be2/u1 branch September 22, 2026 05:51

@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: 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.

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