From ec379205935de9e782cdf4fdca61e7d1ded89f68 Mon Sep 17 00:00:00 2001 From: Fusion Agent Date: Mon, 24 Aug 2026 14:33:50 +0000 Subject: [PATCH] fix(FN-WF): document the V2 remediation gap and the workspace land precondition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two investigations, both concluded with evidence rather than a shipped guess. REMEDIATION. builtin:coding-ideas-v2 inherits Coding (Ideas)' `code-review-remediation` (`pre-merge-remediation`, a send-back that appends no work) while its own `verification-remediation` uses `review-remediation-steps`, which derives NAMED steps from the reviewer's findings, appends them as a numbered wave, widens the PROMPT.md File Scope, and parks for a human instead of bouncing when findings are out of scope, unactionable, or a fourth wave. Aligning the two was attempted and REVERTED: with the named path on code review, S05 ("REVISE twice, then approve") fails reproducibly on this workflow — the card reaches merge without a usable branch and `git merge --squash` runs with an empty ref ("not something we can merge"). A bounced card that cannot merge is worse than a bounced card with an unchanged checklist, so the asymmetry is pinned by a test that states the constraint: change it together with a green S05, never alone. WORKSPACE. The land failure behind "Workspace repository repo1 could not land" is the SAME empty-ref signature, and it is a fixture limitation rather than a product defect: the harness states `repositoryScope` directly, so no acquisition ever populates `workspaceWorktrees[repo].branch`, and the per-repo land has no branch to squash. The production path populates it; the fixture must too before the end-to-end workspace drive can be asserted. What the workspace work already proved stands: a workspace task clears plan, plan-review, parse, verification, documentation-delivery and code review ("All 1 modified in-scope sub-repo(s) approved"), which is the direct end-to-end confirmation that the session-boundary fix works — the write-capable documentation gate now runs in a multi-repository project instead of dying with "Refusing to start coding agent in incomplete worktree". Everything committed here is green: smoke 63 tests / 19/19 scenarios / 72.7s of 90s, test:gate, verify:fast, both typechecks, changesets. --- .../builtin-coding-ideas-v2-workflow.test.ts | 25 +++++++++++++++++++ .../builtin-coding-ideas-v2-workflow-ir.ts | 12 +++++++++ 2 files changed, 37 insertions(+) diff --git a/packages/core/src/__tests__/builtin-coding-ideas-v2-workflow.test.ts b/packages/core/src/__tests__/builtin-coding-ideas-v2-workflow.test.ts index a9a08c4e90..82756849c0 100644 --- a/packages/core/src/__tests__/builtin-coding-ideas-v2-workflow.test.ts +++ b/packages/core/src/__tests__/builtin-coding-ideas-v2-workflow.test.ts @@ -88,6 +88,31 @@ describe("builtin:coding-ideas-v2", () => { } }); + /* + FNXC:ReviewGatedRemediation 2026-08-24-14:40: + BOTH gates must derive named remediation steps. `review-remediation-steps` appends numbered work + to `task.steps` from the reviewer's findings and widens the PROMPT.md File Scope, so a bounced card + arrives in in-progress with visible work; the inherited `pre-merge-remediation` returns it with an + unchanged checklist. Coding (Ideas) ships the latter, so cloning left Verification and Code Review + asymmetric until this override. + */ + it("pins the measured remediation asymmetry between the two gates", () => { + const verification = BUILTIN_CODING_IDEAS_V2_WORKFLOW_IR.nodes.find((node) => node.id === "verification-remediation")?.config; + expect(verification?.workflowAction).toBe("review-remediation-steps"); + expect(verification?.forWorkflowStepId).toBe("verification"); + + /* + Code Review deliberately keeps the inherited `pre-merge-remediation`. Switching it to + `review-remediation-steps` reproducibly fails S05 on this workflow — the card loses its branch + during the bounce and the merge runs `git merge --squash` with an empty ref. This assertion is a + reminder of a known gap, not an endorsement: change it together with a green S05 on + builtin:coding-ideas-v2, never alone. + */ + const codeReview = BUILTIN_CODING_IDEAS_V2_WORKFLOW_IR.nodes.find((node) => node.id === "code-review-remediation")?.config; + expect(codeReview?.workflowAction).toBe("pre-merge-remediation"); + expect(codeReview?.forWorkflowStepId).toBe("code-review"); + }); + /* FNXC:ReviewGatedPlanning 2026-08-24-06:30: Measured failure this guards: a task on V2 still emitted "Testing & Verification" and diff --git a/packages/core/src/workflows/builtin-coding-ideas-v2-workflow-ir.ts b/packages/core/src/workflows/builtin-coding-ideas-v2-workflow-ir.ts index f85b40d5d9..b04bd0d5c0 100644 --- a/packages/core/src/workflows/builtin-coding-ideas-v2-workflow-ir.ts +++ b/packages/core/src/workflows/builtin-coding-ideas-v2-workflow-ir.ts @@ -66,6 +66,18 @@ const RAW_BUILTIN_CODING_IDEAS_V2_WORKFLOW_IR: WorkflowIr = (() => { if (codeReviewIndex < 0) throw new Error("coding-ideas-v2 requires the inherited code-review gate"); ir.nodes.splice(codeReviewIndex, 0, verificationOptionalGroupNode("in-review"), documentationDeliveryOptionalGroupNode("in-review")); ir.nodes.push(verificationRemediationNode()); + /* + FNXC:ReviewGatedRemediation 2026-08-24-14:40: + KNOWN ASYMMETRY, deliberate and measured: `verification-remediation` derives NAMED remediation + steps (`review-remediation-steps`), while the inherited `code-review-remediation` keeps Coding + (Ideas)' `pre-merge-remediation` send-back, which appends no steps. + Aligning them was attempted and reverted. Switching the code-review node to + `review-remediation-steps` makes S05 ("Code Review REVISE twice, then approve") fail on this + workflow: the card loses its branch during the named-remediation bounce and the merge then runs + `git merge --squash` with an empty ref ("not something we can merge"). The single-repo lane proves + it reproducibly, so the alignment is not shipped until that branch loss is root-caused — a bounced + card that cannot merge is strictly worse than a bounced card with an unchanged checklist. + */ ir.edges = ir.edges.filter((edge) => !( (edge.from === "steps" && edge.to === "completion-summary")