Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions src/durable-operations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,86 @@ test("dependency_sync frozen recipe succeeds without changing manifest or lock i
}
});

test("dependency_sync OWNER_DIRECT isolated mode does not require carrier authority", async () => {
const f = await fixture();
try {
const project = join(f.root, "project");
await mkdir(project);
await writeFile(join(project, "package.json"), JSON.stringify({ name: "fixture", version: "1.0.0" }) + "\n");
await writeFile(join(project, "package-lock.json"), JSON.stringify({ name: "fixture", version: "1.0.0", lockfileVersion: 3, packages: {} }) + "\n");
await git(project, "init");
await git(project, "config", "user.email", "devspace@example.com");
await git(project, "config", "user.name", "DevSpace Test");
await git(project, "add", ".");
await git(project, "commit", "-m", "fixture");
let calls = 0;
const runner: CommandRunner = async () => {
calls += 1;
return { exitCode: 0, stdout: "ok", stderr: "" };
};
const manager = new DurableOperationManager(f.config, runner);
try {
const result = await manager.dependencySync({
attemptKey: "deps-owner-direct-isolated-1",
workspaceId: "ws_isolated",
workspaceRoot: project,
recipe: "npm_ci",
authorityMode: "OWNER_DIRECT",
ownerDirectIsolated: true,
});
assert.equal(result.status, "succeeded");
assert.equal(calls, 1);
const witness = manager.store.readDependencyTerminal(result.operationId);
assert.equal(witness?.leaseId, "OWNER_DIRECT_ISOLATED");
assert.equal(witness?.requestHash, result.requestHash);
assert.equal(witness?.frozenInputsUnchanged, true);
assert.equal((result.request as Record<string, unknown>).ownerDirectIsolated, true);
} finally {
manager.close();
}
} finally {
await f.cleanup();
}
});

test("dependency_sync OWNER_DIRECT isolated reconciliation requires its exact terminal witness", async () => {
const f = await fixture();
try {
const project = join(f.root, "project");
await mkdir(project);
await writeFile(join(project, "package.json"), JSON.stringify({ name: "fixture", version: "1.0.0" }) + "\n");
await writeFile(join(project, "package-lock.json"), JSON.stringify({ name: "fixture", version: "1.0.0", lockfileVersion: 3, packages: {} }) + "\n");
await git(project, "init");
await git(project, "config", "user.email", "devspace@example.com");
await git(project, "config", "user.name", "DevSpace Test");
await git(project, "add", ".");
await git(project, "commit", "-m", "fixture");
const manager = new DurableOperationManager(f.config, async () => {
throw new Error("simulated unacknowledged dependency process");
});
try {
const result = await manager.dependencySync({
attemptKey: "deps-owner-direct-isolated-unknown",
workspaceId: "ws_isolated",
workspaceRoot: project,
recipe: "npm_ci",
authorityMode: "OWNER_DIRECT",
ownerDirectIsolated: true,
});
assert.equal(result.status, "outcome_unknown");
assert.throws(
() => manager.reconcileOwnerDirectDependencySync(result.operationId),
/No exact terminal witness exists/,
);
assert.equal(manager.store.getByOperationId(result.operationId)?.status, "outcome_unknown");
} finally {
manager.close();
}
} finally {
await f.cleanup();
}
});

