test(FN-783): cover poisoned write-scope metadata
Fusion-Task-Id: FN-783 Co-authored-by: Fusion <noreply@runfusion.ai>
This commit is contained in:
@@ -419,6 +419,96 @@ describe("TaskStore", () => {
|
||||
const paths = await store.parseFileScopeFromPrompt(task.id);
|
||||
expect(paths).toEqual(["packages/dashboard/app/components/TaskDetailModal.tsx"]);
|
||||
});
|
||||
|
||||
it("deduplicates effective write scope while preserving broad mixed-case source globs", async () => {
|
||||
const task = await store.createTask({ description: "Duplicate effective scope" });
|
||||
const dir = join(rootDir, ".fusion", "tasks", task.id);
|
||||
await writeFile(
|
||||
join(dir, "PROMPT.md"),
|
||||
`# ${task.id}: Duplicate effective scope
|
||||
|
||||
## File Scope
|
||||
|
||||
- \`packages/core/**\`
|
||||
- \`packages/core/**\`
|
||||
- \`Packages/MobileApp/**\`
|
||||
- \`Tests/AtlasNotesMobileUITests/**\`
|
||||
`,
|
||||
);
|
||||
|
||||
const paths = await store.parseFileScopeFromPrompt(task.id);
|
||||
expect(paths).toEqual([
|
||||
"packages/core/**",
|
||||
"Packages/MobileApp/**",
|
||||
"Tests/AtlasNotesMobileUITests/**",
|
||||
]);
|
||||
});
|
||||
|
||||
it("excludes poisoned FN-779/FN-756 context-only paths from effective write scope", async () => {
|
||||
const task = await store.createTask({ description: "Poisoned Fusion prompt" });
|
||||
const dir = join(rootDir, ".fusion", "tasks", task.id);
|
||||
await writeFile(
|
||||
join(dir, "PROMPT.md"),
|
||||
`# ${task.id}: Poisoned Fusion prompt
|
||||
|
||||
## File Scope
|
||||
|
||||
Expected touched paths in \`/Users/plarson/src/Fusion-local-runtime\`:
|
||||
|
||||
- \`packages/core/src/store.ts\`
|
||||
- \`packages/engine/src/scheduler.ts\`
|
||||
- \`packages/dashboard/**\`
|
||||
- \`packages/cli/**\`
|
||||
- \`packages/core/src/__tests__/store-parsing.test.ts\`
|
||||
|
||||
Forbidden paths / non-goals:
|
||||
|
||||
- Do not edit Atlas Notes Swift/mobile files: \`project.yml\`, \`AtlasNotes.xcodeproj/**\`, \`Tests/AtlasNotesMobileUITests/**\`, \`Packages/MobileApp/**\`, \`Sources/**\`.
|
||||
- Do not hand-edit \`.fusion/fusion.db\` or \`.fusion/tasks/*/task.json\`.
|
||||
- Generated locks such as \`Packages/*/Package.resolved\` are evidence only.
|
||||
- \`.changeset/*.md\` is required only if published behavior changes.
|
||||
- Operator routes/actions: \`/tasks/:id\`, \`fn_task_update\`, \`review\`, \`merge\`, \`retry\`, \`archive\`.
|
||||
`,
|
||||
);
|
||||
|
||||
const paths = await store.parseFileScopeFromPrompt(task.id);
|
||||
expect(paths).toEqual([
|
||||
"packages/core/src/store.ts",
|
||||
"packages/engine/src/scheduler.ts",
|
||||
"packages/dashboard/**",
|
||||
"packages/cli/**",
|
||||
"packages/core/src/__tests__/store-parsing.test.ts",
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps true Atlas mobile hot-file family writes when declared as implementation scope", async () => {
|
||||
const task = await store.createTask({ description: "Atlas mobile scope" });
|
||||
const dir = join(rootDir, ".fusion", "tasks", task.id);
|
||||
await writeFile(
|
||||
join(dir, "PROMPT.md"),
|
||||
`# ${task.id}: Atlas mobile scope
|
||||
|
||||
## File Scope
|
||||
|
||||
Expected touched paths:
|
||||
|
||||
- \`project.yml\`
|
||||
- \`AtlasNotes.xcodeproj/**\`
|
||||
- \`Tests/AtlasNotesMobileUITests/**\`
|
||||
- \`Packages/MobileApp/**\`
|
||||
- \`Sources/AtlasNotesMobileApp/**\`
|
||||
`,
|
||||
);
|
||||
|
||||
const paths = await store.parseFileScopeFromPrompt(task.id);
|
||||
expect(paths).toEqual([
|
||||
"project.yml",
|
||||
"AtlasNotes.xcodeproj/**",
|
||||
"Tests/AtlasNotesMobileUITests/**",
|
||||
"Packages/MobileApp/**",
|
||||
"Sources/AtlasNotesMobileApp/**",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("FN-5216 File Scope sanitization on copy paths", () => {
|
||||
|
||||
@@ -1813,6 +1813,84 @@ describe("approved triage recovery", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("stamps source metadata from sanitized effective write scope during recovery", async () => {
|
||||
await writeFile(
|
||||
join(rootDir, ".fusion", "tasks", "FN-001", "PROMPT.md"),
|
||||
`# Task: FN-001 - Fix poisoned scope
|
||||
|
||||
**Size:** L
|
||||
|
||||
## File Scope
|
||||
|
||||
Expected touched paths:
|
||||
|
||||
- \`packages/core/src/store.ts\`
|
||||
- \`packages/engine/src/scheduler.ts\`
|
||||
- \`packages/dashboard/**\`
|
||||
|
||||
Forbidden paths / non-goals:
|
||||
|
||||
- Do not edit Atlas files: \`AtlasNotes.xcodeproj/**\`, \`Tests/AtlasNotesMobileUITests/**\`, \`Packages/MobileApp/**\`.
|
||||
- Evidence only: \`.fusion/fusion.db\`, \`.fusion/tasks/*/task.json\`, \`Packages/*/Package.resolved\`.
|
||||
- Conditional only: \`.changeset/*.md\`.
|
||||
`,
|
||||
);
|
||||
|
||||
const store = createMockStore({
|
||||
getSettings: vi.fn().mockResolvedValue({
|
||||
maxConcurrent: 2,
|
||||
maxWorktrees: 4,
|
||||
pollIntervalMs: 10000,
|
||||
groupOverlappingFiles: false,
|
||||
autoMerge: true,
|
||||
requirePlanApproval: false,
|
||||
} as Settings),
|
||||
parseFileScopeFromPrompt: vi.fn().mockResolvedValue([]),
|
||||
});
|
||||
|
||||
const processor = new TriageProcessor(store, rootDir);
|
||||
const recovered = await processor.recoverApprovedTask({
|
||||
id: "FN-001",
|
||||
description: "Recovered triage task",
|
||||
column: "triage",
|
||||
status: "planning",
|
||||
dependencies: [],
|
||||
steps: [],
|
||||
currentStep: 0,
|
||||
log: [{ timestamp: "2026-01-01T00:00:00.000Z", action: "Spec review: APPROVE" }],
|
||||
createdAt: "2026-01-01T00:00:00.000Z",
|
||||
updatedAt: "2026-01-01T00:02:00.000Z",
|
||||
});
|
||||
|
||||
expect(recovered).toBe(true);
|
||||
expect(store.updateTask).toHaveBeenCalledWith(
|
||||
"FN-001",
|
||||
expect.objectContaining({
|
||||
sourceMetadataPatch: expect.objectContaining({
|
||||
fileScope: [
|
||||
"packages/core/src/store.ts",
|
||||
"packages/engine/src/scheduler.ts",
|
||||
"packages/dashboard/**",
|
||||
],
|
||||
intentSignature: expect.objectContaining({
|
||||
filePaths: [
|
||||
"packages/core/src/store.ts",
|
||||
"packages/engine/src/scheduler.ts",
|
||||
],
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
const metadataPatch = (store.updateTask as ReturnType<typeof vi.fn>).mock.calls
|
||||
.map(([, patch]) => patch?.sourceMetadataPatch)
|
||||
.find(Boolean);
|
||||
expect(metadataPatch.fileScope).not.toContain("AtlasNotes.xcodeproj/**");
|
||||
expect(metadataPatch.fileScope).not.toContain(".fusion/fusion.db");
|
||||
expect(metadataPatch.fileScope).not.toContain("Packages/*/Package.resolved");
|
||||
expect(metadataPatch.fileScope).not.toContain(".changeset/*.md");
|
||||
expect(metadataPatch.intentSignature.filePaths).not.toContain("AtlasNotes.xcodeproj/**");
|
||||
});
|
||||
|
||||
it("updates malformed metadata title from prompt heading when task ID matches", async () => {
|
||||
await writeFile(
|
||||
join(rootDir, ".fusion", "tasks", "FN-001", "PROMPT.md"),
|
||||
|
||||
Reference in New Issue
Block a user