FN-5764: expose mission assertion backfill through API and CLI

Expose mission assertion backfill controls and run paths across engine, API, and CLI surfaces.

- add mission assertion backfill gating classification and reliability coverage for assertion-linked validation recovery
- add dashboard mission routes plus legacy API coverage and e2e checks for running assertion backfill
- expose new extension tool support and update Fusion skill docs/capability references
- add a changeset for @runfusion/fusion and refresh mission documentation

Files changed:
 .changeset/fn-5764-mission-backfill-assertions.md  |   9 ++
 docs/missions-completion-contract.md               |   7 +-
 docs/missions.md                                   |   7 +-
 packages/cli/skill/fusion/SKILL.md                 |   2 +-
 .../cli/skill/fusion/references/extension-tools.md |   9 ++
 .../skill/fusion/references/fusion-capabilities.md |   1 +
 packages/cli/src/__tests__/extension.test.ts       |  41 +++++++
 packages/cli/src/extension.ts                      |  43 +++++++
 .../dashboard/app/__tests__/api-missions.test.ts   |  44 +++++++
 packages/dashboard/app/api/legacy.ts               |  34 ++++++
 .../dashboard/src/__tests__/mission-e2e.test.ts    | 127 +++++++++++++++++++++
 packages/dashboard/src/mission-routes.ts           |  29 +++++
 .../mission-validation-trigger-gap.test.ts         |  66 +++++++++++
 .../workflow-step-readonly-allowlist.test.ts       |   1 +
 packages/engine/src/gating-classifications.ts      |   1 +
 15 files changed, 418 insertions(+), 3 deletions(-)

Fusion-Task-Id: FN-5764

Fusion-Task-Lineage: b16123c2-7cfe-4b17-a899-4e30044a7a49
This commit is contained in:
gsxdsm
2026-05-31 00:54:07 -07:00
parent ec92434f4c
commit acad46cc10
15 changed files with 418 additions and 3 deletions

View File

@@ -29,7 +29,7 @@ Mission → Milestone → Slice → Feature → Task
<!-- BEGIN: tool-categories (auto-generated by scripts/sync-fusion-skill-tools.mjs — do not edit by hand) -->
- **Task tools** — `fn_task_create`, `fn_task_update`, `fn_task_list`, `fn_task_show`, `fn_task_attach`, `fn_task_pause`, `fn_task_unpause`, `fn_task_retry`, `fn_task_duplicate`, `fn_task_refine`, `fn_task_archive`, `fn_task_unarchive`, `fn_task_delete`, `fn_task_plan`
- **GitHub tools** — `fn_task_import_github`, `fn_task_import_github_issue`, `fn_task_browse_github_issues`
- **Mission tools** — `fn_mission_create`, `fn_mission_list`, `fn_mission_show`, `fn_mission_delete`, `fn_mission_update`, `fn_milestone_add`, `fn_slice_add`, `fn_feature_add`, `fn_feature_delete`, `fn_slice_delete`, `fn_milestone_delete`, `fn_slice_activate`, `fn_feature_link_task`, `fn_feature_update`, `fn_milestone_update`
- **Mission tools** — `fn_mission_create`, `fn_mission_list`, `fn_mission_show`, `fn_mission_backfill_assertions`, `fn_mission_delete`, `fn_mission_update`, `fn_milestone_add`, `fn_slice_add`, `fn_feature_add`, `fn_feature_delete`, `fn_slice_delete`, `fn_milestone_delete`, `fn_slice_activate`, `fn_feature_link_task`, `fn_feature_update`, `fn_milestone_update`
- **Goal tools** — `fn_goal_list`, `fn_goal_create`, `fn_goal_archive`, `fn_goal_show`
- **Agent tools** — `fn_agent_stop`, `fn_agent_start`, `fn_agent_create`, `fn_agent_delete`, `fn_list_agents`, `fn_delegate_task`, `fn_agent_show`, `fn_agent_org_chart`
- **Skills tools** — `fn_skills_search`, `fn_skills_install`

View File

@@ -196,6 +196,15 @@ Show mission details with full hierarchy: milestones → slices → features.
|-----------|------|----------|-------------|
| `id` | string | ✓ | Mission ID (e.g., M-001) |
### fn_mission_backfill_assertions
Backfill mission assertions by deriving and linking one store-managed assertion for each feature without linked assertions. Supports dry-run mode.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `missionId` | string | — | Mission ID to scope backfill to (e.g., M-001) |
| `dryRun` | boolean | — | When true, preview repairs without writing changes (default: true) |
### fn_mission_delete
Delete a mission and all its milestones, slices, and features. Cannot be undone.

View File

