Files
fusion/packages/engine/src/executor/acquire-session-registry-path.ts
gsxdsm 3dea1bb851 FN-9172: isolate executor run-audit emissions
Keep executor lifecycle transitions moving when run-audit sinks fail or stall.

- Add a shared bounded, best-effort executor audit emission seam.
- Route executor lifecycle audit writes through sink isolation.
- Cover absent, throwing, rejecting, hanging, and late-settling sinks.
- Document the emitter policy and publish a patch changeset.

Files changed:
 .changeset/fn-9172-executor-run-audit-isolation.md |   7 +
 AGENTS.md                                          |   7 +
 docs/run-audit.md                                  |   6 +-
 .../src/__tests__/emit-bounded-run-audit.test.ts   |  58 +++++
 .../executor-run-audit-emitter-isolation.test.ts   | 252 +++++++++++++++++++++
 .../src/executor/acquire-session-registry-path.ts  |   5 +-
 .../engine/src/executor/completion-finalization.ts |   3 +-
 .../engine/src/executor/create-task-done-tool.ts   |   3 +-
 .../engine/src/executor/emit-bounded-run-audit.ts  |  48 ++++
 .../executor/emit-merge-boundary-unproven-audit.ts |  68 ++----
 .../engine/src/executor/execute-workflow-graph.ts  |   5 +-
 .../engine/src/executor/handle-graph-failure.ts    |  18 +-
 ...dle-stale-in-review-parse-pause-abort-replay.ts |   7 +-
 ...ndle-stale-in-review-plan-pause-abort-replay.ts |   7 +-
 .../maybe-dispatch-workflow-work-engine.ts         |   7 +-
 .../src/executor/no-merge-complete-column.ts       |   3 +-
 .../reenter-paused-aborted-workflow-node.ts        |   7 +-
 .../src/executor/required-artifact-recovery.ts     |   3 +-
 packages/engine/src/executor/run-implementation.ts |   5 +-
 19 files changed, 433 insertions(+), 86 deletions(-)

Fusion-Task-Id: FN-9172

Fusion-Task-Lineage: 80c67c36-3bbb-4274-ac4c-9808c4cda3ce

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-08-19 20:35:19 -07:00

60 lines
2.6 KiB
TypeScript

/**
* FNXC:CodeOrganization 2026-08-03-17:30:
* acquireSessionRegistryPath peeled from TaskExecutor (U4).
*
* FNXC:SessionContention 2026-07-25-21:30 (contention prevention at the registration seam):
* Every executor session registration goes through acquireActiveSessionPath instead of the raw
* registerPath, so a LEAKED entry owned by a task with no live session surface in this process is
* RECLAIMED rather than throwing at the newcomer. That closes the second contention class (a dead
* holder can never release, so waiting on it is waiting forever). A genuinely live holder still throws
* the typed error — that case is real serialization, and callers classify it as a retryable contention
* hold (SESSION_CONTENTION_HOLD_VALUE), never as a provider/model failure.
* The probe reports LIVE on any uncertainty: an unknown holder with a fresh entry is treated as live by
* the staleness floor, so the reclaim only ever fires on proven-dead, aged entries.
*/
import type { TaskStore } from "@fusion/core";
import {
ActiveSessionPathHeldByForeignTaskError,
acquireActiveSessionPath,
activeSessionRegistry,
executingTaskLock,
type ActiveSessionKind,
} from "../agents/active-session-registry.js";
import { executorLog } from "../logger.js";
import { generateSyntheticRunId } from "../util/run-audit.js";
import { emitBoundedRunAudit } from "./emit-bounded-run-audit.js";
export type AcquireSessionRegistryPathDeps = {
store: TaskStore;
hasLiveTaskSessionSurface: (taskId: string) => boolean;
};
export function acquireSessionRegistryPath(
deps: AcquireSessionRegistryPathDeps,
taskId: string,
registryPath: string,
kind: ActiveSessionKind,
ownerKey: string,
): void {
const outcome = acquireActiveSessionPath(activeSessionRegistry, registryPath, { taskId, kind, ownerKey }, {
holderLiveProbe: (holderTaskId) => deps.hasLiveTaskSessionSurface(holderTaskId) || executingTaskLock.has(holderTaskId),
});
if (outcome.action === "contended") {
throw new ActiveSessionPathHeldByForeignTaskError(registryPath, outcome.holderTaskId, taskId);
}
if (outcome.action === "reclaimed-stale-foreign") {
executorLog.warn(
`${taskId}: reclaimed a stale active-session entry on ${registryPath} from dead task ${outcome.holderTaskId} (idle ${outcome.ageMs}ms)`,
);
void emitBoundedRunAudit(deps.store, {
taskId,
agentId: "executor",
runId: generateSyntheticRunId("session-path-reclaim", taskId),
domain: "database",
mutationType: "session:reclaim-stale-foreign-path",
target: taskId,
metadata: { taskId, holderTaskId: outcome.holderTaskId, kind, ageMs: outcome.ageMs },
});
}
}