From 374ae08d566fc91e8edd7cb2d239dd812d2b1b4c Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sat, 15 Aug 2026 15:31:58 -0700 Subject: [PATCH] test(engine): repoint source-scan contracts to the peeled module layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full-suite repair, engine source-scan cluster. The package code organization waves moved ~30 engine modules into subdirectories (plugins/, execution/, scheduling/, healing/, worktree/, executor/ peels); the log-severity manifest, prompt carve-out, emit-surface, failure-lane, and worktree-invariant scanners now read the moved locations, verified per file via git log --follow. Two scans caught real drift rather than moves: the lifecycle census had 12 unexamined column guards (resolved with DELIBERATE-LITERAL markers for the mailbox archived tab, the FN-9059 lease-owner terminality check, and the FN-9056 legacy done fallback — baseline re-recorded with zero absorbed debt), and planning-claim gained a genuine second writer in self-healing's FN-8998 transport-failure recovery, admitted to the allowlist with its CAS-guarded justification. 9 files / 119 tests green. Co-Authored-By: Claude Fable 5 --- .../core/src/tasks/workspace-lease-types.ts | 6 ++ .../dashboard/app/components/MailboxModal.tsx | 21 +++- .../dashboard/app/components/MailboxView.tsx | 25 +++-- .../executor-approval-prompt-carveout.test.ts | 7 +- ...cutor-graph-failure-lanes-resolved.test.ts | 32 +++++-- .../executor-skip-bypass-taint.test.ts | 17 ++-- .../src/__tests__/log-severity-manifest.ts | 66 ++++++------- .../log-severity-spam-contract.test.ts | 96 ++++++++++--------- .../planning-claim-single-writer.test.ts | 14 ++- ...updated-lanes-engine-emit-surfaces.test.ts | 7 +- ...orktree-primary-checkout-invariant.test.ts | 20 +++- packages/engine/src/self-healing.ts | 12 ++- .../lifecycle-census-mailbox-tab.test.mjs | 41 ++++++++ .../lib/lifecycle-column-census-baseline.json | 5 +- 14 files changed, 255 insertions(+), 114 deletions(-) create mode 100644 scripts/__tests__/lifecycle-census-mailbox-tab.test.mjs diff --git a/packages/core/src/tasks/workspace-lease-types.ts b/packages/core/src/tasks/workspace-lease-types.ts index 69539cc3fe..610de1611b 100644 --- a/packages/core/src/tasks/workspace-lease-types.ts +++ b/packages/core/src/tasks/workspace-lease-types.ts @@ -18,6 +18,12 @@ export interface WorkspaceLandIntent { taskId: string; repoRelPath: string; remo * FNXC:Workspace 2026-08-15-08:23: * Store reclaim and workspace self-healing share this deliberately narrow * terminal rule so either cannot reclaim a task the other considers live. + * + * DELIBERATE-LITERAL — narrow terminal-owner rule (FN-9059). A lease-owner row is + * read without its workflow context, so resolving the complete lane per-workflow + * here would let a resolver failure make a live owner read as terminal and allow a + * competing reclaim. The legacy `done` literal is the intentionally conservative + * shared floor for both reclaim paths. */ export function isTerminalWorkspaceLeaseOwner(row: Pick | null | undefined): boolean { return row != null && (row.column === "done" || row.status === "failed"); diff --git a/packages/dashboard/app/components/MailboxModal.tsx b/packages/dashboard/app/components/MailboxModal.tsx index bdf53191fe..736482e2c6 100644 --- a/packages/dashboard/app/components/MailboxModal.tsx +++ b/packages/dashboard/app/components/MailboxModal.tsx @@ -57,6 +57,19 @@ import { getRelativeTimeBucket } from "../utils/relativeTimeAgo"; type MailboxTab = "inbox" | "outbox" | "archived" | "agents"; +/* +FNXC:LifecycleColumnCensus 2026-08-13-21:58: +DELIBERATE-LITERAL — mailbox folder tab, not a board column. + +FN-9014 named a folder `archived`. The tab comparison is that folder switch. Converting it to +resolveLifecycleColumns would ask a workflow which lane a mailbox folder is in. Keep the +comparison inside this helper so a real board guard that happens to use the name `activeTab` +still counts in the census. +*/ +function isMailboxArchivedTab(tab: MailboxTab): boolean { + return tab === "archived"; +} + const ALL_AGENTS_MAILBOX_ID = "__all_agents__"; interface MailboxModalProps { @@ -488,7 +501,7 @@ export function MailboxModal({ if (!isOpen) return; if (activeTab === "inbox") loadInbox(); else if (activeTab === "outbox") loadOutbox(); - else if (activeTab === "archived") loadArchivedInbox(); + else if (isMailboxArchivedTab(activeTab)) loadArchivedInbox(); }, [isOpen, activeTab, loadInbox, loadOutbox, loadArchivedInbox]); // Load agent mailbox when selected @@ -701,7 +714,7 @@ export function MailboxModal({ handleCloseMessage(); if (activeTab === "inbox") loadInbox(); else if (activeTab === "outbox") loadOutbox(); - else if (activeTab === "archived") loadArchivedInbox(); + else if (isMailboxArchivedTab(activeTab)) loadArchivedInbox(); else if (selectedAgentId === ALL_AGENTS_MAILBOX_ID) loadAllAgentsMailbox(); else if (selectedAgentId) loadAgentMailbox(selectedAgentId); void refreshUnreadCount(); @@ -905,7 +918,7 @@ export function MailboxModal({ {t("mailbox.outboxTab", "Outbox")} - + )} diff --git a/packages/dashboard/app/components/MailboxView.tsx b/packages/dashboard/app/components/MailboxView.tsx index 57e2d3bedf..6abb501b22 100644 --- a/packages/dashboard/app/components/MailboxView.tsx +++ b/packages/dashboard/app/components/MailboxView.tsx @@ -62,6 +62,19 @@ import { getRelativeTimeBucket } from "../utils/relativeTimeAgo"; type MailboxTab = "inbox" | "outbox" | "archived" | "agents" | "approvals"; +/* +FNXC:LifecycleColumnCensus 2026-08-13-21:58: +DELIBERATE-LITERAL — mailbox folder tab, not a board column. + +FN-9014 named a folder `archived`. The tab comparison is that folder switch. Converting it to +resolveLifecycleColumns would ask a workflow which lane a mailbox folder is in. Keep the +comparison inside this helper so a real board guard that happens to use the name `activeTab` +still counts in the census. +*/ +function isMailboxArchivedTab(tab: MailboxTab): boolean { + return tab === "archived"; +} + interface MailboxViewProps { projectId?: string; addToast?: (msg: string, type?: "success" | "error") => void; @@ -573,7 +586,7 @@ export function MailboxView({ useEffect(() => { if (activeTab === "inbox") loadInbox(); else if (activeTab === "outbox") loadOutbox(); - else if (activeTab === "archived") loadArchivedInbox(); + else if (isMailboxArchivedTab(activeTab)) loadArchivedInbox(); else if (activeTab === "agents") loadAgents(); else if (activeTab === "approvals") { void loadApprovals(approvalSubTab); @@ -783,7 +796,7 @@ export function MailboxView({ try { await archiveMessage(id, projectId); dismissMessage(); - if (activeTab === "archived") loadArchivedInbox(); + if (isMailboxArchivedTab(activeTab)) loadArchivedInbox(); else if (activeTab === "outbox") loadOutbox(); else if (activeTab === "inbox") loadInbox(); else if (selectedAgentId === ALL_AGENTS_MAILBOX_ID) loadAllAgentsMailbox(); @@ -812,7 +825,7 @@ export function MailboxView({ // Refresh current tab if (activeTab === "inbox") loadInbox(); else if (activeTab === "outbox") loadOutbox(); - else if (activeTab === "archived") loadArchivedInbox(); + else if (isMailboxArchivedTab(activeTab)) loadArchivedInbox(); else if (selectedAgentId === ALL_AGENTS_MAILBOX_ID) loadAllAgentsMailbox(); else if (selectedAgentId) loadAgentMailbox(selectedAgentId); addToast?.("Message deleted", "success"); @@ -1134,7 +1147,7 @@ export function MailboxView({ const renderListPane = () => ( <> - {activeTab === "archived" && ( + {isMailboxArchivedTab(activeTab) && (
{isLoading && !archivedInbox && } {archivedInbox?.messages.length === 0 &&
No archived messages
} @@ -1576,7 +1589,7 @@ export function MailboxView({ onClick={() => { if (activeTab === "inbox") loadInbox(); else if (activeTab === "outbox") loadOutbox(); - else if (activeTab === "archived") loadArchivedInbox(); + else if (isMailboxArchivedTab(activeTab)) loadArchivedInbox(); else if (activeTab === "approvals") loadApprovals(approvalSubTab); else if (selectedAgentId === ALL_AGENTS_MAILBOX_ID) loadAllAgentsMailbox(); else if (selectedAgentId) loadAgentMailbox(selectedAgentId); @@ -1610,7 +1623,7 @@ export function MailboxView({ {t("mailbox.outbox", "Outbox")} - +