Goal action errors must carry a concrete next step (recovery affordance), not just a refusal#1791
Draft
interactor-pm-service[bot] wants to merge 6 commits into
Conversation
…er (G#..., foundation) Give every engine goal-action refusal a machine-readable, render-ready structure so the UI shows plain-language guidance + a concrete next action without string-matching the human message (G#371 binding rules). - New PURE module src/lib/engine/goal-action-refusal.ts: GoalActionRefusal type (kind/title/message/recovery[]/resolvesAt?/actor?/occurredAt?) + GoalRecoveryAction, modeled on machine-blocking-reason.ts's BlockingReason. One dependency-free builder per kind (already_terminal, deploy_gate_failed, github_unverifiable/rate_limited, open_pause, no_credential/no_connection, merge_conflict, pr_closed/pr_draft/pr_not_ready). - Carry it: optional readonly `refusal?: GoalActionRefusal` on EngineTransitionError, EngineValidationError, EngineDependencyError constructors (mirrors how InitiativeCollisionError carries initiative/drivers). Param is optional — no existing call site breaks. Coexists with the flat code/detail contract. - Mapper: engineErrorToHttp surfaces `refusal` in the 409/422/502 bodies when present, via refusalFields; EngineHttpError.body gains `refusal?`. error/message unchanged (backward compatible). - Unit tests for the builders (two-binding-rules well-formedness) and the mapper. Scope: type + carrier + mapper + tests only — throw sites (task 2) and UI (task 3) untouched. No agent-visible tools/data/workflows change, so no PM Agent config update is needed (AI Agent Maintenance rule). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…row site (G#371, task 2) Attach a fully-populated, render-ready `GoalActionRefusal` (task 1's type/builders) to every goal-action refusal in goal-review.ts, honoring the two BINDING amendment rules: NON-TECHNICAL copy (never a raw `GET pull #NNNN failed: 403`) and ALWAYS a concrete next action. The refusal rides the thrown EngineTransition/Validation/Dependency error and is surfaced verbatim by `engineErrorToHttp` (task 1) — routes already delegate to it, so no v1 (daemon) / v2 (browser) route change is needed. Enriched sites (all goal-review.ts): - already-terminal accept + closeGoal (accepted/cancelled) + requestChanges + requestGoalReReview: shared `terminalRefusal` helper derives WHO/WHEN from the goal's lifecycle trail (non-technical actor label), links the MERGED PR, and self-heals the stale card via a `refetch_card` recovery. Extended requestChanges/requestGoalReReview selects to load prNumber/prUrl/prState so their refusals can link the PR too. - deploy-gate failures in assertGoalMergeable (CI-not-passed, unresolved must-fix, unreviewed-HEAD): all carry a `deploy_gate_failed` refusal with a "Send back to fleet" action + a `<prUrl>/checks` link; the raw CI enum is translated to plain words. - unverifiable / rate-limited GitHub at accept (single + multi-repo): TRANSLATE — the raw error (`String(e)`, status codes) now stays in the server log ONLY, never in the message or refusal. Rate-limit → `github_rate_limited` with a self-enabling Retry; resolvesAt from the circuit-breaker `openedUntil`, falling back to the ~5-min base cooldown. - open child pause: `open_pause` refusal deep-linking the specific pause. - no-credential / no-operator-identity / no-connection / pr-closed / pr-draft / merge-conflict: the matching task-1 builder (integrations link, refetch, resolve-conflict). Tests: a new block asserts each kind's refusal fields — self-heal recovery, PR/checks links, derived actor/when, and (rate-limit) that the copy contains no `403`. Full engine suite + typecheck green. No agent-visible tools/data/workflows change, so no PM Agent config update is needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Refusal at every throw site (G#371, task 2 rework) The prior task-2 commit enriched only the single-repo accept path; the multi-repo path (mergeGoalRepoTarget / acceptGoalMultiRepo) still threw bare EngineTransitionError/EngineValidationError with no stable code and no render-ready refusal, and leaked GitHub's raw detail / `String(e)` (HTTP status) into user-facing text. This closes those sites to the same bar as single-repo: - no-credential / no-operator / no-connection (:788/:794/:796): carry the no_github_connection code + noCredential/noConnection refusal. - no-PR-found (:814): no_pr_found code + a not-ready refusal. - verify catch (:825): TRANSLATE — classify rate-limit vs generic (classifyGithubError), attach github_rate_limited/github_unverifiable with a resetAt derived from the circuit-breaker (or a ~5-min fallback); raw error in the server log ONLY. - pr closed / draft (:833/:834): pr_closed / pr_draft code + refusal. - merge catch (:866): plain-language messages (raw body/status logged, never shown), permission-denied → mergePermissionDenied code + noCredential refusal, conflict → reuse goalMergeConflictReason + mergeConflict code + mergeConflict refusal (resolve-conflict route), generic fallback → a Retry refusal (no `String(e)` leak). - aggregate multi-repo throw (:923): propagates the FIRST blocked repo's code + refusal so the user still gets ONE concrete next step; summary lists every blocked repo (all messages already translated). Also attaches the no_github_connection code to the single-repo no-credential/no-operator sites that previously passed `undefined` meta. Adds 10 multi-repo refusal-field unit tests (each kind's code + refusal.kind + non-technical copy, incl. that the rate-limit and merge messages carry no '403'). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… task #1456) Deliverable 2 + 3 of "goal action errors must carry a concrete next step": lock in that a stale Accept on a goal the server has ALREADY moved to a terminal state self-heals the card instead of stranding the user, and that the kanban Accept honors the single-source-of-truth `actionable` flag — closing the loop the server refusal contract (#1445) and the cross-surface refusal renderer (#1446/#1447) opened. - goals-kanban-view.tsx: the Queue `ready` next-action now gates the Accept on `resolveAction.actionable`, not the `kind` label alone. `deriveGoalResolveAction` always marks a terminal/underivable goal `actionable:false`, so this can never render a live Accept for one; a stale card whose server state already moved to accepted still derives `ready` from its stale row, shows Accept once, and the refused click self-heals (acceptGoal → goalRefusalToast → applyEngineChange surgical refetch) so the row re-derives to the non-clickable terminal state. Behaviour-preserving today, regression-proof going forward. - goals-kanban-view.test.tsx: two tests for the kanban surface (the detail-page equivalent already exists from #1447) — a 409 goal_already_terminal on Accept refetches the row and shows NO bare error toast (and no false success), and after the refetch converges the row to accepted the Accept re-derives to non-clickable with no lingering error. No PM Agent config change — UI-only; no new agent-visible tool, entity, or workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… Fix CI, move renderer to components/engine (G#371) Close the last bare-string goal-action failure paths and give the shared refusal renderer a home outside the goals route group so shared components can use it. - Move the client refusal renderer + its tests from the goals route dir to src/components/engine/goal-action-refusal.tsx (the location the task + CLAUDE.md Heal-Before-Add rules name). Its only new consumer, goal-cancel-dialog.tsx, lives in components/engine, so a shared renderer no longer imports from deep inside a route group. Updated the 3 existing importers (kanban / detail / list) to the @/components/engine alias; no behavior change. - goal-cancel-dialog.tsx: the goal `close`/cancel refusal now renders via goalRefusalToast(parseGoalRefusal(res)) — plain-language, recovery-carrying, and self-healing (a stale already-terminal cancel refreshes the parent via onCancelled instead of stranding the user). The bare toast.error(body?.message) is gone. - engine-goal-detail-client.tsx handleFixCi: the /fix-ci refusal was the one detail goal-action still on toast.error(j?.message ?? ...). Rewired to goalRefusalToast with onRefresh (converge a stale card) AND onRetry (re-invoke Fix CI) so the github_rate_limited case carries a real Retry, per the retry-recovery contract. - Tests: new goal-cancel-dialog.test.tsx (mapped refusal → plain-language + action, stale self-heal, legacy code-less fallback) and a detail Fix CI refusal test (rate-limited → structured toast, no raw "failed: 403" string, carries Retry). Backward-compat preserved: non-review mutations (priority, task-queue routing, auto-deliver toggle, delete) keep their plain fallback message — they aren't part of the GoalActionRefusal contract (rule 3). No PM Agent config change — UI-only presentation; no agent-visible tool, entity, or workflow changed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Add (G#371) Enrich the 'Unblock-the-User UX — Heal Before Add' standing rules so the goal-action refusal surface is named as the reference implementation of the two binding rules: the server carrier src/lib/engine/goal-action-refusal.ts and the goal cards (goals kanban + detail) join the already-cited refusal-codes, http-errors, and client renderer — set alongside the fleet's machine-blocking-reason.tsx as the same code-keyed, plain-language, remediation-carrying pattern for a different surface. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Integration PR for this goal — its tasks commit onto this branch (one PR per goal). Opened automatically by the engine on first push; left as a draft until the goal completes.