FN-7539: suppress oversight badge for inherited default level
Prevent the Auto-recovery/oversight badge from rendering on virtually every task card when the effective planner oversight level is just the inherited schema default. - TaskCard: track whether the effective oversight level was reached purely via inheritance (no per-task override, no explicit non-default workflow tier), and suppress the badge in that case - Preserve badge visibility when a task explicitly overrides oversight to "autonomous" or a workflow tier explicitly resolves to a non-default level - Update TaskCard oversight tests to cover inherited-default suppression vs explicit-override rendering - Add changeset documenting the fix - Update dashboard guide docs Files changed: .changeset/fn-7539-oversight-badge-default.md | 7 ++++ docs/dashboard-guide.md | 3 +- packages/dashboard/app/components/TaskCard.tsx | 21 +++++++++- .../__tests__/TaskCard.oversight.test.tsx | 48 +++++++++++++++++++--- .../app/components/__tests__/TaskCard.test.tsx | 11 +++-- 5 files changed, 76 insertions(+), 14 deletions(-) Fusion-Task-Id: FN-7539 Fusion-Task-Lineage: 8ac9a60c-69c6-4095-b6b2-18c79718b62b Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7539-oversight-badge-default.md
Normal file
7
.changeset/fn-7539-oversight-badge-default.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Task cards no longer show the "Auto-recovery" oversight badge unless oversight is explicitly configured.
|
||||||
|
category: fix
|
||||||
|
dev: `TaskCard.tsx`'s `showOversightBadge` gate now also suppresses the badge when the effective level equals `DEFAULT_PLANNER_OVERSIGHT_LEVEL` ("autonomous") and there is no explicit per-task `plannerOversightLevel` override; an explicit per-task override of "autonomous" still renders the badge.
|
||||||
@@ -183,7 +183,8 @@ Features:
|
|||||||
- Task card header meta badges group priority, fast mode, agent-created provenance, workflow identity, and elapsed/created-time chips into one wrapping row; agent labels prefer `sourceMetadata.agentName` over raw agent IDs
|
- Task card header meta badges group priority, fast mode, agent-created provenance, workflow identity, and elapsed/created-time chips into one wrapping row; agent labels prefer `sourceMetadata.agentName` over raw agent IDs
|
||||||
<!-- FNXC:PlannerOversight 2026-07-04-00:00: FN-7516 adds a read-only effective oversight-level badge plus an active-overseer-state indicator to the card-meta-badges cluster. The overseer-state indicator is derived card-locally from already-on-Task fields (mirroring the engine's stage-resolution precedence) rather than a new engine-plumbed field, since @fusion/engine's in-memory monitor state is not persisted onto Task/exposed via API. -->
|
<!-- FNXC:PlannerOversight 2026-07-04-00:00: FN-7516 adds a read-only effective oversight-level badge plus an active-overseer-state indicator to the card-meta-badges cluster. The overseer-state indicator is derived card-locally from already-on-Task fields (mirroring the engine's stage-resolution precedence) rather than a new engine-plumbed field, since @fusion/engine's in-memory monitor state is not persisted onto Task/exposed via API. -->
|
||||||
<!-- FNXC:PlannerOversight 2026-07-04-16:00: round-2 code-review fix — when a card must fetch the workflow's effective oversight tier (no synchronous per-task override), neither badge below renders until that fetch resolves; the schema default must never render as a guess while the true workflow tier is unknown. -->
|
<!-- FNXC:PlannerOversight 2026-07-04-16:00: round-2 code-review fix — when a card must fetch the workflow's effective oversight tier (no synchronous per-task override), neither badge below renders until that fetch resolves; the schema default must never render as a guess while the true workflow tier is unknown. -->
|
||||||
- Task cards show a read-only **oversight-level badge** (`Observe`, `Steer`, or `Auto-recovery`) in the meta-badges cluster reflecting the effective planner-oversight level (a task's explicit override wins and renders immediately; otherwise the card resolves the task's workflow-configured effective `plannerOversightLevel` when the board supplies a `workflowBadge`, falling back to the schema default `Auto-recovery` only when neither tier resolves) (`data-testid="card-oversight-badge"`); the badge is absent (no empty shell) when the effective level is explicitly "off", **and** while an inherited (no per-task-override) workflow tier is still being resolved (in flight or not yet fetched) — it never shows a guessed default during that window.
|
<!-- FNXC:PlannerOversight 2026-07-04-19:10: FN-7539 fix — the badge was rendering on virtually every card because the schema default `autonomous` tier was treated as "meaningfully configured". Narrowed the gate so an inherited (no per-task-override, no non-default workflow tier) `autonomous` level renders no badge; only an explicit per-task override or a resolved workflow/effective tier that is not the plain inherited default surfaces the badge. -->
|
||||||
|
- Task cards show a read-only **oversight-level badge** (`Observe`, `Steer`, or `Auto-recovery`) in the meta-badges cluster reflecting the effective planner-oversight level, but only when oversight is *meaningfully configured* — an explicit per-task override (including an explicit `autonomous` override), or a resolved workflow/effective tier of `observe`/`steer` (`data-testid="card-oversight-badge"`). A card that merely **inherits** the schema default `autonomous` tier (no per-task override, no non-default workflow tier) renders no badge and no empty `.card-meta-badges` shell. The badge is also absent when the effective level is explicitly "off", **and** while an inherited (no per-task-override) workflow tier is still being resolved (in flight or not yet fetched) — it never shows a guessed default during that window.
|
||||||
- Task cards also show a read-only **active-overseer-state indicator** (`.card-overseer-state-badge`, `data-testid="card-overseer-state-badge"`) — "Executor", "Reviewer", "Merger", "Pull request", or "Workflow gate" — while the task is in a monitorable stage (in-progress/in-review, or paused on a workflow input/approval gate) and the effective oversight level is not "off" (and is known, per the same resolution gate as the oversight badge above). The indicator is suppressed (no empty shell) when the task is user-paused, agent-paused off a workflow gate, `done`, or `archived`.
|
- Task cards also show a read-only **active-overseer-state indicator** (`.card-overseer-state-badge`, `data-testid="card-overseer-state-badge"`) — "Executor", "Reviewer", "Merger", "Pull request", or "Workflow gate" — while the task is in a monitorable stage (in-progress/in-review, or paused on a workflow input/approval gate) and the effective oversight level is not "off" (and is known, per the same resolution gate as the oversight badge above). The indicator is suppressed (no empty shell) when the task is user-paused, agent-paused off a workflow gate, `done`, or `archived`.
|
||||||
<!-- FNXC:PlannerOversight 2026-07-04-17:00: FN-7517 adds interactive task-detail planner-overseer controls (quick level change, manual nudge, stop oversight, explain current action) alongside the FN-7516 read-only card badges above. These controls live ONLY in TaskDetailModal, not TaskCard. -->
|
<!-- FNXC:PlannerOversight 2026-07-04-17:00: FN-7517 adds interactive task-detail planner-overseer controls (quick level change, manual nudge, stop oversight, explain current action) alongside the FN-7516 read-only card badges above. These controls live ONLY in TaskDetailModal, not TaskCard. -->
|
||||||
- The task detail modal's inline meta-controls cluster (next to Priority/Execution mode) adds four planner-overseer controls: a **quick oversight-level select** (`data-testid="detail-oversight-level-select"`) that writes the per-task `plannerOversightLevel` override (Off/Observe/Steer/Autonomous recovery) or clears it back to the inherited workflow/project default via an "Inherit" option; a **manual nudge** button (`data-testid="detail-overseer-nudge"`) that asks the overseer to inject one guidance-only steering comment into the currently watched stage right now (never a merge/PR/destructive action), disabled when the overseer is off/inactive or the task is user-paused/done/archived/`autoMerge:false` in-review; a **stop oversight** button (`data-testid="detail-overseer-stop"`) that disables active oversight for the task (confirmation-gated), hidden once oversight is already off; and an **explain current action** button (`data-testid="detail-overseer-explain"`) that toggles a small read-only panel (`data-testid="detail-overseer-explain-panel"`) showing the overseer's watched stage, reason, last action, and attempt count/limit, with a non-empty-shell inactive state when the overseer is not currently watching. All three action controls call the `POST /tasks/:id/overseer/nudge`, `POST /tasks/:id/overseer/stop`, and `GET /tasks/:id/overseer/explain` routes.
|
- The task detail modal's inline meta-controls cluster (next to Priority/Execution mode) adds four planner-overseer controls: a **quick oversight-level select** (`data-testid="detail-oversight-level-select"`) that writes the per-task `plannerOversightLevel` override (Off/Observe/Steer/Autonomous recovery) or clears it back to the inherited workflow/project default via an "Inherit" option; a **manual nudge** button (`data-testid="detail-overseer-nudge"`) that asks the overseer to inject one guidance-only steering comment into the currently watched stage right now (never a merge/PR/destructive action), disabled when the overseer is off/inactive or the task is user-paused/done/archived/`autoMerge:false` in-review; a **stop oversight** button (`data-testid="detail-overseer-stop"`) that disables active oversight for the task (confirmation-gated), hidden once oversight is already off; and an **explain current action** button (`data-testid="detail-overseer-explain"`) that toggles a small read-only panel (`data-testid="detail-overseer-explain-panel"`) showing the overseer's watched stage, reason, last action, and attempt count/limit, with a non-empty-shell inactive state when the overseer is not currently watching. All three action controls call the `POST /tasks/:id/overseer/nudge`, `POST /tasks/:id/overseer/stop`, and `GET /tasks/:id/overseer/explain` routes.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { createPortal } from "react-dom";
|
|||||||
import { Link, Clock, Layers, Pencil, ChevronDown, Folder, Target, Bot, Trash2, RotateCw, Zap, GitBranch, GitPullRequest, AlertTriangle, ArrowUpRight, Eye } from "lucide-react";
|
import { Link, Clock, Layers, Pencil, ChevronDown, Folder, Target, Bot, Trash2, RotateCw, Zap, GitBranch, GitPullRequest, AlertTriangle, ArrowUpRight, Eye } from "lucide-react";
|
||||||
import type { Task, TaskDetail, Column, ColumnId, PrInfo, IssueInfo, TaskPriority, GithubIssueAction, MergeResult, PlannerOversightLevel } from "@fusion/core";
|
import type { Task, TaskDetail, Column, ColumnId, PrInfo, IssueInfo, TaskPriority, GithubIssueAction, MergeResult, PlannerOversightLevel } from "@fusion/core";
|
||||||
import {
|
import {
|
||||||
|
DEFAULT_PLANNER_OVERSIGHT_LEVEL,
|
||||||
DEFAULT_TASK_PRIORITY,
|
DEFAULT_TASK_PRIORITY,
|
||||||
HIGH_FANOUT_BLOCKER_TODO_THRESHOLD,
|
HIGH_FANOUT_BLOCKER_TODO_THRESHOLD,
|
||||||
PLANNER_OVERSIGHT_LEVELS,
|
PLANNER_OVERSIGHT_LEVELS,
|
||||||
@@ -1702,14 +1703,32 @@ function TaskCardComponent({
|
|||||||
* default/guessed level. Only an effective level that resolves to "off"
|
* default/guessed level. Only an effective level that resolves to "off"
|
||||||
* renders no badge either (no empty shell) — see the
|
* renders no badge either (no empty shell) — see the
|
||||||
* `hasCardMetaBadges`/render guard below.
|
* `hasCardMetaBadges`/render guard below.
|
||||||
|
*
|
||||||
|
* FN-7539: the round-2 fix above still showed the badge on virtually every
|
||||||
|
* card, because a task with no per-task override and no explicit
|
||||||
|
* non-default workflow tier resolves to the schema default
|
||||||
|
* (`DEFAULT_PLANNER_OVERSIGHT_LEVEL`, "autonomous") — and that default was
|
||||||
|
* still treated as "resolved" and rendered. An inherited default is not
|
||||||
|
* meaningfully-configured oversight, so it must not surface a per-card
|
||||||
|
* badge. Narrowed: suppress the badge when the effective level equals the
|
||||||
|
* schema default AND there is no explicit per-task override — i.e. the
|
||||||
|
* default was reached purely by inheritance (no override, no non-default
|
||||||
|
* workflow tier). An EXPLICIT per-task override of "autonomous" still
|
||||||
|
* renders the badge (explicit intent is preserved, not treated as
|
||||||
|
* inherited default), and a workflow tier that explicitly resolves to
|
||||||
|
* "autonomous" also renders nothing, matching the inherited-default case.
|
||||||
*/
|
*/
|
||||||
const hasTaskOversightOverride = isPlannerOversightLevelValue(task.plannerOversightLevel);
|
const hasTaskOversightOverride = isPlannerOversightLevelValue(task.plannerOversightLevel);
|
||||||
const effectiveOversightLevel: PlannerOversightLevel = resolveEffectivePlannerOversightLevel(
|
const effectiveOversightLevel: PlannerOversightLevel = resolveEffectivePlannerOversightLevel(
|
||||||
task.plannerOversightLevel,
|
task.plannerOversightLevel,
|
||||||
workflowOversightEffectiveLevel,
|
workflowOversightEffectiveLevel,
|
||||||
);
|
);
|
||||||
|
const isInheritedDefaultOversightLevel =
|
||||||
|
!hasTaskOversightOverride && effectiveOversightLevel === DEFAULT_PLANNER_OVERSIGHT_LEVEL;
|
||||||
const showOversightBadge =
|
const showOversightBadge =
|
||||||
(hasTaskOversightOverride || workflowOversightResolved) && effectiveOversightLevel !== "off";
|
(hasTaskOversightOverride || workflowOversightResolved) &&
|
||||||
|
effectiveOversightLevel !== "off" &&
|
||||||
|
!isInheritedDefaultOversightLevel;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* FNXC:PlannerOversight 2026-07-04-00:00:
|
* FNXC:PlannerOversight 2026-07-04-00:00:
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
* and the active-overseer-state indicator. Covers the Surface Enumeration data
|
* and the active-overseer-state indicator. Covers the Surface Enumeration data
|
||||||
* states: Observe/Steer/Autonomous render a labeled badge; an explicit "off"
|
* states: Observe/Steer/Autonomous render a labeled badge; an explicit "off"
|
||||||
* effective level renders nothing (no empty shell); an unset per-task override
|
* effective level renders nothing (no empty shell); an unset per-task override
|
||||||
* resolves to the schema default ("autonomous") rather than rendering nothing,
|
* that resolves to the schema default ("autonomous") renders NO badge at all
|
||||||
* per code review. The overseer-state indicator renders only while the
|
* (FN-7539: an inherited default is not meaningfully-configured oversight),
|
||||||
|
* while an EXPLICIT per-task override of "autonomous" still renders the
|
||||||
|
* badge (explicit intent is preserved). The overseer-state indicator renders only while the
|
||||||
* card-local watched-stage derivation (`deriveOverseerCardWatchedStage` in
|
* card-local watched-stage derivation (`deriveOverseerCardWatchedStage` in
|
||||||
* TaskCard.tsx, mirroring the engine's `resolveWatchedStage`) resolves a stage
|
* TaskCard.tsx, mirroring the engine's `resolveWatchedStage`) resolves a stage
|
||||||
* AND the task is not paused/done/archived AND the effective oversight level
|
* AND the task is not paused/done/archived AND the effective oversight level
|
||||||
@@ -111,9 +113,15 @@ describe("TaskCard effective oversight-level badge (FN-7516)", () => {
|
|||||||
expect(screen.queryByTestId("card-oversight-badge")).toBeNull();
|
expect(screen.queryByTestId("card-oversight-badge")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("resolves the schema default (autonomous) — and renders the badge — when the level field is undefined", () => {
|
it("renders no badge (no empty shell) when the level field is undefined and it resolves to the inherited schema default (autonomous) (FN-7539)", () => {
|
||||||
renderCard({ column: "todo" });
|
renderCard({ column: "todo" });
|
||||||
|
|
||||||
|
expect(screen.queryByTestId("card-oversight-badge")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders the badge when the level is EXPLICITLY overridden to autonomous (explicit intent preserved) (FN-7539)", () => {
|
||||||
|
renderCard({ plannerOversightLevel: "autonomous", column: "todo" });
|
||||||
|
|
||||||
const badge = screen.getByTestId("card-oversight-badge");
|
const badge = screen.getByTestId("card-oversight-badge");
|
||||||
expect(badge).toBeTruthy();
|
expect(badge).toBeTruthy();
|
||||||
expect(badge.className).toContain("card-oversight-badge--autonomous");
|
expect(badge.className).toContain("card-oversight-badge--autonomous");
|
||||||
@@ -130,6 +138,15 @@ describe("TaskCard effective oversight-level badge (FN-7516)", () => {
|
|||||||
expect(metaBadges.querySelector(".card-oversight-badge")).toBeNull();
|
expect(metaBadges.querySelector(".card-oversight-badge")).toBeNull();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not render an always-on empty card-meta-badges child for the inherited-default case (FN-7539)", () => {
|
||||||
|
const { container } = renderCard({ column: "todo" });
|
||||||
|
|
||||||
|
const metaBadges = container.querySelector(".card-meta-badges");
|
||||||
|
if (metaBadges) {
|
||||||
|
expect(metaBadges.querySelector(".card-oversight-badge")).toBeNull();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("TaskCard active-overseer-state indicator (FN-7516)", () => {
|
describe("TaskCard active-overseer-state indicator (FN-7516)", () => {
|
||||||
@@ -259,10 +276,12 @@ describe("TaskCard workflow-effective oversight level (FN-7516 code-review fix)"
|
|||||||
expect(screen.queryByTestId("card-overseer-state-badge")).toBeNull();
|
expect(screen.queryByTestId("card-overseer-state-badge")).toBeNull();
|
||||||
|
|
||||||
// Once the fetch resolves (workflow has no oversight setting → schema
|
// Once the fetch resolves (workflow has no oversight setting → schema
|
||||||
// default applies), the badge appears.
|
// default applies), the badge STILL does not render (FN-7539: an
|
||||||
|
// inherited default is not meaningfully-configured oversight).
|
||||||
resolveFetch({ stored: {}, effective: {}, orphaned: [] });
|
resolveFetch({ stored: {}, effective: {}, orphaned: [] });
|
||||||
const badge = await screen.findByTestId("card-oversight-badge");
|
await waitFor(() => {
|
||||||
expect(badge.className).toContain("card-oversight-badge--autonomous");
|
expect(screen.queryByTestId("card-oversight-badge")).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders a per-task override immediately even while the workflow-tier fetch is pending", async () => {
|
it("renders a per-task override immediately even while the workflow-tier fetch is pending", async () => {
|
||||||
@@ -304,6 +323,23 @@ describe("TaskCard workflow-effective oversight level (FN-7516 code-review fix)"
|
|||||||
const badge = await screen.findByTestId("card-oversight-badge");
|
const badge = await screen.findByTestId("card-oversight-badge");
|
||||||
expect(badge.className).toContain("card-oversight-badge--observe");
|
expect(badge.className).toContain("card-oversight-badge--observe");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders no badge when the workflow's effective level explicitly resolves to autonomous (equals the inherited default) (FN-7539)", async () => {
|
||||||
|
vi.mocked(fetchWorkflowSettingValues).mockResolvedValueOnce({
|
||||||
|
stored: { plannerOversightLevel: "autonomous" },
|
||||||
|
effective: { plannerOversightLevel: "autonomous" },
|
||||||
|
orphaned: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
renderCard({ column: "todo" }, { workflowBadge: { workflowId: "wf-configured-autonomous", workflowName: "Configured Autonomous" } });
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(fetchWorkflowSettingValues).toHaveBeenCalledWith("wf-configured-autonomous", undefined);
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByTestId("card-oversight-badge")).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("TaskCard memo comparator — oversight level and overseer state (FN-7516)", () => {
|
describe("TaskCard memo comparator — oversight level and overseer state (FN-7516)", () => {
|
||||||
|
|||||||
@@ -2360,16 +2360,15 @@ describe("TaskCard", () => {
|
|||||||
expect(timer?.closest(".card-meta-badges")).toBeNull();
|
expect(timer?.closest(".card-meta-badges")).toBeNull();
|
||||||
expect(timer?.closest(".card-footer-row-right")).not.toBeNull();
|
expect(timer?.closest(".card-footer-row-right")).not.toBeNull();
|
||||||
|
|
||||||
// FNXC:PlannerOversight 2026-07-04-00:00: an unset per-task oversight override
|
// FNXC:PlannerOversight 2026-07-04-00:00: an unset per-task oversight
|
||||||
// resolves to the schema default ("autonomous") via the FN-7516 card badge,
|
// override resolving to the inherited schema default ("autonomous") no
|
||||||
// so it now appears alongside the other opt-in meta badges (FN-7516).
|
// longer renders a per-card badge (FN-7539) — an inherited default is not
|
||||||
|
// meaningfully-configured oversight, so it does not appear among the
|
||||||
|
// opt-in meta badges here.
|
||||||
expect(Array.from(group?.children ?? []).map((child) => child.className)).toEqual([
|
expect(Array.from(group?.children ?? []).map((child) => child.className)).toEqual([
|
||||||
"card-priority-badge card-priority-badge--high",
|
"card-priority-badge card-priority-badge--high",
|
||||||
"card-execution-mode-badge card-execution-mode-badge--fast",
|
"card-execution-mode-badge card-execution-mode-badge--fast",
|
||||||
"card-agent-created-badge",
|
"card-agent-created-badge",
|
||||||
"card-oversight-badge card-oversight-badge--autonomous",
|
|
||||||
|
|
||||||
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user