test(engine): repoint source-scan contracts to the peeled module layout

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 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-08-15 15:31:58 -07:00
parent f11bb2e899
commit 374ae08d56
14 changed files with 255 additions and 114 deletions

View File

@@ -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<Task, "column" | "status"> | null | undefined): boolean {
return row != null && (row.column === "done" || row.status === "failed");

View File

@@ -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({
<Send size={14} />
<span>{t("mailbox.outboxTab", "Outbox")}</span>
</button>
<button className={`btn btn-sm btn-secondary mailbox-tab ${activeTab === "archived" ? "active" : ""}`} onClick={() => { consumeCurrentDeepLink(); setActiveTab("archived"); setSelectedMessage(null); }} data-testid="mailbox-tab-archived"><Archive size={14} /><span>Archived</span></button>
<button className={`btn btn-sm btn-secondary mailbox-tab ${isMailboxArchivedTab(activeTab) ? "active" : ""}`} onClick={() => { consumeCurrentDeepLink(); setActiveTab("archived"); setSelectedMessage(null); }} data-testid="mailbox-tab-archived"><Archive size={14} /><span>Archived</span></button>
<button
className={`btn btn-sm btn-secondary mailbox-tab ${activeTab === "agents" ? "active" : ""}`}
onClick={() => { consumeCurrentDeepLink(); setActiveTab("agents"); setSelectedMessage(null); }}
@@ -1092,7 +1105,7 @@ export function MailboxModal({
{!selectedMessage && !showComposer && (
<>
{/* Inbox Tab */}
{activeTab === "archived" && (
{isMailboxArchivedTab(activeTab) && (
<div className="mailbox-list" data-testid="mailbox-archived-list">
{archivedInbox?.messages.length === 0 && <div className="mailbox-empty" data-testid="mailbox-archived-empty">No archived messages</div>}
{archivedInbox?.messages.map((message) => <button type="button" className="mailbox-item" key={message.id} onClick={() => void handleOpenMessage(message)} data-testid={`mailbox-item-${message.id}`}>{message.content}</button>)}

View File

@@ -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) && (
<div className="mailbox-list" data-testid="mailbox-archived-list">
{isLoading && !archivedInbox && <MailboxSkeleton />}
{archivedInbox?.messages.length === 0 && <div className="mailbox-empty" data-testid="mailbox-archived-empty">No archived messages</div>}
@@ -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({
<Send size={14} />
<span>{t("mailbox.outbox", "Outbox")}</span>
</button>
<button className={`btn btn-sm btn-secondary mailbox-tab ${activeTab === "archived" ? "active" : ""}`} onClick={() => handleSelectTab("archived")} data-testid="mailbox-tab-archived">Archived</button>
<button className={`btn btn-sm btn-secondary mailbox-tab ${isMailboxArchivedTab(activeTab) ? "active" : ""}`} onClick={() => handleSelectTab("archived")} data-testid="mailbox-tab-archived">Archived</button>
<button
className={`btn btn-sm btn-secondary mailbox-tab ${activeTab === "agents" ? "active" : ""}`}
onClick={() => handleSelectTab("agents")}

View File

@@ -14,13 +14,16 @@ the engine copy is asserted by reading the source file directly.
*/
const CARVEOUT_MARKER = "Exception — pending approval.";
// FNXC:ExecutorPrompt 2026-08-15-19:10: package code organization waves moved
// EXECUTOR_SYSTEM_PROMPT out of the thin executor.ts shell into
// executor/system-prompt.ts, and core's agent-prompts.ts into agents/.
function readExecutorSourcePrompt(): string {
const executorTsPath = fileURLToPath(new URL("../executor.ts", import.meta.url));
const executorTsPath = fileURLToPath(new URL("../executor/system-prompt.ts", import.meta.url));
return readFileSync(executorTsPath, "utf8");
}
function readAgentPromptsSource(): string {
const agentPromptsTsPath = fileURLToPath(new URL("../../../core/src/agent-prompts.ts", import.meta.url));
const agentPromptsTsPath = fileURLToPath(new URL("../../../core/src/agents/agent-prompts.ts", import.meta.url));
return readFileSync(agentPromptsTsPath, "utf8");
}

View File

@@ -453,19 +453,33 @@ describe("one lane snapshot per recovery, across every classifier", () => {
(PR #2703 review) — its eligibility check and its review branch each resolved independently. */
const SELF_CONTAINED = ["handleNonContinuableSessionError"];
/*
FNXC:CodeOrganization 2026-08-15-19:10 (executor peels):
Every classifier moved out of the TaskExecutor class body into its own executor/ free-function
module (`export async function <name>(`). The scan follows the peel: find the one module that
declares the function and bound its body by the next top-level `export` declaration — the same
wrong-window hazard the `private ` bound guarded against, now at module granularity.
*/
const CLASSIFIER_MODULES: Record<string, string> = {
isRetryableBenignMergePauseAbort: "is-retryable-benign-merge-pause-abort.ts",
isBenignManualMergeHoldPauseAbort: "is-benign-manual-merge-hold-pause-abort.ts",
handleStaleInReviewPlanPauseAbortReplay: "handle-stale-in-review-plan-pause-abort-replay.ts",
handleStaleInReviewParsePauseAbortReplay: "handle-stale-in-review-parse-pause-abort-replay.ts",
isReentrantPausedAbortedInFlightNode: "is-reentrant-paused-aborted-in-flight-node.ts",
routeUnusableWorktreeGraphFailureToRecovery: "route-unusable-worktree-graph-failure-to-recovery.ts",
routeGraphFailureToExecutionResume: "route-graph-failure-to-execution-resume.ts",
handleNonContinuableSessionError: "non-continuable-session.ts",
};
async function methodBody(name: string): Promise<string> {
const { readFile } = await import("node:fs/promises");
const source = await readFile(new URL("../executor.ts", import.meta.url), "utf8");
const module = CLASSIFIER_MODULES[name];
expect(module, `${name} has no mapped executor/ module — update this test, do not delete it`).toBeDefined();
const source = await readFile(new URL(`../executor/${module}`, import.meta.url), "utf8");
const code = source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
const start = code.indexOf(`private async ${name}(`);
const start = code.indexOf(`export async function ${name}(`);
expect(start, `${name} not found — update this test, do not delete it`).toBeGreaterThan(-1);
/*
Bounded by the next `private ` declaration of ANY kind. My first version bounded on the next member of
the same list, so the last entry's window ran to EOF and it accused a method of a call living 1200 lines
away. A ratchet with the wrong window accuses the wrong function — worse than no ratchet, because the
"fix" lands on code that was already correct.
*/
const next = code.indexOf("\n private ", start + 1);
const next = code.indexOf("\nexport ", start + 1);
return code.slice(start, next === -1 ? code.length : next);
}

View File

@@ -1,6 +1,12 @@
import "./executor-test-helpers.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { TaskExecutor, evaluateTaskDoneRefusal } from "../executor.js";
import { TaskExecutor, evaluateTaskDoneRefusal, evaluateImplicitCompletionRefusal } from "../executor.js";
/*
FNXC:CodeOrganization 2026-08-15-19:10:
evaluateImplicitCompletionRefusal was peeled off the TaskExecutor class into the pure
executor/completion-predicates.ts module (re-exported from executor.js). Same behavior,
same (task, codeReviewVerdicts) signature — these tests call the free function directly.
*/
import { resetExecutorMocks } from "./executor-test-helpers.js";
import { evaluateSkipBypassTaint } from "@fusion/core";
@@ -48,26 +54,23 @@ describe("TaskExecutor skip-bypass taint (FN-8141)", () => {
});
it("implicit completion is REFUSED when steps were skipped after a bulk-step-completion refusal", () => {
const executor = new TaskExecutor(createStore(), "/tmp/test");
// The FN-8141 sequence: 3 done + 2 skipped, refusal marker active, no accepted done.
const task = taskWith(["done", "done", "done", "skipped", "skipped"], "2026-07-16T21:40:00.000Z");
const result = (executor as any).evaluateImplicitCompletionRefusal(task, new Map());
const result = evaluateImplicitCompletionRefusal(task, new Map()) as any;
expect(result.ok).toBe(false);
expect(result.refusalClass).toBe("bulk-step-completion-without-review");
expect(result.reason).toContain("skipped after a bulk-step-completion refusal");
});
it("implicit completion is ALLOWED for a clean all-done/skipped task with no refusal marker", () => {
const executor = new TaskExecutor(createStore(), "/tmp/test");
const task = taskWith(["done", "skipped"], undefined);
const result = (executor as any).evaluateImplicitCompletionRefusal(task, new Map());
const result = evaluateImplicitCompletionRefusal(task, new Map());
expect(result).toEqual({ ok: true });
});
it("implicit completion is ALLOWED for a tainted task once every step is genuinely done (no skips left)", () => {
const executor = new TaskExecutor(createStore(), "/tmp/test");
const task = taskWith(["done", "done", "done"], "2026-07-16T21:40:00.000Z");
const result = (executor as any).evaluateImplicitCompletionRefusal(task, new Map());
const result = evaluateImplicitCompletionRefusal(task, new Map());
expect(result).toEqual({ ok: true });
});

View File

@@ -12,34 +12,34 @@ export type SeverityManifestEntry = {
};
export const logSeverityManifest: SeverityManifestEntry[] = [
{ pkg: "engine", file: "plugin-runner.ts", anchor: "condition evaluated false", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Executor runtime environment event: ${event}", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Tool execution event: ${event}", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Runtime instantiation event: ${event}", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "debug: (...args: unknown[]) => this.log.debug(prefix, ...args)", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Tools cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Routes cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "UI slots cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "UI contributions cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Runtimes cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "CLI provider contributions cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Skills cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "MCP servers cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Workflow steps cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Workflow extensions cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Workflow step templates cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Plugin traits cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Prompt contributions cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Setup cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "condition evaluated false", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Executor runtime environment event: ${event}", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Tool execution event: ${event}", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Runtime instantiation event: ${event}", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "debug: (...args: unknown[]) => this.log.debug(prefix, ...args)", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Tools cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Routes cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "UI slots cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "UI contributions cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Runtimes cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "CLI provider contributions cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Skills cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "MCP servers cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Workflow steps cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Workflow extensions cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Workflow step templates cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Plugin traits cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Prompt contributions cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Setup cache invalidated", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "self-healing.ts", anchor: "Cleaned ${cleaned} stale AI merge temp worktree(s)", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "self-healing.ts", anchor: "cleanup-old-chats\" removed stale data", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "self-healing.ts", anchor: "cleanup-old-mail\" removed stale data", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "self-healing.ts", anchor: "Auto-archiving ${stale.length}", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "self-healing.ts", anchor: "auto-archive: archived", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "self-healing.ts", anchor: "Auto-archived ${archived} stale done task(s)", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "pty-native.ts", anchor: "Pre-loaded native module via dlopen", priorSeverity: "console", severity: "debug" },
{ pkg: "engine", file: "pty-native.ts", anchor: "dlopen pre-load failed (continuing)", priorSeverity: "console", severity: "debug" },
{ pkg: "engine", file: "goal-anchoring-audit.ts", anchor: "goal retrieval audit emission skipped", priorSeverity: "console", severity: "debug" },
{ pkg: "engine", file: "cli-runtime/pty-native.ts", anchor: "Pre-loaded native module via dlopen", priorSeverity: "console", severity: "debug" },
{ pkg: "engine", file: "cli-runtime/pty-native.ts", anchor: "dlopen pre-load failed (continuing)", priorSeverity: "console", severity: "debug" },
{ pkg: "engine", file: "goals/goal-anchoring-audit.ts", anchor: "goal retrieval audit emission skipped", priorSeverity: "console", severity: "debug" },
{ pkg: "engine", file: "runtimes/child-process-worker.ts", anchor: "Child process worker starting", priorSeverity: "log", severity: "debug" },
/*
FNXC:EngineDiagnostics 2026-07-30-04:00:
@@ -93,8 +93,8 @@ export const logSeverityManifest: SeverityManifestEntry[] = [
triple sites, and zero-recovery gates are pinned in the spam-contract suite instead.
*/
{ pkg: "engine", file: "triage.ts", anchor: "planLog.debug(`${task.id}: planning in ${leanPlanning ? \"fast\" : \"standard\"} mode`)", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "session-token-usage.ts", anchor: "cacheMetricsLog.debug(JSON.stringify({", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "executor.ts", anchor: "tokenCacheMetricsLog.debug(JSON.stringify({", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "execution/session-token-usage.ts", anchor: "cacheMetricsLog.debug(JSON.stringify({", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "executor/persist-token-usage.ts", anchor: "tokenCacheMetricsLog.debug(JSON.stringify({", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "runtimes/in-process-runtime.ts", anchor: "runtimeLog.debug(`Specifying ${t.id}...`)", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "triage.ts", anchor: "planLog.debug(`${taskId}: failed to read PROMPT.md during ${context} (${promptPath}): ${msg}`)", priorSeverity: "warn", severity: "debug" },
/*
@@ -106,19 +106,19 @@ export const logSeverityManifest: SeverityManifestEntry[] = [
{ pkg: "engine", file: "scheduler.ts", anchor: "No linked feature found for task ${taskId}", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "scheduler.ts", anchor: "Task created — triggering scheduling", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "scheduler.ts", anchor: "Task moved to ${to} — triggering scheduling", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "auto-claim-snapshot.ts", anchor: "invalidate reason=${reason}", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "scheduling/auto-claim-snapshot.ts", anchor: "invalidate reason=${reason}", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "agent-heartbeat.ts", anchor: "Assignment trigger skipped for ${agent.id} (ephemeral/internal)", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "agent-heartbeat.ts", anchor: "Assignment trigger skipped for ${agent.id} (disabled)", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "agent-heartbeat.ts", anchor: "Assignment trigger skipped for ${agent.id} (active run)", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "runtimes/in-process-runtime.ts", anchor: "Scheduled task ${task.id}", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "runtimes/in-process-runtime.ts", anchor: "Started executing task ${task.id} in ${worktreePath}", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "executor.ts", anchor: "executorLog.debug(`${task.id}: executor runtime env injected", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "executor.ts", anchor: "executorLog.debug(`${live.id}: graph node '${node.id}' runtime env injected", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "executor.ts", anchor: "executorLog.debug(`${task.id}: workflow node '${nodeId}' acquired worktree", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "executor.ts", anchor: "executorLog.debug(`${task.id}: captured baseCommitSha ${baseCommitSha.slice(0, 7)}`)", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "executor.ts", anchor: "executorLog.debug(`${taskId}: reconcile step source governed by parse-steps", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "run-verification-tool.ts", anchor: "executorLog.debug(`[fn_run_verification] command failed (exit=", priorSeverity: "warn", severity: "debug" },
{ pkg: "engine", file: "worktree-acquisition.ts", anchor: "logger.debug(`Reusing existing worktree: ${path}`)", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "worktree-acquisition.ts", anchor: "logger.debug(`Reusing existing worktree: ${worktreePath}`)", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "executor/run-implementation.ts", anchor: "executorLog.debug(`${task.id}: executor runtime env injected", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "executor/run-graph-custom-node.ts", anchor: "executorLog.debug(`${live.id}: graph node '${node.id}' runtime env injected", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "executor/ensure-graph-custom-node-worktree.ts", anchor: "executorLog.debug(`${task.id}: workflow node '${nodeId}' acquired worktree", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "executor/worktree-git-refs.ts", anchor: "executorLog.debug(`${task.id}: captured baseCommitSha ${baseCommitSha.slice(0, 7)}`)", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "executor/reconcile-steps-from-git-history.ts", anchor: "executorLog.debug(`${taskId}: reconcile step source governed by parse-steps", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "execution/run-verification-tool.ts", anchor: "executorLog.debug(`[fn_run_verification] command failed (exit=", priorSeverity: "warn", severity: "debug" },
{ pkg: "engine", file: "worktree/worktree-acquisition.ts", anchor: "logger.debug(`Reusing existing worktree: ${path}`)", priorSeverity: "log", severity: "debug" },
{ pkg: "engine", file: "worktree/worktree-acquisition.ts", anchor: "logger.debug(`Reusing existing worktree: ${worktreePath}`)", priorSeverity: "log", severity: "debug" },
{ pkg: "core", file: "postgres/embedded-lifecycle.ts", anchor: "log.debug(`embedded postgres: already running on port", priorSeverity: "log", severity: "debug" },
];

View File

@@ -63,7 +63,7 @@ describe("log severity spam contract (source)", () => {
it("all type=info skill diagnostics (Requested skill listings and not-found) route to debug", () => {
const pi = readSrc("pi.ts");
const resolver = readSrc("skill-resolver.ts");
const resolver = readSrc("cli-runtime/skill-resolver.ts");
expect(pi).toMatch(/else if \(diag\.type === "info"\) piLog\.debug\(msg\)/);
expect(resolver).toMatch(/isSkillInfoDiagnostic/);
expect(resolver).toMatch(/else if \(isSkillInfoDiagnostic\(diag\)\) piLog\.debug\(msg\)/);
@@ -71,7 +71,7 @@ describe("log severity spam contract (source)", () => {
});
it("activity-recorded heartbeats and periodic stuck poll use debug", () => {
const src = readSrc("stuck-task-detector.ts");
const src = readSrc("healing/stuck-task-detector.ts");
expect(src).toMatch(/stuckLog\.debug\(\s*`Activity recorded for/);
expect(src).toMatch(/stuckLog\.debug\("Running periodic stuck task check \(polling\)"\)/);
expect(src).not.toMatch(/stuckLog\.log\(\s*`Activity recorded for/);
@@ -86,26 +86,26 @@ describe("log severity spam contract (source)", () => {
});
it("cron multi-scope skip chatter uses debug; execute stays on log", () => {
const src = readSrc("cron-runner.ts");
const src = readSrc("scheduling/cron-runner.ts");
expect(src).toMatch(/log\.debug\(`Skipping \$\{schedule\.name\}[\s\S]*already executed from another scope/);
expect(src).toMatch(/log\.debug\(`Skipping \$\{schedule\.name\}[\s\S]*claim lost to another poller/);
expect(src).toMatch(/log\.log\(`Executing \$\{schedule\.name\}/);
});
it("plugin skill contribution/merge chatter uses debug", () => {
const src = readSrc("session-skill-context.ts");
const src = readSrc("cli-runtime/session-skill-context.ts");
expect(src).toMatch(/piLog\.debug\(`\[skills\] Plugin \$\{pluginId\} contributes skill:/);
expect(src).toMatch(/piLog\.debug\(\s*`\[skills\] Merged \$\{appendedPluginNames\.length\}/);
});
it("hold-release capacity race uses debug like deferred-no-slot", () => {
const src = readSrc("hold-release.ts");
const src = readSrc("execution/hold-release.ts");
expect(src).toMatch(/schedulerLog\.debug\(`Hold release for \$\{task\.id\} rejected on capacity/);
expect(src).toMatch(/schedulerLog\.debug\(`Hold release for \$\{task\.id\} deferred — no reservable slot/);
});
it("routine-scheduler re-entrance and pause no-ops use debug", () => {
const src = readSrc("routine-scheduler.ts");
const src = readSrc("scheduling/routine-scheduler.ts");
expect(src).toMatch(/logger\.debug\("Tick already in progress, skipping"\)/);
expect(src).toMatch(/logger\.debug\(\s*`Paused: globalPause=/);
expect(src).not.toMatch(/logger\.log\("Tick already in progress, skipping"\)/);
@@ -113,7 +113,7 @@ describe("log severity spam contract (source)", () => {
});
it("peer-exchange zero-work sync cycle uses debug; non-zero/error stay on log", () => {
const src = readSrc("peer-exchange-service.ts");
const src = readSrc("project/peer-exchange-service.ts");
expect(src).toMatch(/peerExchangeLog\.debug\(`Starting sync with \$\{onlineRemoteNodes\.length\} peers`\)/);
expect(src).toMatch(/peerExchangeLog\.debug\(\s*`Sync complete: \$\{onlineRemoteNodes\.length\} peers synced\./);
// Non-zero discovery path and error summary remain log
@@ -135,15 +135,15 @@ describe("log severity spam contract (source)", () => {
exclusions, zero-recovery no-ops, and metrics JSON must stay off default info/warn.
*/
it("session setup, track bookkeeping, skill exclusion, and zero-recovery stay debug-gated", () => {
const session = readSrc("agent-session-helpers.ts");
const stuck = readSrc("stuck-task-detector.ts");
const session = readSrc("agents/agent-session-helpers.ts");
const stuck = readSrc("healing/stuck-task-detector.ts");
const triage = readSrc("triage.ts");
const resolver = readSrc("skill-resolver.ts");
const resolver = readSrc("cli-runtime/skill-resolver.ts");
const selfHealing = readSrc("self-healing.ts");
const mission = readSrc("mission-execution-loop.ts");
const mission = readSrc("missions/mission-execution-loop.ts");
const runtime = readSrc("runtimes/in-process-runtime.ts");
const tokenUsage = readSrc("session-token-usage.ts");
const executor = readSrc("executor.ts");
const tokenUsage = readSrc("execution/session-token-usage.ts");
const executor = readSrc("executor/persist-token-usage.ts");
expect(session).toMatch(/sessionLog\.debug\(\s*`\[\$\{sessionPurpose\}\] grok-cli fallback/);
expect(session).toMatch(/sessionLog\.debug\(\s*`\[\$\{sessionPurpose\}\] Using runtime/);
@@ -178,7 +178,7 @@ describe("log severity spam contract (source)", () => {
it("self-healing no-action/skip, worktree-pool probes, and ntfy bookkeeping use debug", () => {
const sh = readSrc("self-healing.ts");
const wt = readSrc("worktree-pool.ts");
const wt = readSrc("worktree/worktree-pool.ts");
const ntfy = readSrc("notification/ntfy-provider.ts");
const notify = readSrc("notification/notification-service.ts");
expect(sh).toMatch(/log\.debug\(`\[\$\{stage\}\] \$\{task\.id\}: triple-proof not satisfied — no action/);
@@ -192,7 +192,7 @@ describe("log severity spam contract (source)", () => {
});
it("pi session-purpose runtime routing and skip bookkeeping uses debug", () => {
const runtime = readSrc("runtime-resolution.ts");
const runtime = readSrc("execution/runtime-resolution.ts");
const pi = readSrc("pi.ts");
expect(runtime).toMatch(/runtimeLog\.debug\(`\[\$\{sessionPurpose\}\] No runtime hint configured/);
expect(runtime).toMatch(/runtimeLog\.debug\(`\[\$\{sessionPurpose\}\] Runtime hint is "pi\/default"/);
@@ -203,8 +203,8 @@ describe("log severity spam contract (source)", () => {
});
it("checkpoint bookkeeping (self-improve + RETHINK rewind) uses debug", () => {
const improve = readSrc("agent-self-improve.ts");
const step = readSrc("step-runner.ts");
const improve = readSrc("agents/agent-self-improve.ts");
const step = readSrc("execution/step-runner.ts");
expect(improve).toMatch(/selfImproveLog\.debug\(`Recorded self-improve checkpoint for/);
expect(step).toMatch(/executorLog\.debug\(`\$\{taskId\}: RETHINK — session rewound to checkpoint/);
expect(step).toMatch(/executorLog\.debug\(`\$\{taskId\}: RETHINK — no session checkpoint for step/);
@@ -213,7 +213,7 @@ describe("log severity spam contract (source)", () => {
it("merger intermediate plumbing uses debug; outcomes stay at log", () => {
const merger = readSrc("merger.ts");
const conflict = readSrc("merger-conflict-resolution.ts");
const conflict = readSrc("merge/merger-conflict-resolution.ts");
expect(conflict).toMatch(/mergerLog\.debug\(`Auto-resolved \$\{filePath\} using --ours`\)/);
expect(merger).toMatch(/mergerLog\.debug\(`\$\{taskId\}: merge details stored/);
expect(merger).toMatch(/mergerLog\.debug\(`\$\{taskId\}: git pull --rebase succeeded/);
@@ -225,8 +225,8 @@ describe("log severity spam contract (source)", () => {
});
it("foreach step skip/rework and per-step success use debug", () => {
const foreach = readSrc("workflow-graph-foreach.ts");
const exec = readSrc("executor.ts");
const foreach = readSrc("workflows/workflow-graph-foreach.ts");
const exec = readSrc("executor/run-implementation.ts");
expect(foreach).toMatch(/schedulerLog\.debug\(\s*`foreach \$\{foreachNode\.id\} for task \$\{env\.task\.id\}: skipping step/);
expect(foreach).toMatch(/schedulerLog\.debug\(\s*`foreach \$\{foreachNode\.id\} step \$\{inst\.stepIndex\}: integration-conflict/);
expect(foreach).not.toMatch(/schedulerLog\.log\(\s*`foreach \$\{foreachNode\.id\} for task/);
@@ -235,21 +235,25 @@ describe("log severity spam contract (source)", () => {
});
it("executor high-frequency dispatch/session bookkeeping uses debug", () => {
const src = readSrc("executor.ts");
expect(src).toMatch(/executorLog\.debug\(`TaskExecutor constructed/);
expect(src).toMatch(/executorLog\.debug\(`execute\(\) called for \$\{task\.id\} \(claimed=/);
expect(src).toMatch(/executorLog\.debug\(`\$\{task\.id\}: worktree ready at/);
expect(src).toMatch(/executorLog\.debug\(`\$\{task\.id\}: session registered/);
expect(src).toMatch(/executorLog\.debug\(`\$\{task\.id\}: calling promptWithFallback/);
expect(src).toMatch(/executorLog\.debug\(`\[workflow-graph\] \$\{event\.type\}/);
expect(src).toMatch(/executorLog\.debug\(`\[workflow-column-boundary\]/);
// FNXC:EngineDiagnostics 2026-08-15-19:10: executor.ts peels (package code organization waves) moved these call sites into executor/ modules; anchors verified moved, not duplicated.
const impl = readSrc("executor/run-implementation.ts");
const lifecycle = readSrc("executor/wire-executor-lifecycle.ts");
const graph = readSrc("executor/execute-workflow-graph.ts");
const boundary = readSrc("executor/build-column-boundary-hooks.ts");
expect(lifecycle).toMatch(/executorLog\.debug\(`TaskExecutor constructed/);
expect(impl).toMatch(/executorLog\.debug\(`execute\(\) called for \$\{task\.id\} \(claimed=/);
expect(impl).toMatch(/executorLog\.debug\(`\$\{task\.id\}: worktree ready at/);
expect(impl).toMatch(/executorLog\.debug\(`\$\{task\.id\}: session registered/);
expect(impl).toMatch(/executorLog\.debug\(`\$\{task\.id\}: calling promptWithFallback/);
expect(graph).toMatch(/executorLog\.debug\(`\[workflow-graph\] \$\{event\.type\}/);
expect(boundary).toMatch(/executorLog\.debug\(`\[workflow-column-boundary\]/);
// Lifecycle outcomes stay at log
expect(src).toMatch(/executorLog\.log\(`Starting \$\{task\.id\}:/);
expect(src).not.toMatch(/executorLog\.log\(`execute\(\) called for \$\{task\.id\} \(claimed=/);
expect(impl).toMatch(/executorLog\.log\(`Starting \$\{task\.id\}:/);
expect(impl).not.toMatch(/executorLog\.log\(`execute\(\) called for \$\{task\.id\} \(claimed=/);
});
it("MCP server connected success chatter uses logger.debug", () => {
const src = readSrc("mcp-session-tools.ts");
const src = readSrc("mcp/mcp-session-tools.ts");
expect(src).toMatch(/opts\.logger\.debug\(connectMsg\)/);
expect(src).toMatch(/MCP server connected for pi session/);
// Must not route the success line only through log without preferring debug.
@@ -257,10 +261,10 @@ describe("log severity spam contract (source)", () => {
});
it("fn_run_verification and green verification result paths use debug", () => {
const tool = readSrc("run-verification-tool.ts");
const utils = readSrc("verification-utils.ts");
const stuck = readSrc("stuck-task-detector.ts");
const exec = readSrc("executor.ts");
const tool = readSrc("execution/run-verification-tool.ts");
const utils = readSrc("execution/verification-utils.ts");
const stuck = readSrc("healing/stuck-task-detector.ts");
const exec = readSrc("executor/deterministic-verification.ts");
expect(tool).toMatch(/executorLog\.debug\(\s*`\[fn_run_verification\] command quiet for/);
expect(tool).toMatch(/\(log\.debug \?\? log\.info\)\(/);
expect(tool).toMatch(/if \(result\.success\) \{\s*\(log\.debug \?\? log\.info\)\(/);
@@ -285,10 +289,14 @@ describe("log severity spam contract (source)", () => {
it("busy-board lifecycle noise stays debug-gated; starts and creates stay log", () => {
const scheduler = readSrc("scheduler.ts");
const runtime = readSrc("runtimes/in-process-runtime.ts");
const exec = readSrc("executor.ts");
// FNXC:EngineDiagnostics 2026-08-15-19:10: executor.ts peels split these anchors across executor/ modules.
const execImpl = readSrc("executor/run-implementation.ts");
const execWorktreeCreate = readSrc("executor/worktree-create-conflict.ts");
const execGitRefs = readSrc("executor/worktree-git-refs.ts");
const execNodeWorktree = readSrc("executor/ensure-graph-custom-node-worktree.ts");
const heartbeat = readSrc("agent-heartbeat.ts");
const autoClaim = readSrc("auto-claim-snapshot.ts");
const worktree = readSrc("worktree-acquisition.ts");
const autoClaim = readSrc("scheduling/auto-claim-snapshot.ts");
const worktree = readSrc("worktree/worktree-acquisition.ts");
expect(scheduler).toMatch(/schedulerLog\.debug\(`No linked feature found for task/);
expect(scheduler).toMatch(/schedulerLog\.debug\("Task created — triggering scheduling"\)/);
@@ -300,12 +308,12 @@ describe("log severity spam contract (source)", () => {
expect(runtime).toMatch(/runtimeLog\.debug\(`Started executing task \$\{task\.id\} in \$\{worktreePath\}`\)/);
expect(runtime).not.toMatch(/runtimeLog\.log\(`Scheduled task \$\{task\.id\}`\)/);
expect(exec).toMatch(/executorLog\.log\(`Starting \$\{task\.id\}:/);
expect(exec).toMatch(/executorLog\.log\(`Worktree created:/);
expect(exec).toMatch(/executorLog\.debug\(`\$\{task\.id\}: executor runtime env injected/);
expect(exec).toMatch(/executorLog\.debug\(`\$\{task\.id\}: captured baseCommitSha/);
expect(exec).toMatch(/executorLog\.debug\(`\$\{task\.id\}: workflow node '\$\{nodeId\}' acquired worktree/);
expect(exec).not.toMatch(/executorLog\.log\(`\$\{task\.id\}: executor runtime env injected/);
expect(execImpl).toMatch(/executorLog\.log\(`Starting \$\{task\.id\}:/);
expect(execWorktreeCreate).toMatch(/executorLog\.log\(`Worktree created:/);
expect(execImpl).toMatch(/executorLog\.debug\(`\$\{task\.id\}: executor runtime env injected/);
expect(execGitRefs).toMatch(/executorLog\.debug\(`\$\{task\.id\}: captured baseCommitSha/);
expect(execNodeWorktree).toMatch(/executorLog\.debug\(`\$\{task\.id\}: workflow node '\$\{nodeId\}' acquired worktree/);
expect(execImpl).not.toMatch(/executorLog\.log\(`\$\{task\.id\}: executor runtime env injected/);
expect(heartbeat).toMatch(/heartbeatLog\.debug\(`Assignment trigger skipped for \$\{agent\.id\} \(ephemeral\/internal\)`\)/);
expect(heartbeat).not.toMatch(/heartbeatLog\.log\(`Assignment trigger skipped for \$\{agent\.id\} \(ephemeral\/internal\)`\)/);

View File

@@ -90,7 +90,19 @@ function sourceRoots(base: string = REPO_ROOT): string[] {
* means a second owner now writes the claim, which is the FN-8504 shape. If that
* is genuinely intended, the justification belongs beside the new entry.
*/
const PLANNING_CLAIM_WRITERS = ["packages/engine/src/triage.ts"];
/*
FNXC:PlanningClaimSingleWriter 2026-08-15-19:10 (FN-8998, d9a0ed7837):
`self-healing.ts` is a SECOND intended writer, added by FN-8998's planning-lock
reentry fix: `recordPlanningHandoffTransportFailure` RE-asserts `status: "planning"`
(plus retry bookkeeping) on a card whose canonical planning handoff hit a transport
failure, so the retry sweep can pick it up instead of stalling the lock. It is not a
fresh claim by a second planner: the write goes through `updateTaskAtomic` with an
`isTaskStillInPlanningStage(live)` re-check, the same guarded CAS shape the
"planning-stage-guarded helper" assertion below demands of triage, so it cannot
stamp `planning` onto a card the scheduler already advanced (the FN-7977/FN-8361 bug
this ratchet exists to prevent).
*/
const PLANNING_CLAIM_WRITERS = ["packages/engine/src/self-healing.ts", "packages/engine/src/triage.ts"];
/**
* Modules permitted to BIND the planning literal to a constant.

View File

@@ -14,8 +14,8 @@ const REPO_ROOT = resolve(__dirname, "../../../..");
const ENGINE_ROOT = join(REPO_ROOT, "packages/engine/src");
const DROP_BRIDGES = {
"packages/engine/src/hybrid-executor.ts": "HybridExecutor",
"packages/engine/src/project-manager.ts": "ProjectManager",
"packages/engine/src/concurrency/hybrid-executor.ts": "HybridExecutor",
"packages/engine/src/project/project-manager.ts": "ProjectManager",
"packages/engine/src/runtimes/child-process-runtime.ts": "ChildProcessRuntime",
"packages/engine/src/runtimes/in-process-runtime.ts": "InProcessRuntime",
"packages/engine/src/runtimes/remote-node-runtime.ts": "RemoteNodeRuntime",
@@ -67,6 +67,9 @@ describe("engine task:updated emit surface", () => {
taskStore: inProcessUpstream,
recordActivity: () => undefined,
config: { projectId: "project" },
// FNXC:WorkflowEvents 2026-08-15-19:10: the real task:updated registration now runs FN-7608-style approval-hold bookkeeping before re-emitting; the fixture supplies the sets so the actual forwarding body executes unchanged.
approvalHeldTaskIds: new Set<string>(),
approvalReleasedTaskIds: new Set<string>(),
});
(InProcessRuntime.prototype as any).setupEventForwarding.call(inProcess);

View File

@@ -46,7 +46,12 @@ FNXC:CodeOrganization 2026-08-03-15:20:
U4 Slice B peels createWorktree into worktree-create-outer.ts / worktree-create-conflict.ts;
worktree-acquisition lives under worktree/. Source-scan surfaces must follow the peels.
*/
const executorSource = readFileSync(fileURLToPath(new URL("../executor.ts", import.meta.url)), "utf8");
/*
FNXC:CodeOrganization 2026-08-15-19:10:
Later executor peel waves moved the createWorktree facade off the thin executor.ts shell into
executor/task-executor-worktree-pure-facades.ts (one-line delegation to createWorktreeImpl).
*/
const executorSource = readFileSync(fileURLToPath(new URL("../executor/task-executor-worktree-pure-facades.ts", import.meta.url)), "utf8");
const createOuterSource = readFileSync(fileURLToPath(new URL("../executor/worktree-create-outer.ts", import.meta.url)), "utf8");
const createConflictSource = readFileSync(fileURLToPath(new URL("../executor/worktree-create-conflict.ts", import.meta.url)), "utf8");
const acquisitionSource = readFileSync(fileURLToPath(new URL("../worktree/worktree-acquisition.ts", import.meta.url)), "utf8");
@@ -131,13 +136,20 @@ describe("TaskExecutor primary-checkout worktree invariant", () => {
*/
const executorFacade = sourceRegion(
executorSource,
"private async createWorktree(",
"private async removeOwnWorktreeWithReconcile(",
"protected async createWorktree(",
"disposeStoreLifecycleDisposers(",
);
// Full peeled modules: createWorktree is not first in worktree-create-outer.ts.
const outerImpl = createOuterSource;
const conflictImpl = createConflictSource;
const acquisition = sourceRegion(acquisitionSource, "const createWorktreeImpl = createWorktree", "const logConfiguredCopyFileResults");
/*
FNXC:CodeOrganization 2026-08-15-19:10:
Acquisition's creation slice was reshaped: `createWorktreeImpl` is no longer a bare alias of the
injected createWorktree — it now wraps `createWorktreeWithoutReservation` (which owns the
`backend.create(` call) with path-reservation handling. Scan from the reservation-free creator
so both creation surfaces stay inside the guarded region.
*/
const acquisition = sourceRegion(acquisitionSource, "const createWorktreeWithoutReservation = async (", "const logConfiguredCopyFileResults");
const mergerReacquire = sourceRegion(mergerSource, "const reacquireReuseIntegrationWorktree = async", "// 3b. Ensure rootDir is based on the resolved integration target before merging.");
expect(executorFacade).toContain("createWorktreeImpl");

View File

@@ -218,6 +218,16 @@ guards (86 -> 89 on #2883).
function isDependencySatisfiedWithoutWorkflowMetadata(column: string): boolean {
return column === "done" || column === "archived" || column === "in-review";
}
/*
DELIBERATE-LITERAL — legacy complete-lane fallback for the FN-9056 orphaned-workspace-worktree
sweep (FNXC:Workspace 2026-08-15-19:10). The sweep unions the workflow-resolved complete columns
with the legacy `done` id so a row parked under the default lifecycle before its workflow could be
read still qualifies for the complete lane. Hoisted so the marker sits in the declaration's leading
comments, which is where the census looks.
*/
function isLegacyCompleteColumnForWorkspaceTeardown(column: string): boolean {
return column === "done";
}
const worktreeMetadataReconcileLog = createLogger("worktree-metadata-reconcile");
/*
FNXC:EngineDiagnostics 2026-07-26-10:25:
@@ -10703,7 +10713,7 @@ const movedTask = await this.store.moveTask(task.id, completeLane);
|| this.isWorkspaceTaskLive(task).live
|| await this.options.isMergePending?.(task.id) === true
|| this.options.getActiveMergeTaskId?.() === task.id) continue;
if (completeColumns.has(task.column) || task.column === "done") { candidates.push({ task, lane: "complete" }); continue; }
if (completeColumns.has(task.column) || isLegacyCompleteColumnForWorkspaceTeardown(task.column)) { candidates.push({ task, lane: "complete" }); continue; }
const lane: Lane | null = task.deletedAt ? "soft-deleted" : task.status === "failed" ? "failed" : null;
if (!lane) continue;
const touched = Math.max(Date.parse(task.columnMovedAt ?? "") || 0, Date.parse(task.updatedAt ?? "") || 0, Date.parse(task.deletedAt ?? "") || 0);

View File

@@ -0,0 +1,41 @@
/*
FNXC:LifecycleColumnCensus 2026-08-13-21:58:
Mailbox folder tabs reuse the word `archived`. A bare `activeTab === "archived"` must stay a
column-guard hit so a genuine lifecycle comparison that happens to use that variable name still
fails `--strict`. The mailbox sites mark a helper with DELIBERATE-LITERAL instead of a global
receiver exemption.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { findComparisons } from "../lib/lifecycle-column-census-ast.mjs";
test("a bare activeTab archived comparison stays in the column backlog", () => {
const findings = findComparisons(
"t.ts",
'if (activeTab === "archived") loadArchivedInbox();',
);
assert.deepEqual(
findings.map(({ columnId, receiver, kind }) => ({ columnId, receiver, kind })),
[{ columnId: "archived", receiver: "activeTab", kind: "column" }],
);
});
test("a mailbox archived-tab helper with DELIBERATE-LITERAL is not backlog", () => {
const findings = findComparisons(
"MailboxView.tsx",
`/*
DELIBERATE-LITERAL — mailbox folder tab, not a board column.
*/
function isMailboxArchivedTab(tab) {
return tab === "archived";
}
`,
);
assert.deepEqual(
findings.map(({ columnId, receiver, kind }) => ({ columnId, receiver, kind })),
[{ columnId: "archived", receiver: "tab", kind: "deliberate" }],
);
});

View File

@@ -6,6 +6,7 @@
"packages/dashboard/app/components/TaskContextMenu.tsx\u0000in-review": 3,
"packages/engine/src/scheduler.ts\u0000in-progress": 3,
"packages/engine/src/scheduler.ts\u0000in-review": 3,
"packages/engine/src/self-healing.ts\u0000done": 3,
"packages/engine/src/self-healing.ts\u0000in-review": 3,
"packages/core/src/agents/live-agent-count.ts\u0000in-progress": 2,
"packages/core/src/agents/live-agent-count.ts\u0000in-review": 2,
@@ -32,7 +33,6 @@
"packages/engine/src/merge/auto-merge-finalization.ts\u0000done": 2,
"packages/engine/src/scheduler.ts\u0000archived": 2,
"packages/engine/src/scheduler.ts\u0000done": 2,
"packages/engine/src/self-healing.ts\u0000done": 2,
"packages/engine/src/triage.ts\u0000triage": 2,
"plugins/fusion-plugin-reports/src/store/report-store.ts\u0000archived": 2,
"packages/cli/src/commands/task.ts\u0000archived": 1,
@@ -67,12 +67,15 @@
"packages/core/src/tasks/task-intake-owner-resolver.ts\u0000todo": 1,
"packages/core/src/tasks/task-move-disposer.ts\u0000in-progress": 1,
"packages/core/src/tasks/task-move-disposer.ts\u0000todo": 1,
"packages/core/src/tasks/workspace-lease-types.ts\u0000done": 1,
"packages/dashboard/app/components/command-center/liveSnapshotMetrics.ts\u0000in-progress": 1,
"packages/dashboard/app/components/command-center/MissionControlPanel.tsx\u0000done": 1,
"packages/dashboard/app/components/command-center/MissionControlPanel.tsx\u0000in-review": 1,
"packages/dashboard/app/components/command-center/MissionControlPanel.tsx\u0000todo": 1,
"packages/dashboard/app/components/DocumentsView.tsx\u0000archived": 1,
"packages/dashboard/app/components/DocumentsView.tsx\u0000done": 1,
"packages/dashboard/app/components/MailboxModal.tsx\u0000archived": 1,
"packages/dashboard/app/components/MailboxView.tsx\u0000archived": 1,
"packages/dashboard/app/components/MissionManager.tsx\u0000archived": 1,
"packages/dashboard/app/components/RoutineEditor.tsx\u0000triage": 1,
"packages/dashboard/app/components/ScheduleForm.tsx\u0000triage": 1,