@@ -48,6 +48,7 @@ All skill/extension tool invocations in this catalog use the public `fn_*` names
| `fn_goal_archive` | Archive a goal by ID. |
| `fn_goal_show` | Show full details for a single goal by ID. |
| `fn_mission_show` | Show mission details with full hierarchy: milestones → slices → features. |
| `fn_mission_backfill_assertions` | Backfill mission assertions by deriving and linking one store-managed assertion for each feature without linked assertions. Supports dry-run mode. |
| `fn_mission_delete` | Delete a mission and all its milestones, slices, and features. Cannot be undone. |
| `fn_mission_update` | Update an existing mission's title or description. Partial patches leave untouched fields intact. |
| `fn_milestone_add` | Add a milestone to a mission. Milestones represent phases of work. |

View File

@@ -215,6 +215,7 @@ describe.skipIf(!SHOULD_RUN_LEGACY_EXTENSION_INTEGRATION)("fn pi extension (lega
"fn_mission_create",
"fn_mission_list",
"fn_mission_show",
"fn_mission_backfill_assertions",
"fn_mission_delete",
"fn_mission_update",
"fn_milestone_add",
@@ -1215,6 +1216,46 @@ describe.skipIf(!SHOULD_RUN_LEGACY_EXTENSION_INTEGRATION)("fn pi extension (lega
});
});
describe("fn_mission_backfill_assertions", () => {
it("supports dry-run and apply execution", async () => {
const missionTool = api.tools.get("fn_mission_create")!;
const backfillTool = api.tools.get("fn_mission_backfill_assertions")!;
const mission = await missionTool.execute("m1", { title: "Backfill Mission" }, undefined, undefined, makeCtx(tmpDir));
const store = new TaskStore(tmpDir);
await store.init();
const missionStore = store.getMissionStore();
const milestone = missionStore.addMilestone(mission.details.missionId, { title: "Milestone" });
const slice = missionStore.addSlice(milestone.id, { title: "Slice" });
const feature = missionStore.addFeature(slice.id, {
title: "Feature needing assertion",
acceptanceCriteria: "must be true",
});
expect(missionStore.listAssertionsForFeature(feature.id)).toHaveLength(0);
const dryRun = await backfillTool.execute(
"bf-1",
{ missionId: mission.details.missionId, dryRun: true },
undefined,
undefined,
makeCtx(tmpDir),
);
expect(dryRun.details.repaired.length).toBe(1);
expect(missionStore.listAssertionsForFeature(feature.id)).toHaveLength(0);
const apply = await backfillTool.execute(
"bf-2",
{ missionId: mission.details.missionId, dryRun: false },
undefined,
undefined,
makeCtx(tmpDir),
);
expect(apply.details.repaired.length).toBe(1);
expect(missionStore.listAssertionsForFeature(feature.id)).toHaveLength(1);
expect(apply.content[0].text).toContain("Backfill apply complete");
});
});
describe("fn_mission_delete", () => {
it("deletes mission and confirms", async () => {
// Create mission

View File

@@ -2674,6 +2674,49 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── fn_mission_backfill_assertions ─────────────────────────────
pi.registerTool({
name: "fn_mission_backfill_assertions",
label: "fn: Backfill Mission Assertions",
description:
"Backfill mission assertions by deriving and linking one store-managed assertion for each feature without linked assertions. Supports dry-run mode.",
promptSnippet: "Backfill mission assertions for unlinked features",
promptGuidelines: [
"Use dryRun=true first to inspect proposed repairs before applying",
"Scopes to a single mission when missionId is provided; otherwise scans all missions",
"Creates one store-managed assertion per unlinked feature and links it",
],
parameters: Type.Object({
missionId: Type.Optional(Type.String({ description: "Mission ID to scope backfill to (e.g., M-001)" })),
dryRun: Type.Optional(Type.Boolean({ description: "When true, preview repairs without writing changes (default: true)" })),
}),
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
const store = await getStore(ctx.cwd);
const missionStore = store.getMissionStore();
const report = missionStore.backfillFeatureAssertions({
missionId: params.missionId,
dryRun: params.dryRun ?? true,
});
const scopeLabel = params.missionId ? `mission ${params.missionId}` : "all missions";
const modeLabel = (params.dryRun ?? true) ? "dry-run" : "apply";
const lines = [
`Backfill ${modeLabel} complete for ${scopeLabel}.`,
`Scanned: ${report.scanned}`,
`Already linked: ${report.alreadyLinked}`,
`Repaired: ${report.repaired.length}`,
`Skipped errors: ${report.skippedErrors.length}`,
];
return {
content: [{ type: "text", text: lines.join("\n") }],
details: report,
};
},
});
// ── fn_mission_delete ───────────────────────────────────────────
pi.registerTool({