diff --git a/.changeset/fn-7510-oversight-default.md b/.changeset/fn-7510-oversight-default.md new file mode 100644 index 0000000000..94cce48169 --- /dev/null +++ b/.changeset/fn-7510-oversight-default.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Planner oversight now defaults to full steering/control for every workflow unless explicitly changed. +category: feature +dev: Confirms the `plannerOversightLevel` workflow-setting default is the highest (autonomous) level; unset workflow value and unset per-task override both resolve to full steering via `resolveEffectivePlannerOversightLevel` (task override → workflow effective value → autonomous), adding dedicated regression coverage for the "unless explicitly disabled" precedence. diff --git a/docs/workflow-steps.md b/docs/workflow-steps.md index 5ec0fcb37d..d0ade45c00 100644 --- a/docs/workflow-steps.md +++ b/docs/workflow-steps.md @@ -837,8 +837,13 @@ automatic large-task splitting guidance for oversized M/L work. Set it to `false in a workflow's Values tab when triage should keep large tasks whole unless the task explicitly has `breakIntoSubtasks: true`; explicit subtask requests still follow the mandatory split flow. Planner oversight uses `plannerOversightLevel` -(default `autonomous`) with `off`, `observe`, `steer`, and `autonomous` values; -per-task override and engine runtime behavior are follow-up work. See +(default `autonomous`) with `off`, `observe`, `steer`, and `autonomous` values — +full steering/control is ON for every workflow unless explicitly changed. Tasks +may set a nullable `Task.plannerOversightLevel` override that wins over the +workflow value when present; `resolveEffectivePlannerOversightLevel` in +`@fusion/core` resolves the effective level as task override → workflow +effective value → `autonomous`. The overseer runtime/monitoring behavior that +acts on this level is still follow-up work (FN-7511+). See [Settings Reference → Workflow Settings](./settings-reference.md#workflow-settings) for the full moved-key catalog, the editor walkthrough, and the export/sync posture. diff --git a/packages/core/src/__tests__/plannerOversightLevel-default.test.ts b/packages/core/src/__tests__/plannerOversightLevel-default.test.ts new file mode 100644 index 0000000000..d28839ebb8 --- /dev/null +++ b/packages/core/src/__tests__/plannerOversightLevel-default.test.ts @@ -0,0 +1,79 @@ +/* + * FNXC:PlannerOversight 2026-07-04-13:00: + * FN-7510 dedicated regression coverage: planner oversight defaults to full + * steering/control ("autonomous") for every workflow unless explicitly + * disabled at the workflow-setting scope or the per-task override scope. + * Precedence is task override -> workflow effective value -> "autonomous" + * declaration default (see resolveEffectivePlannerOversightLevel). + */ +import { describe, it, expect, vi } from "vitest"; + +import { BUILTIN_OVERSIGHT_SETTINGS, BUILTIN_WORKFLOW_SETTINGS } from "../builtin-workflow-settings.js"; +import { DEFAULT_PLANNER_OVERSIGHT_LEVEL } from "../types.js"; +import { + resolveEffectiveSettingsById, + resolveEffectivePlannerOversightLevel, + type WorkflowSettingsResolverStore, +} from "../workflow-settings-resolver.js"; + +const PROJECT = "proj-1"; + +function makeStore(values?: Record): WorkflowSettingsResolverStore { + return { + getTaskWorkflowSelection: vi.fn(() => undefined), + getWorkflowDefinition: vi.fn(async () => undefined), + getWorkflowSettingValues: vi.fn(() => values ?? {}), + getWorkflowSettingsProjectId: vi.fn(() => PROJECT), + }; +} + +describe("plannerOversightLevel default (FN-7510)", () => { + it("(a) the declaration default is the full-steering value 'autonomous'", () => { + const decl = BUILTIN_OVERSIGHT_SETTINGS.find((s) => s.id === "plannerOversightLevel"); + expect(decl).toBeDefined(); + expect(decl?.default).toBe("autonomous"); + expect(decl?.default).toBe(DEFAULT_PLANNER_OVERSIGHT_LEVEL); + // The declaration lives in the composed catalog every built-in workflow spreads. + expect(BUILTIN_WORKFLOW_SETTINGS.some((s) => s.id === "plannerOversightLevel")).toBe(true); + }); + + it("(b) resolveEffectiveSettingsById for a built-in workflow with no stored value returns full steering", async () => { + const store = makeStore({}); + const eff = await resolveEffectiveSettingsById(store, "builtin:coding", PROJECT); + expect(eff.plannerOversightLevel).toBe("autonomous"); + }); + + it("(c) an explicit stored workflow value overrides the default ('off')", async () => { + const store = makeStore({ plannerOversightLevel: "off" }); + const eff = await resolveEffectiveSettingsById(store, "builtin:coding", PROJECT); + expect(eff.plannerOversightLevel).toBe("off"); + }); + + it("(c) an explicit stored workflow value overrides the default ('steer')", async () => { + const store = makeStore({ plannerOversightLevel: "steer" }); + const eff = await resolveEffectiveSettingsById(store, "builtin:coding", PROJECT); + expect(eff.plannerOversightLevel).toBe("steer"); + }); + + it("(d) an unset per-task override falls through to the workflow/default level", () => { + expect(resolveEffectivePlannerOversightLevel(undefined, "autonomous")).toBe("autonomous"); + expect(resolveEffectivePlannerOversightLevel(null, "steer")).toBe("steer"); + }); + + it("(d) an explicit per-task override wins over the workflow value", () => { + expect(resolveEffectivePlannerOversightLevel("steer", "autonomous")).toBe("steer"); + expect(resolveEffectivePlannerOversightLevel("off", "autonomous")).toBe("off"); + }); + + it("(e) 'off' is honored at the workflow scope when no task override is set", () => { + expect(resolveEffectivePlannerOversightLevel(undefined, "off")).toBe("off"); + }); + + it("(e) 'off' is honored at the per-task scope even when the workflow value is full steering", () => { + expect(resolveEffectivePlannerOversightLevel("off", "autonomous")).toBe("off"); + }); + + it("both workflow value and task override unset resolve to full steering (unless-explicitly-disabled)", () => { + expect(resolveEffectivePlannerOversightLevel(undefined, undefined)).toBe(DEFAULT_PLANNER_OVERSIGHT_LEVEL); + }); +});