refactor(workspace): extract resolveWorkspaceWithLockMode shared helper

P1-21: worker-crash-recovery reinvented workspace resolution inline and
silently dropped the exclusive/shared distinction — a recovered run
could claim shared mode that the original run held exclusively.
Introduce resolve-workspace.ts as the single source of truth for the
task→agent→project→company fallback chain and its lockMode, used by
both run-dispatch and crash recovery.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-16 23:00:13 -04:00
1 parent 6ce4fd1fe4
commit a852597d2e
3 files changed
+49 -37

No files matched your search

@@ -0,0 +1,43 @@
import type { AppDatabase } from "../database";
import type { WorkspaceLockMode } from "./types";
/**
* Resolve the workspace and lock semantics for a given run context.
*
* Previously this logic lived both in run-dispatch.ts (normal dispatch path)
* and worker-crash-recovery.ts (re-enqueue after crash); the recovery
* copy silently dropped the exclusive/shared distinction. Sharing the
* helper guarantees both paths agree on which runs hold exclusive locks.
*/
export function resolveWorkspaceWithLockMode(
db: AppDatabase,
task: { workspaceId: string | null; projectId?: string | null },
agent: { workspaceId: string | null },
companyId: string,
): {
workspace: ReturnType<AppDatabase["getWorkspace"]>;
lockMode: WorkspaceLockMode;
source: "task" | "agent" | "project" | "company_fallback" | "none";
} {
const taskWorkspace = db.getWorkspace(task.workspaceId);
if (taskWorkspace) {
return { workspace: taskWorkspace, lockMode: "exclusive", source: "task" };
}
const agentWorkspace = db.getWorkspace(agent.workspaceId);
if (agentWorkspace) {
return { workspace: agentWorkspace, lockMode: "exclusive", source: "agent" };
}
const projectWorkspace = db.getProjectPrimaryWorkspace(task.projectId ?? null, companyId);
if (projectWorkspace) {
return { workspace: projectWorkspace, lockMode: "exclusive", source: "project" };
}
const fallbackWorkspace = db.getAnyCompanyWorkspace(companyId);
if (fallbackWorkspace) {
return { workspace: fallbackWorkspace, lockMode: "shared", source: "company_fallback" };
}
return { workspace: null, lockMode: "none", source: "none" };
}
+3 -22
View File
@@ -1,8 +1,9 @@
import { getConnectorExecutionReadinessIssue } from "@shared/connector-policy";
import type { ConnectorRecord } from "@shared/types";
import type { WorkspaceLockMode, OrchestrationDependencies } from "./types";
import type { OrchestrationDependencies } from "./types";
import { buildRunPayload, type RunDispatchParams } from "./turn-context";
import { validateConnectorModelSelection } from "../connector-model-policy";
import { resolveWorkspaceWithLockMode } from "./resolve-workspace";
export function createRunDispatch(deps: OrchestrationDependencies) {
const {
@@ -24,27 +25,7 @@ export function createRunDispatch(deps: OrchestrationDependencies) {
agent: { workspaceId: string | null },
companyId: string,
) {
const taskWorkspace = db.getWorkspace(task.workspaceId);
if (taskWorkspace) {
return { workspace: taskWorkspace, lockMode: "exclusive" as WorkspaceLockMode, source: "task" as const };
}
const agentWorkspace = db.getWorkspace(agent.workspaceId);
if (agentWorkspace) {
return { workspace: agentWorkspace, lockMode: "exclusive" as WorkspaceLockMode, source: "agent" as const };
}
const projectWorkspace = db.getProjectPrimaryWorkspace(task.projectId ?? null, companyId);
if (projectWorkspace) {
return { workspace: projectWorkspace, lockMode: "exclusive" as WorkspaceLockMode, source: "project" as const };
}
const fallbackWorkspace = db.getAnyCompanyWorkspace(companyId);
if (fallbackWorkspace) {
return { workspace: fallbackWorkspace, lockMode: "shared" as WorkspaceLockMode, source: "company_fallback" as const };
}
return { workspace: null, lockMode: "none" as WorkspaceLockMode, source: "none" as const };
return resolveWorkspaceWithLockMode(db, task, agent, companyId);
}
function getRunEligibilityFailure(
+3 -15
View File
@@ -1,4 +1,5 @@
import type { WorkerServiceDependencies } from "./worker-types";
import { resolveWorkspaceWithLockMode } from "../orchestration/resolve-workspace";
export function handleWorkerExit(
code: number | null,
@@ -94,20 +95,7 @@ export function handleWorkerExit(
const task = db.getTask(candidate.taskId);
const agent = db.getAgent(candidate.agentId);
const connector = db.getConnector(agent.connectorId);
const workspaceResolution = {
taskWorkspace: db.getWorkspace(task.workspaceId),
agentWorkspace: db.getWorkspace(agent.workspaceId),
projectWorkspace: db.getProjectPrimaryWorkspace(task.projectId ?? null, candidate.companyId),
};
const workspace = workspaceResolution.taskWorkspace
?? workspaceResolution.agentWorkspace
?? workspaceResolution.projectWorkspace
?? db.getAnyCompanyWorkspace(candidate.companyId);
const workspaceMode = workspaceResolution.taskWorkspace || workspaceResolution.agentWorkspace || workspaceResolution.projectWorkspace
? "exclusive"
: workspace
? "shared"
: "none";
const { workspace, lockMode } = resolveWorkspaceWithLockMode(db, task, agent, candidate.companyId);
const recoveryCompany = recoverySnapshot.companies.find((company) => company.id === candidate.companyId);
dispatchRunToWorker({
@@ -117,7 +105,7 @@ export function handleWorkerExit(
task,
connector,
workspace,
workspaceMode,
workspaceMode: lockMode,
companyName: recoveryCompany?.name ?? "Company",
companyDescription: recoveryCompany?.description ?? "",
snapshot: recoverySnapshot,