Remove complex-task splitting and parent-deletion behavior across core, dashboard, and engine. - Remove subtask splitting, breakdown UI, and related API routes and lifecycle handling. - Add the migration and prompt/documentation updates that enforce the simplified task model. - Update affected tests and integrations for the removed behavior. Files changed: .changeset/fn-074-removal.md | 7 + docs/dashboard-guide.md | 17 +- docs/gitlab-parity-inventory.md | 7 +- docs/mcp.md | 6 +- docs/settings-reference.md | 16 +- docs/task-management.md | 31 +- docs/workflow-steps.md | 10 +- .../cli/skill/fusion/references/task-structure.md | 2 +- .../cli/skill/fusion/workflows/specifications.md | 4 +- packages/core/src/__tests__/agent-prompts.test.ts | 19 +- .../builtin-workflow-settings-triage.test.ts | 32 +- .../src/__tests__/postgres/schema-applier.test.ts | 2 +- .../core/src/__tests__/prompt-overrides.test.ts | 3 +- ...-deleted-observed-dispatch-side-effects.test.ts | 3 - .../task-deleted-outbox-consumer-backoff.test.ts | 1 - packages/core/src/agents/agent-prompts.ts | 56 +- packages/core/src/index.ts | 2 +- .../0062_remove_task_subtask_splitting.sql | 2 + packages/core/src/postgres/schema-applier.ts | 14 +- packages/core/src/postgres/schema/project.ts | 1 - packages/core/src/store.ts | 12 +- packages/core/src/task-delete-attribution.ts | 20 +- packages/core/src/task-delete-notice.ts | 7 +- .../__tests__/lifecycle-outbox-writer.test.ts | 4 +- .../core/src/task-store/archive-lifecycle-2.ts | 13 +- packages/core/src/task-store/archive-lifecycle.ts | 6 +- packages/core/src/task-store/lifecycle-outbox.ts | 2 - packages/core/src/task-store/persistence.ts | 2 - packages/core/src/task-store/serialization.ts | 2 - packages/core/src/task-store/task-creation.ts | 2 - .../src/task-store/task-deleted-outbox-consumer.ts | 4 - packages/core/src/task-store/task-mutation-ops.ts | 2 +- packages/core/src/task-store/task-row-mappers.ts | 2 +- packages/core/src/tasks/prompt-overrides.ts | 35 - packages/core/src/types.ts | 12 - packages/core/src/types/mesh/archive-planning.ts | 1 - packages/core/src/types/task/task-core.ts | 3 - .../src/workflows/builtin-workflow-settings.ts | 86 -- packages/dashboard/app/App.tsx | 15 - .../mission-planning-modals-mobile.test.ts | 15 - packages/dashboard/app/api/legacy.ts | 18 +- packages/dashboard/app/api/planning/ai-text.ts | 171 +--- packages/dashboard/app/api/planning/planning.ts | 46 - packages/dashboard/app/api/tasks/tasks.ts | 4 +- packages/dashboard/app/components/AppModals.tsx | 39 +- packages/dashboard/app/components/Board.tsx | 10 +- packages/dashboard/app/components/Column.tsx | 9 +- .../dashboard/app/components/InlineCreateCard.tsx | 63 +- packages/dashboard/app/components/Lane.tsx | 3 +- packages/dashboard/app/components/ListView.tsx | 10 +- packages/dashboard/app/components/NewTaskModal.tsx | 8 +- .../dashboard/app/components/PlanningModeModal.css | 128 --- .../dashboard/app/components/PlanningModeModal.tsx | 566 +---------- .../dashboard/app/components/QuickEntryBox.tsx | 43 +- .../dashboard/app/components/SettingsModal.tsx | 2 - .../app/components/SubtaskBreakdownModal.tsx | 932 ----------------- packages/dashboard/app/components/TaskCard.tsx | 1 - packages/dashboard/app/components/TaskForm.tsx | 23 +- .../__tests__/AgentPromptsManager.test.tsx | 1 - .../app/components/__tests__/AppModals.test.tsx | 92 +- .../app/components/__tests__/Board.test.tsx | 69 +- .../components/__tests__/InlineCreateCard.test.tsx | 170 ---- .../app/components/__tests__/ListView.test.tsx | 38 - .../app/components/__tests__/NewTaskModal.test.tsx | 48 - .../components/__tests__/QuickEntryBox.test.tsx | 293 +----- .../__tests__/SubtaskBreakdownModal.test.tsx | 933 ----------------- ...skDetail.mobile-transition.board-panel.test.tsx | 1 - .../__tests__/TaskDetail.swipe-back.test.tsx | 1 - .../app/components/__tests__/TaskForm.test.tsx | 67 -- .../__tests__/WorkflowSettingsPanel.test.tsx | 51 - .../app/components/__tests__/board-mobile.test.tsx | 20 - .../composer-settings-read-isolation.test.tsx | 30 - .../components/__tests__/migratedModalFixtures.tsx | 2 - .../app/components/dashboard/MainContent.tsx | 5 - .../dashboard/__tests__/DashboardBanners.test.tsx | 1 - .../__tests__/MainContent.graph-popout.test.tsx | 1 - .../dashboard/app/components/dashboard/types.ts | 2 - .../app/components/workflow-setting-display.ts | 10 - ...ckgroundSessions.resume-instrumentation.test.ts | 1 - .../hooks/__tests__/useBackgroundSessions.test.ts | 25 +- .../app/hooks/__tests__/useModalManager.test.ts | 3 - packages/dashboard/app/hooks/modalPersistence.ts | 14 - .../dashboard/app/hooks/useBackgroundSessions.ts | 9 +- packages/dashboard/app/hooks/useModalManager.ts | 58 +- packages/dashboard/app/hooks/useProjectActions.ts | 2 +- packages/dashboard/app/hooks/useTaskHandlers.ts | 9 - packages/dashboard/app/utils/builtinPrompts.ts | 7 - packages/dashboard/app/utils/projectStorage.ts | 1 - .../src/__tests__/ai-session-diagnostics.test.ts | 5 +- .../src/__tests__/github-tracking-state.test.ts | 97 -- .../src/__tests__/gitlab-delete-close.test.ts | 6 +- .../gitlab-parity-inventory-documentation.test.ts | 1 - .../src/__tests__/gitlab-split-close.test.ts | 125 --- .../planning-flow-diagnostics-guardrail.test.ts | 1 - .../src/__tests__/process-lifecycle.test.ts | 1 - .../dashboard/src/__tests__/routes-auth.test.ts | 2 - .../src/__tests__/routes-automation.test.ts | 2 - .../dashboard/src/__tests__/routes-git.test.ts | 2 - .../dashboard/src/__tests__/routes-github.test.ts | 92 -- .../src/__tests__/routes-planning-tracking.test.ts | 130 --- .../src/__tests__/routes-planning.test.ts | 1062 +------------------- .../dashboard/src/__tests__/routes-system.test.ts | 2 - .../dashboard/src/__tests__/routes-tasks.test.ts | 902 ----------------- .../shared-branch-group-entry-points.test.ts | 93 -- packages/dashboard/src/ai-session-diagnostics.ts | 3 +- packages/dashboard/src/ai-session-store.ts | 6 +- packages/dashboard/src/ai-session-timeout.ts | 3 +- packages/dashboard/src/github-tracking-state.ts | 43 +- packages/dashboard/src/gitlab-delete-close.ts | 7 +- packages/dashboard/src/gitlab-lifecycle.ts | 4 +- packages/dashboard/src/gitlab-split-close.ts | 103 -- packages/dashboard/src/gitlab-tracking-state.ts | 2 +- packages/dashboard/src/index.ts | 1 - packages/dashboard/src/planning.ts | 171 ---- packages/dashboard/src/routes.ts | 6 - ...er-planning-subtask-routes.parent-close.test.ts | 120 --- .../dashboard/src/routes/register-git-github.ts | 6 - .../src/routes/register-planning-subtask-routes.ts | 773 +------------- .../src/routes/register-task-workflow-routes.ts | 6 +- packages/dashboard/src/server.ts | 8 - packages/dashboard/src/subtask-breakdown.ts | 833 --------------- packages/dashboard/src/triage-trait.ts | 93 +- .../src/__tests__/agent-task-read-tools.test.ts | 1 - .../triage-duplicate-search-regression.test.ts | 3 - .../triage-split-into-subtasks-delete.test.ts | 222 ---- packages/engine/src/__tests__/triage.test.ts | 519 +--------- packages/engine/src/triage.ts | 239 +---- 127 files changed, 214 insertions(+), 10001 deletions(-) Fusion-Task-Id: FN-074 Fusion-Task-Lineage: 23207799-4ec4-4ec7-bcd7-83cba4d98f53 Co-authored-by: Fusion <noreply@runfusion.ai>
106 lines
5.3 KiB
TypeScript
106 lines
5.3 KiB
TypeScript
/*
|
|
FNXC:TaskDeleteAttribution 2026-07-26-14:30:
|
|
Four tasks were deleted inside one hour in a live project and the run-audit rows could not answer
|
|
"which one did the human delete?". Two concrete holes produced that: (1) `task:deleted` metadata
|
|
persisted `auditContext.sessionId` but never `auditContext.taskId`, so an AI agent's `fn_task_delete`
|
|
call recorded only the tool surface (`agentId:"pi-extension"`) and the CALLING task/agent was lost
|
|
forever; (2) the dashboard `DELETE /api/tasks/:id` handler hardcoded `agentId:"system"`, so an
|
|
operator clicking Delete in the UI and a script hitting the same endpoint produced byte-identical
|
|
rows. This module is the single closed vocabulary that fixes both: `callerKind` names the class of
|
|
actor and `callerTaskId` names the calling task.
|
|
|
|
TRUST MODEL — read before extending. `callerKind` is ATTRIBUTION, NOT AUTHENTICATION. The HTTP
|
|
variants are derived from a self-reported `x-fusion-client` request header, so all the row can
|
|
honestly claim is "the client identified itself as the dashboard UI" (`operator-ui`) versus
|
|
"nothing identified itself" (`api-unattributed`). Anything can send the header. Never gate a
|
|
permission, a delete, or any other decision on this value, and never describe it as proof of a
|
|
human. It exists so forensics has a starting hypothesis, not a verdict.
|
|
|
|
Scope note: this is observability only. No delete-blocking, gating, or permission logic is added or
|
|
implied here; whether agents should hold `fn_task_delete` at all is a separate operator policy call.
|
|
*/
|
|
|
|
/**
|
|
* FNXC:TaskDeleteAttribution 2026-08-20-18:40:
|
|
* Closed set of delete-caller classes, ordered from most to least attributable.
|
|
*
|
|
* - `operator-ui` — an HTTP client that identified itself as the dashboard UI (see trust model).
|
|
* - `operator-cli` — the interactive `fn task delete` command, which prompts a human for
|
|
* confirmation at a terminal. Added beyond the original four because that surface is a real,
|
|
* distinct human actor already tagged `agentId:"cli"`; without its own member it would have to
|
|
* masquerade as `api-unattributed` and re-blur exactly the operator-vs-automation line this
|
|
* change exists to draw.
|
|
* - `agent-tool` — an AI agent's tool call (`fn_task_delete`). Pair with `callerTaskId`.
|
|
* - `engine` — an autonomous engine lane (duplicate resolution or self-healing).
|
|
* - `api-unattributed` — an HTTP caller that sent no recognized client header. The deliberate
|
|
* default: unknown is recorded as unknown rather than guessed as operator.
|
|
*/
|
|
export const TASK_DELETE_CALLER_KINDS = [
|
|
"operator-ui",
|
|
"operator-cli",
|
|
"agent-tool",
|
|
"engine",
|
|
"api-unattributed",
|
|
] as const;
|
|
|
|
export type TaskDeleteCallerKind = (typeof TASK_DELETE_CALLER_KINDS)[number];
|
|
|
|
/**
|
|
* FNXC:TaskDeleteAttribution 2026-07-26-14:30:
|
|
* Shared shape for every delete audit context. Previously this object literal was retyped inline at
|
|
* ten call signatures across `store.ts` and both archive-lifecycle branches, which is how `taskId`
|
|
* could exist on the type, be passed by `fn_task_delete`, be consumed by the self-delete guard, and
|
|
* still never reach persisted metadata without anything flagging it.
|
|
*
|
|
* `taskId` is the CALLER's task (the task whose agent is asking for the delete), not the target.
|
|
* It doubles as the self-delete guard input (`auditContext.taskId === id` is refused) and, from
|
|
* this change on, is persisted as `callerTaskId`.
|
|
*/
|
|
export interface TaskDeleteAuditContext {
|
|
agentId: string;
|
|
runId: string;
|
|
sessionId?: string;
|
|
taskId?: string;
|
|
callerKind?: TaskDeleteCallerKind;
|
|
}
|
|
|
|
/**
|
|
* FNXC:TaskDeleteAttribution 2026-07-26-14:30:
|
|
* Request header a Fusion first-party HTTP client sets to identify itself. Self-reported; see the
|
|
* module trust-model note. Kept in core so the dashboard's browser client and the dashboard's
|
|
* Express route cannot drift on the spelling.
|
|
*/
|
|
export const FUSION_CLIENT_HEADER = "x-fusion-client";
|
|
|
|
/** Value the dashboard web/desktop UI sends in {@link FUSION_CLIENT_HEADER}. */
|
|
export const FUSION_DASHBOARD_UI_CLIENT = "dashboard-ui";
|
|
|
|
/**
|
|
* FNXC:TaskDeleteAttribution 2026-07-26-14:30:
|
|
* Map a raw `x-fusion-client` header to a caller kind. Only the exact recognized dashboard-UI token
|
|
* maps to `operator-ui`; absent, array-valued (duplicate header), or unrecognized values all fall
|
|
* back to `api-unattributed` so an unknown caller is never upgraded into an operator claim.
|
|
*/
|
|
export function resolveHttpDeleteCallerKind(headerValue: unknown): TaskDeleteCallerKind {
|
|
if (typeof headerValue !== "string") return "api-unattributed";
|
|
return headerValue.trim().toLowerCase() === FUSION_DASHBOARD_UI_CLIENT
|
|
? "operator-ui"
|
|
: "api-unattributed";
|
|
}
|
|
|
|
/**
|
|
* FNXC:TaskDeleteAttribution 2026-07-26-14:30:
|
|
* Persisted metadata fragment for a `task:deleted` run-audit row. Returns enum/id values only —
|
|
* never prose, never a user-agent string — per the run-audit ids/counts/outcomes-only rule.
|
|
* `callerKind` defaults to `api-unattributed` so every row from here on carries a value and an old
|
|
* row's missing field is distinguishable from a new row's unknown caller.
|
|
*/
|
|
export function buildDeleteCallerAuditFields(
|
|
auditContext: TaskDeleteAuditContext | undefined,
|
|
): { callerKind: TaskDeleteCallerKind; callerTaskId: string | null } {
|
|
return {
|
|
callerKind: auditContext?.callerKind ?? "api-unattributed",
|
|
callerTaskId: auditContext?.taskId ?? null,
|
|
};
|
|
}
|