From e4a59f726905018a3d898a24572c97fea994507b Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Thu, 9 Jul 2026 10:08:17 -0700 Subject: [PATCH] fix: remove over-firing triage release-authorization gate The triage release-authorization gate (FN-6481/FN-6469) false-flagged any spec that merely mentioned release tooling (scripts/release.mjs, pnpm release) and, because non-user sources made the in-band authorization marker inert, stranded ordinary tasks in awaiting-approval with no exit. - Delete triage-release-authorization.ts + its test and the finalizeApprovedTask parking block; release-class specs now flow through triage normally. - Remove the dashboard approve/reject-plan API guards and UI gating so tasks still carrying the legacy awaitingApprovalReason="release-authorization" hold render as ordinary manual plan-approval holds and can be resolved. - Keep the awaitingApprovalReason field + activity label for backward-compat. - Replace the engine gate with agent instruction (AGENTS.md -> Releasing): agents must never run a release from inside a Fusion task. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../remove-release-authorization-gate.md | 7 + AGENTS.md | 4 +- packages/core/src/types.ts | 22 +- .../dashboard/app/components/TaskCard.tsx | 12 +- .../app/components/TaskDetailModal.tsx | 41 +-- .../components/__tests__/TaskCard.test.tsx | 29 +-- ...askDetailModal.definition-actions.test.tsx | 18 +- .../src/__tests__/routes-github.test.ts | 35 +-- .../routes/register-task-workflow-routes.ts | 31 +-- .../triage-release-authorization.test.ts | 233 ------------------ packages/engine/src/__tests__/triage.test.ts | 86 +------ .../src/triage-release-authorization.ts | 135 ---------- packages/engine/src/triage.ts | 62 +---- 13 files changed, 91 insertions(+), 624 deletions(-) create mode 100644 .changeset/remove-release-authorization-gate.md delete mode 100644 packages/engine/src/__tests__/triage-release-authorization.test.ts delete mode 100644 packages/engine/src/triage-release-authorization.ts diff --git a/.changeset/remove-release-authorization-gate.md b/.changeset/remove-release-authorization-gate.md new file mode 100644 index 0000000000..0976fd085f --- /dev/null +++ b/.changeset/remove-release-authorization-gate.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Tasks are no longer stuck "awaiting release authorization" — the over-firing release gate was removed. +category: fix +dev: Removed the triage release-authorization gate (packages/engine/src/triage-release-authorization.ts + finalizeApprovedTask block) and its dashboard approve/reject-plan guards. It false-flagged specs that merely mentioned release tooling and stranded tasks in awaiting-approval with no in-band exit. Legacy `awaitingApprovalReason: "release-authorization"` rows now render as ordinary manual plan-approval holds. Releases are kept out of Fusion by agent instruction (AGENTS.md → Releasing) instead. diff --git a/AGENTS.md b/AGENTS.md index 0cd79cb424..38418777ea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -67,7 +67,9 @@ A linter (`pnpm check:changesets`) validates this format and runs in the PR-chec ### Releasing -Use only: +**Never run a release from inside a Fusion task.** Do not run `pnpm release`, `changeset publish`, `pnpm publish`, `npm publish`, or cut git version tags as part of any Fusion-dispatched work (triage/executor/reviewer/merger/agent-heartbeat lanes). Releasing is an operator-only action performed by a human outside the task loop. If a task's spec appears to require a release, stop and leave it for a human operator — do not self-authorize or perform the publish. (The former engine "release authorization" gate that parked such tasks was removed because it over-fired on specs that merely *mentioned* release tooling; this instruction replaces it.) + +When a human operator does release, use only: ```bash pnpm release --yes diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 1fc4093034..b505d7d6af 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -2526,19 +2526,15 @@ export interface Task { * `nextRecoveryAt` is still in the future. Cleared alongside `recoveryRetryCount`. */ nextRecoveryAt?: string; /* - * FNXC:PlanApproval 2026-07-04-21:35: - * FN-7559: release authorization (packages/engine/src/triage-release-authorization.ts) - * and the ordinary manual plan-approval gate (packages/core/src/plan-approval.ts, - * resolvePlanApprovalRequired) both park a task with status "awaiting-approval" and - * previously rendered an identical badge/Approve-Plan affordance in the dashboard. - * Project auto-approve-all (planApprovalMode: "auto-approve-all") bypasses ONLY the - * manual gate — release authorization is an independent safety gate it never skips — - * so an operator with auto-approve on could not tell a still-parked release hold from - * a (never-fired) manual hold and reasonably concluded auto-approve was broken. - * Set to "release-authorization" only by the release-authorization gate; the manual - * gate always writes it back to undefined/null so a stale reason from an earlier pass - * never survives past the manual gate's own awaiting-approval. Undefined means either - * no hold or an ordinary manual-approval hold. + * FNXC:ReleaseAuthorizationGate 2026-07-09-00:00: + * DEPRECATED — the triage release-authorization gate that set this field was removed + * (it over-fired on AI-authored specs that merely mention release tooling and stranded + * ordinary tasks in "awaiting-approval" with no in-band exit). No code writes + * "release-authorization" anymore; releases are kept out of Fusion by agent instruction + * (AGENTS.md → "Releasing"), not an engine gate. The field is retained only so existing + * task rows persisted with the legacy value still deserialize; the dashboard now treats + * any such hold as an ordinary manual plan-approval hold (Approve/Reject Plan render + * normally). Undefined means either no hold or a manual-approval hold. */ awaitingApprovalReason?: "release-authorization"; /* diff --git a/packages/dashboard/app/components/TaskCard.tsx b/packages/dashboard/app/components/TaskCard.tsx index b25933b3d1..42f68a0ab0 100644 --- a/packages/dashboard/app/components/TaskCard.tsx +++ b/packages/dashboard/app/components/TaskCard.tsx @@ -1271,14 +1271,12 @@ function TaskCardComponent({ const taskAgeStalenessCopy = getTaskAgeStalenessCopy(task.ageStaleness); const isAwaitingApproval = task.column === "triage" && task.status === "awaiting-approval"; /* - * FNXC:PlanApproval 2026-07-04-21:35: - * FN-7559: release-authorization holds and manual plan-approval holds both use - * status "awaiting-approval" (auto-approve-all intentionally bypasses only the - * manual gate — see FNXC:PlanApproval in types.ts). Distinguish them for the - * operator via the awaitingApprovalReason discriminator instead of showing the - * generic manual-approval badge/label for both. + * FNXC:ReleaseAuthorizationGate 2026-07-09-00:00: + * The triage release-authorization gate was removed; a legacy release-authorization + * hold is now just an ordinary manual plan-approval hold and no longer gets a + * distinct badge. */ - const isReleaseAuthorizationHold = isAwaitingApproval && task.awaitingApprovalReason === "release-authorization"; + const isReleaseAuthorizationHold = false; const isAwaitingInput = task.status === "awaiting-user-input"; const isArchived = task.column === "archived"; const isAgentActive = !globalPaused && !queued && !isFailed && !isPaused && !isStuck && !isAwaitingApproval && !isAwaitingInput && (task.column === "in-progress" || ACTIVE_STATUSES.has(visualStatus as string)); diff --git a/packages/dashboard/app/components/TaskDetailModal.tsx b/packages/dashboard/app/components/TaskDetailModal.tsx index ceb8a9afdd..c60a9c579c 100644 --- a/packages/dashboard/app/components/TaskDetailModal.tsx +++ b/packages/dashboard/app/components/TaskDetailModal.tsx @@ -2668,16 +2668,13 @@ export function TaskDetailContent({ const isTaskPaused = task.paused || task.userPaused; /* - * FNXC:PlanApproval 2026-07-04-21:35: - * FN-7559: release-authorization holds and manual plan-approval holds both - * use status "awaiting-approval" (auto-approve-all intentionally bypasses only - * the manual gate — see FNXC:PlanApproval in types.ts). Gate the manual - * Approve/Reject Plan affordance to genuine manual holds only — clicking - * "Approve Plan" on a release-authorization hold would let a release-class - * spec bypass FN-6481's explicit-marker requirement via a plain button click. + * FNXC:ReleaseAuthorizationGate 2026-07-09-00:00: + * The triage release-authorization gate was removed. Any task still carrying the + * legacy awaitingApprovalReason === "release-authorization" is now treated as an + * ordinary manual plan-approval hold so it shows Approve/Reject Plan and is not + * stranded with no resolvable affordance. */ const isAwaitingApproval = task.column === "triage" && task.status === "awaiting-approval"; - const isReleaseAuthorizationHold = isAwaitingApproval && task.awaitingApprovalReason === "release-authorization"; const handleTogglePause = useCallback(async () => { try { @@ -5667,10 +5664,10 @@ export function TaskDetailContent({ ) : ( <> - {/* Approve/Reject Plan buttons — only for genuine manual plan-approval - holds (FN-7559: a release-authorization hold shares the same - status but must never be resolvable via this plain button click). */} - {isAwaitingApproval && !isReleaseAuthorizationHold && workingTask.prompt && ( + {/* Approve/Reject Plan buttons for manual plan-approval holds. + FNXC:ReleaseAuthorizationGate 2026-07-09-00:00: the release-authorization + gate was removed, so legacy release-authorization holds render these too. */} + {isAwaitingApproval && workingTask.prompt && ( <>