test("dependency_sync rejects a changed request before creating an operation or pin", async () => {
const f = await fixture();
try {
Expand Down
188 changes: 145 additions & 43 deletions src/durable-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,31 @@ export class DurableOperationStore {
.run(operationId,requestHash,leaseId,exitCode,frozenInputsUnchanged?1:0);
}

readDependencyTerminal(operationId: string): {
operationId: string;
requestHash: string;
leaseId: string;
exitCode: number;
frozenInputsUnchanged: boolean;
} | undefined {
const row = this.database.sqlite.prepare(
"select operation_id,request_hash,lease_id,exit_code,frozen_inputs_unchanged from dependency_terminal_witnesses where operation_id=?",
).get(operationId) as {
operation_id: string;
request_hash: string;
lease_id: string;
exit_code: number;
frozen_inputs_unchanged: number;
} | undefined;
return row ? {
operationId: row.operation_id,
requestHash: row.request_hash,
leaseId: row.lease_id,
exitCode: row.exit_code,
frozenInputsUnchanged: row.frozen_inputs_unchanged === 1,
} : undefined;
}

markInterruptedUnknown(): number {
const now = new Date().toISOString();
const preflight = this.database.sqlite.prepare(`
Expand Down Expand Up @@ -344,6 +369,12 @@ export interface DependencySyncInput {
workspaceRoot: string;
recipe: DependencySyncRecipe;
authorityMode?: ExecutionAuthorityMode;
/**
* Trusted server-side admission only. Callers cannot set this through MCP.
* Allows OWNER_DIRECT frozen dependency sync without a carrier only for a
* DevSpace-managed isolated worktree already admitted for this conversation.
*/
ownerDirectIsolated?: boolean;
}

export type CommandRunner = (
Expand Down Expand Up @@ -688,6 +719,7 @@ export class DurableOperationManager {
workspaceRoot,
recipe: input.recipe,
frozenInputs: before,
...(input.ownerDirectIsolated === true ? { ownerDirectIsolated: true } : {}),
};
const requestHash = hashJson(request);
const operationId = stableOperationId("dependency_sync", workspaceRoot, input.attemptKey);
Expand All @@ -696,64 +728,92 @@ export class DurableOperationManager {
}

async dependencySync(input: DependencySyncInput, consumerContext?: unknown): Promise<DurableOperationRecord> {
if (!this.consumer) throw new ControlPlaneOwnershipError("AUTHORITY_REQUIRED", "dependency sync requires a trusted host authority reader");
const ownerDirectIsolated = input.ownerDirectIsolated === true;
if (!ownerDirectIsolated && !this.consumer) {
throw new ControlPlaneOwnershipError("AUTHORITY_REQUIRED", "dependency sync requires a trusted host authority reader");
}
const consumer = this.consumer;
const {subject, request, workspaceRoot, authorityMode, frozenInputs, before, baseRevision, requestHash, operationId} = await this.planDependencySync(input);
const { record, created, binding, pinnedVersion } = this.store.atomic(() => {
const binding = consumer.authorize(consumerContext, subject);
const directWitnessId = "OWNER_DIRECT_ISOLATED";
const prepared = this.store.atomic(() => {
if (ownerDirectIsolated) {
const value = this.store.createOrReplay({
operationId,
attemptKey: input.attemptKey,
requestHash,
kind: "dependency_sync",
authorityMode,
scopeRoot: workspaceRoot,
workspaceId: input.workspaceId,
request,
});
return { ...value, binding: undefined, pinnedVersion: undefined };
}
const binding = consumer!.authorize(consumerContext, subject);
const value = this.store.createOrReplay({
operationId,
attemptKey: input.attemptKey,
requestHash,
kind: "dependency_sync",
authorityMode,
scopeRoot: workspaceRoot,
workspaceId: input.workspaceId,
request,
operationId,
attemptKey: input.attemptKey,
requestHash,
kind: "dependency_sync",
authorityMode,
scopeRoot: workspaceRoot,
workspaceId: input.workspaceId,
request,
});
const pinnedVersion = value.created ? consumer.pin(consumerContext, subject, binding) : binding.leaseVersion;
return {...value, binding, pinnedVersion};
const pinnedVersion = value.created ? consumer!.pin(consumerContext, subject, binding) : binding.leaseVersion;
return { ...value, binding, pinnedVersion };
});
const { record, created, binding, pinnedVersion } = prepared;
if (!created) return replayResult(record);

const finish = (patch: Parameters<DurableOperationStore["finish"]>[1]) => this.store.atomic(() => {
consumer.finish(consumerContext, subject, binding, pinnedVersion);
if (!ownerDirectIsolated) {
consumer!.finish(consumerContext, subject, binding!, pinnedVersion!);
}
return this.store.finish(operationId, patch);
});
try {
if (await readGitHead(workspaceRoot) !== baseRevision || hashJson(await hashFiles(workspaceRoot, frozenInputs)) !== hashJson(before)) {
throw new Error("Frozen dependency input or base revision changed before launch");
}
consumer.assertPinned(consumerContext, subject, binding, pinnedVersion);
const command = dependencyCommand(input.recipe);
const result = await this.runCommand(command.command, command.args, workspaceRoot);
if (result.exitCode === null) throw new Error("Command termination is unconfirmed");
const after = await hashFiles(workspaceRoot, frozenInputs);
const frozenInputsUnchanged = hashJson(before) === hashJson(after) && await readGitHead(workspaceRoot) === baseRevision;
this.store.recordDependencyTerminal(operationId,requestHash,binding.leaseId,result.exitCode,frozenInputsUnchanged);
if (!frozenInputsUnchanged) {
return finish({
status: "failed",
retrySafe: false,
errorCode: "FROZEN_INPUT_CHANGED",
errorMessage: "Dependency specification or lock input changed during a FROZEN dependency sync.",
receipt: { recipe: input.recipe, before, after, exitCode: result.exitCode },
});
}
if (result.exitCode !== 0) {
if (await readGitHead(workspaceRoot) !== baseRevision || hashJson(await hashFiles(workspaceRoot, frozenInputs)) !== hashJson(before)) {
throw new Error("Frozen dependency input or base revision changed before launch");
}
if (!ownerDirectIsolated) {
consumer!.assertPinned(consumerContext, subject, binding!, pinnedVersion!);
}
const command = dependencyCommand(input.recipe);
const result = await this.runCommand(command.command, command.args, workspaceRoot);
if (result.exitCode === null) throw new Error("Command termination is unconfirmed");
const after = await hashFiles(workspaceRoot, frozenInputs);
const frozenInputsUnchanged = hashJson(before) === hashJson(after) && await readGitHead(workspaceRoot) === baseRevision;
this.store.recordDependencyTerminal(
operationId,
requestHash,
ownerDirectIsolated ? directWitnessId : binding!.leaseId,
result.exitCode,
frozenInputsUnchanged,
);
if (!frozenInputsUnchanged) {
return finish({
status: "failed",
retrySafe: false,
errorCode: "FROZEN_INPUT_CHANGED",
errorMessage: "Dependency specification or lock input changed during a FROZEN dependency sync.",
receipt: { recipe: input.recipe, before, after, exitCode: result.exitCode },
});
}
if (result.exitCode !== 0) {
return finish({
status: "failed",
retrySafe: false,
errorCode: "DEPENDENCY_SYNC_FAILED",
errorMessage: redactSecrets(result.stderr || `${command.command} exited ${result.exitCode}`),
receipt: { recipe: input.recipe, frozenInputs: after, exitCode: result.exitCode },
});
}
return finish({
status: "failed",
status: "succeeded",
retrySafe: false,
errorCode: "DEPENDENCY_SYNC_FAILED",
errorMessage: redactSecrets(result.stderr || `${command.command} exited ${result.exitCode}`),
receipt: { recipe: input.recipe, frozenInputs: after, exitCode: result.exitCode },
});
}
return finish({
status: "succeeded",
retrySafe: false,
receipt: { recipe: input.recipe, frozenInputs: after, exitCode: result.exitCode },
});
} catch (error) {
return this.store.atomic(() => {
if (JSON.stringify(this.store.getByOperationId(operationId)) !== JSON.stringify(record)) {
Expand All @@ -764,6 +824,45 @@ export class DurableOperationManager {
}
}

reconcileOwnerDirectDependencySync(operationId: string): DurableOperationRecord {
return this.store.atomic(() => {
const record = this.store.getByOperationId(operationId);
if (
!record ||
record.kind !== "dependency_sync" ||
record.authorityMode !== "OWNER_DIRECT" ||
record.request.ownerDirectIsolated !== true
) {
throw new ControlPlaneOwnershipError("AUTHORITY_REQUIRED", "owner-direct isolated reconciliation is not authorized for this operation");
}
if (record.status === "succeeded" || record.status === "failed") return record;
const witness = this.store.readDependencyTerminal(operationId);
if (
!witness ||
witness.requestHash !== record.requestHash ||
witness.leaseId !== "OWNER_DIRECT_ISOLATED"
) {
throw new DurableOperationError(
"RECONCILIATION_REQUIRED",
"No exact terminal witness exists for this owner-direct isolated dependency operation; preserve outcome_unknown and do not replay.",
record,
);
}
return this.store.finish(operationId, {
status: witness.exitCode === 0 && witness.frozenInputsUnchanged ? "succeeded" : "failed",
retrySafe: false,
receipt: {
reconciliation: {
mode: "OWNER_DIRECT_ISOLATED",
requestHash: witness.requestHash,
exitCode: witness.exitCode,
frozenInputsUnchanged: witness.frozenInputsUnchanged,
},
},
});
});
}

reconcileDependencySync(operationId: string, evidence: DependencyReconciliationEvidence, consumerContext?: unknown): DurableOperationRecord {
if (!this.consumer) throw new ControlPlaneOwnershipError("AUTHORITY_REQUIRED", "dependency reconciliation requires trusted host authority");
const consumer = this.consumer;
Expand Down Expand Up @@ -806,6 +905,9 @@ export class DurableOperationManager {
if (!record) throw new DurableOperationError("RECONCILIATION_REQUIRED", `Unknown durable operation: ${operationId}`);
if (record.kind === "cutover_start") return this.reconcileCutoverStart(operationId,consumerContext);
if (record.kind === "dependency_sync") {
if (record.authorityMode === "OWNER_DIRECT" && record.request.ownerDirectIsolated === true) {
return this.reconcileOwnerDirectDependencySync(operationId);
}
if (!this.consumer || typeof record.request.baseRevision !== "string") throw new ControlPlaneOwnershipError("AUTHORITY_REQUIRED", "dependency reconciliation requires revision-bound host authority");
const subject = {operationId, requestHash:record.requestHash, workspaceRoot:record.scopeRoot, baseRevision:record.request.baseRevision, operation:"dependency_sync" as const};
return this.reconcileDependencySync(operationId, this.consumer.readReconciliation(consumerContext, subject), consumerContext);
Expand Down
48 changes: 48 additions & 0 deletions src/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4530,6 +4530,54 @@ test("OWNER_DIRECT workspace_clone works and dependency_sync denies unauthentica
assert.equal(nexusBlocked.isError, true);
});

test("OWNER_DIRECT dependency_sync bypasses carrier only for a managed isolated worktree", async (t) => {
const context = await fixture(t, { git: true });
await writeFile(join(context.project, "package.json"), JSON.stringify({ name: "isolated-fixture", version: "1.0.0" }) + "\n");
await writeFile(join(context.project, "package-lock.json"), JSON.stringify({
name: "isolated-fixture",
version: "1.0.0",
lockfileVersion: 3,
requires: true,
packages: { "": { name: "isolated-fixture", version: "1.0.0" } },
}) + "\n");
await git(context.project, ["add", "package.json", "package-lock.json"]);
await git(context.project, ["commit", "-m", "add dependency fixture"]);

const isolated = await callOpen(context.client, context.project, "isolated-dependency-sync", "worktree");
const isolatedWorkspaceId = structuredContent(isolated).workspaceId as string;
const isolatedResult = await context.client.callTool({
name: "dependency_sync",
arguments: {
workspaceId: isolatedWorkspaceId,
attemptKey: "isolated-owner-direct-deps",
recipe: "npm_ci",
authorityMode: "OWNER_DIRECT",
},
_meta: { "openai/session": "isolated-dependency-sync" },
});
assert.equal(isolatedResult.isError, undefined, JSON.stringify(isolatedResult));
assert.equal(structuredContent(isolatedResult).status, "succeeded");
assert.equal(
((structuredContent(isolatedResult).request as Record<string, unknown>) ?? {}).ownerDirectIsolated,
true,
);

const checkout = await callOpen(context.client, context.project, "checkout-dependency-sync", "checkout");
const checkoutWorkspaceId = structuredContent(checkout).workspaceId as string;
const checkoutResult = await context.client.callTool({
name: "dependency_sync",
arguments: {
workspaceId: checkoutWorkspaceId,
attemptKey: "checkout-owner-direct-deps",
recipe: "npm_ci",
authorityMode: "OWNER_DIRECT",
},
_meta: { "openai/session": "checkout-dependency-sync" },
});
assert.equal(checkoutResult.isError, true);
assert.match(JSON.stringify(checkoutResult), /authenticated MCP client context is required/);
});

test("command_status metadata annotations and minimal mode visibility", async (t) => {
// Test minimal mode tools
const context = await fixture(t, { toolMode: "minimal" });
Expand Down
Loading
Loading