FN-8009: preserve approved plans through prompt hygiene
Keep manually approved plans idempotent when deterministic prompt hygiene is applied. - Document normalized fingerprint comparison at the approval gate - Cover approval reuse after Original Description and Frontend UX injection Files changed: packages/engine/src/__tests__/triage.test.ts | 37 ++++++++++++++++++++++++++++ packages/engine/src/triage.ts | 8 ++++++ 2 files changed, 45 insertions(+) Fusion-Task-Id: FN-8009 Fusion-Task-Lineage: 8474bc13-61e0-421a-8e99-080f99382285 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
@@ -3206,6 +3206,43 @@ describe("requirePlanApproval setting", () => {
|
||||
);
|
||||
});
|
||||
|
||||
/*
|
||||
* FNXC:PlanApproval 2026-07-15-21:10:
|
||||
* FN-8009 requires the as-approved comparison to cover both deterministic hygiene
|
||||
* mutations. A legacy operator-approved plan may lack both its original request and
|
||||
* the frontend checklist; these injections must not turn an unchanged plan into a
|
||||
* new manual approval request.
|
||||
*/
|
||||
it("auto-approves a pre-hygiene plan after Original Description and Frontend UX injection", async () => {
|
||||
const frontendPlan = "# Task: FN-FRONTEND-HYGIENE - Frontend plan\n\n## Mission\n\nUpdate the dashboard.\n\n## File Scope\n\n- `packages/dashboard/app/TaskCard.tsx`\n";
|
||||
const task = createTriageTask({
|
||||
id: "FN-FRONTEND-HYGIENE",
|
||||
description: "The original dashboard request is absent from this planner output.",
|
||||
status: "planning",
|
||||
approvedPlanFingerprint: computePlanApprovalFingerprint(frontendPlan),
|
||||
} as Partial<Task>);
|
||||
const store = createMockStore({
|
||||
getTask: vi.fn().mockResolvedValue(task),
|
||||
} as Partial<TaskStore>);
|
||||
await mkdir(join(rootDir, ".fusion", "tasks", task.id), { recursive: true });
|
||||
await writeFile(join(rootDir, ".fusion", "tasks", task.id, "PROMPT.md"), frontendPlan);
|
||||
const processor = new TriageProcessor(store, rootDir);
|
||||
|
||||
await (processor as unknown as {
|
||||
finalizeApprovedTask(task: Task, writtenInput: string, settings: Settings): Promise<void>;
|
||||
}).finalizeApprovedTask(
|
||||
task,
|
||||
frontendPlan,
|
||||
{ requirePlanApproval: true, planApprovalMode: "require-all" } as Settings,
|
||||
);
|
||||
|
||||
expect(store.moveTask).toHaveBeenCalledWith(task.id, "todo");
|
||||
expect(store.updateTask).not.toHaveBeenCalledWith(task.id, expect.objectContaining({ status: "awaiting-approval" }));
|
||||
const persisted = readFileSync(join(rootDir, ".fusion", "tasks", task.id, "PROMPT.md"), "utf8");
|
||||
expect(persisted).toContain("## Original Description");
|
||||
expect(persisted).toContain("## Frontend UX Criteria");
|
||||
});
|
||||
|
||||
it("still re-asks approval for a CHANGED plan when the prior approval predates prompt hygiene", async () => {
|
||||
// The safety edge: legacy tolerance must not approve a plan the operator never saw.
|
||||
const legacyFingerprint = computePlanApprovalFingerprint(planText);
|
||||
|
||||
@@ -2951,6 +2951,14 @@ export class TriageProcessor {
|
||||
* already made their independent decisions, so it never weakens either of those gates
|
||||
* or auto-approve-all (which never reaches this branch at all).
|
||||
*/
|
||||
/*
|
||||
* FNXC:PlanApproval 2026-07-15-21:05:
|
||||
* FN-7569 / FN-8009 — compare the normalized as-approved fingerprint after deterministic
|
||||
* Original Description or Frontend UX hygiene. approve-plan fingerprints the on-disk
|
||||
* PROMPT.md, while recovery can receive pre-injection text; the shared hasher removes only
|
||||
* those generated sections so an unchanged plan does not re-park. A genuinely changed plan
|
||||
* still produces a different fingerprint and requires approval.
|
||||
*/
|
||||
const priorFingerprint = latestTransitionTask?.approvedPlanFingerprint ?? task.approvedPlanFingerprint;
|
||||
// FNXC:PlanApproval 2026-07-15-20:45: The shared hasher strips deterministic
|
||||
// Original Description / Frontend UX hygiene, so approve-plan's on-disk fingerprint and
|
||||
|
||||
Reference in New Issue
Block a user