From 0df96769927a6f87dede04302b3c10c85a820fad Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 12:29:10 +0800 Subject: [PATCH 01/55] feat(chat-swarm): define deterministic continuation contract --- src/chat-swarm-continuation-contract.ts | 38 +++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/chat-swarm-continuation-contract.ts diff --git a/src/chat-swarm-continuation-contract.ts b/src/chat-swarm-continuation-contract.ts new file mode 100644 index 000000000..132bea46d --- /dev/null +++ b/src/chat-swarm-continuation-contract.ts @@ -0,0 +1,38 @@ +export const CHAT_SWARM_CONTINUATION_STATES = ["PENDING", "APPROVED", "EXPIRED"] as const; + +export type ChatSwarmContinuationState = (typeof CHAT_SWARM_CONTINUATION_STATES)[number]; + +export interface ChatSwarmContinuationRequest { + id: string; + swarmId: string; + workerId: string; + attemptKey: string; + requestHash: string; + sourceEpoch: number; + targetEpoch: number; + sourceCarrierFingerprint: string; + targetCarrierFingerprint: string; + checkpointHash: string; + version: number; + status: ChatSwarmContinuationState; + requestedAt: string; + expiresAt: string; + approvedAt?: string; +} + +export interface CreateContinuationRequestInput { + swarmId: string; + workerId: string; + attemptKey: string; + sourceEpoch: number; + targetCarrierFingerprint: string; + ttlSeconds?: number; +} + +export interface ApproveContinuationRequestInput { + swarmId: string; + requestId: string; + ownerIdentityFingerprint: string; + expectedRequestVersion: number; + expectedSwarmVersion: number; +} From cb167d0331165dfc96c73b79b6dd3392db9dbb84 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 12:29:42 +0800 Subject: [PATCH 02/55] feat(chat-swarm): add continuation epoch domain guards --- src/chat-swarm-continuation-domain.ts | 126 ++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 src/chat-swarm-continuation-domain.ts diff --git a/src/chat-swarm-continuation-domain.ts b/src/chat-swarm-continuation-domain.ts new file mode 100644 index 000000000..5b347b572 --- /dev/null +++ b/src/chat-swarm-continuation-domain.ts @@ -0,0 +1,126 @@ +import { createHash } from "node:crypto"; +import { + canonicalize, + ChatSwarmError, + hashContent, +} from "./chat-swarm-contract.js"; +import type { + ChatSwarmContinuationRequest, + CreateContinuationRequestInput, +} from "./chat-swarm-continuation-contract.js"; + +const SHA256 = /^[0-9a-f]{64}$/; + +export interface WorkerContinuationSnapshot { + swarmId: string; + workerId: string; + lifecycleState: "AVAILABLE" | "BUSY" | "DISABLED" | "RECONCILE_REQUIRED"; + currentTaskId?: string; + continuationEpoch: number; + carrierConversationFingerprint?: string; + checkpoint?: Record; +} + +export interface PreparedContinuationMaterial { + requestHash: string; + sourceEpoch: number; + targetEpoch: number; + sourceCarrierFingerprint: string; + targetCarrierFingerprint: string; + checkpointHash: string; +} + +export function prepareContinuationMaterial( + worker: WorkerContinuationSnapshot, + input: CreateContinuationRequestInput, +): PreparedContinuationMaterial { + if (worker.swarmId !== input.swarmId || worker.workerId !== input.workerId) { + throw new ChatSwarmError("OWNERSHIP_CONFLICT", "worker does not match requested swarm identity"); + } + if (worker.lifecycleState !== "AVAILABLE" || worker.currentTaskId) { + throw new ChatSwarmError("RECONCILIATION_REQUIRED", "worker is not at a safe continuation boundary"); + } + if (worker.continuationEpoch !== input.sourceEpoch) { + throw new ChatSwarmError("OWNERSHIP_CONFLICT", "worker continuation epoch changed"); + } + const sourceCarrierFingerprint = requireFingerprint( + worker.carrierConversationFingerprint, + "source carrier fingerprint", + ); + const targetCarrierFingerprint = requireFingerprint( + input.targetCarrierFingerprint, + "target carrier fingerprint", + ); + if (sourceCarrierFingerprint === targetCarrierFingerprint) { + throw new ChatSwarmError("INVALID_INPUT", "target carrier must differ from source carrier"); + } + if (!worker.checkpoint || Array.isArray(worker.checkpoint) || typeof worker.checkpoint !== "object") { + throw new ChatSwarmError("RECONCILIATION_REQUIRED", "worker has no bounded continuation checkpoint"); + } + + const checkpointJson = JSON.stringify(canonicalize(worker.checkpoint)); + const checkpointHash = hashContent(checkpointJson); + const targetEpoch = input.sourceEpoch + 1; + const requestHash = createHash("sha256") + .update(JSON.stringify(canonicalize({ + swarmId: input.swarmId, + workerId: input.workerId, + sourceEpoch: input.sourceEpoch, + targetEpoch, + sourceCarrierFingerprint, + targetCarrierFingerprint, + checkpointHash, + }))) + .digest("hex"); + + return { + requestHash, + sourceEpoch: input.sourceEpoch, + targetEpoch, + sourceCarrierFingerprint, + targetCarrierFingerprint, + checkpointHash, + }; +} + +export function assertContinuationCommitAllowed( + worker: WorkerContinuationSnapshot, + request: ChatSwarmContinuationRequest, +): void { + if (request.status !== "PENDING") { + throw new ChatSwarmError("INVALID_STATE", "continuation request is not pending"); + } + if (worker.swarmId !== request.swarmId || worker.workerId !== request.workerId) { + throw new ChatSwarmError("OWNERSHIP_CONFLICT", "continuation request targets another worker"); + } + if (worker.lifecycleState !== "AVAILABLE" || worker.currentTaskId) { + throw new ChatSwarmError("RECONCILIATION_REQUIRED", "worker is not at a safe continuation boundary"); + } + if (worker.continuationEpoch !== request.sourceEpoch) { + throw new ChatSwarmError("CAS_DRIFT", "worker continuation epoch changed before transfer"); + } + if (request.targetEpoch !== request.sourceEpoch + 1) { + throw new ChatSwarmError("INVALID_STATE", "continuation epoch binding is malformed"); + } + const sourceCarrierFingerprint = requireFingerprint( + worker.carrierConversationFingerprint, + "source carrier fingerprint", + ); + if (sourceCarrierFingerprint !== request.sourceCarrierFingerprint) { + throw new ChatSwarmError("CAS_DRIFT", "worker source carrier changed before transfer"); + } + if (!worker.checkpoint || Array.isArray(worker.checkpoint) || typeof worker.checkpoint !== "object") { + throw new ChatSwarmError("RECONCILIATION_REQUIRED", "worker checkpoint is unavailable"); + } + const checkpointHash = hashContent(JSON.stringify(canonicalize(worker.checkpoint))); + if (checkpointHash !== request.checkpointHash) { + throw new ChatSwarmError("CAS_DRIFT", "worker checkpoint changed before transfer"); + } +} + +function requireFingerprint(value: string | undefined, label: string): string { + if (!value || !SHA256.test(value)) { + throw new ChatSwarmError("INVALID_INPUT", `${label} must be a SHA-256 fingerprint`); + } + return value; +} From 29164711613f69b6a91d98703c61bd5e95093abf Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 12:29:56 +0800 Subject: [PATCH 03/55] test(chat-swarm): cover continuation epoch guards --- src/chat-swarm-continuation-domain.test.ts | 118 +++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 src/chat-swarm-continuation-domain.test.ts diff --git a/src/chat-swarm-continuation-domain.test.ts b/src/chat-swarm-continuation-domain.test.ts new file mode 100644 index 000000000..eab82ee35 --- /dev/null +++ b/src/chat-swarm-continuation-domain.test.ts @@ -0,0 +1,118 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { ChatSwarmError } from "./chat-swarm-contract.js"; +import { + assertContinuationCommitAllowed, + prepareContinuationMaterial, + type WorkerContinuationSnapshot, +} from "./chat-swarm-continuation-domain.js"; +import type { ChatSwarmContinuationRequest } from "./chat-swarm-continuation-contract.js"; + +const source = "a".repeat(64); +const target = "b".repeat(64); + +function worker(overrides: Partial = {}): WorkerContinuationSnapshot { + return { + swarmId: "swarm-1", + workerId: "worker-1", + lifecycleState: "AVAILABLE", + continuationEpoch: 4, + carrierConversationFingerprint: source, + checkpoint: { lastTaskId: "task-9", summary: "safe boundary" }, + ...overrides, + }; +} + +function request(overrides: Partial = {}): ChatSwarmContinuationRequest { + const material = prepareContinuationMaterial(worker(), { + swarmId: "swarm-1", + workerId: "worker-1", + attemptKey: "cont-1", + sourceEpoch: 4, + targetCarrierFingerprint: target, + }); + return { + id: "contreq-1", + swarmId: "swarm-1", + workerId: "worker-1", + attemptKey: "cont-1", + requestHash: material.requestHash, + sourceEpoch: material.sourceEpoch, + targetEpoch: material.targetEpoch, + sourceCarrierFingerprint: material.sourceCarrierFingerprint, + targetCarrierFingerprint: material.targetCarrierFingerprint, + checkpointHash: material.checkpointHash, + version: 1, + status: "PENDING", + requestedAt: "2026-09-13T00:00:00.000Z", + expiresAt: "2026-09-13T00:15:00.000Z", + ...overrides, + }; +} + +test("prepare binds exact epoch, carriers and checkpoint", () => { + const material = prepareContinuationMaterial(worker(), { + swarmId: "swarm-1", + workerId: "worker-1", + attemptKey: "cont-1", + sourceEpoch: 4, + targetCarrierFingerprint: target, + }); + assert.equal(material.sourceEpoch, 4); + assert.equal(material.targetEpoch, 5); + assert.equal(material.sourceCarrierFingerprint, source); + assert.equal(material.targetCarrierFingerprint, target); + assert.match(material.checkpointHash, /^[0-9a-f]{64}$/); + assert.match(material.requestHash, /^[0-9a-f]{64}$/); +}); + +test("prepare rejects unsafe active worker and stale epoch", () => { + assert.throws( + () => prepareContinuationMaterial(worker({ lifecycleState: "BUSY", currentTaskId: "task-live" }), { + swarmId: "swarm-1", + workerId: "worker-1", + attemptKey: "cont-2", + sourceEpoch: 4, + targetCarrierFingerprint: target, + }), + (error: unknown) => error instanceof ChatSwarmError && error.code === "RECONCILIATION_REQUIRED", + ); + + assert.throws( + () => prepareContinuationMaterial(worker(), { + swarmId: "swarm-1", + workerId: "worker-1", + attemptKey: "cont-3", + sourceEpoch: 3, + targetCarrierFingerprint: target, + }), + (error: unknown) => error instanceof ChatSwarmError && error.code === "OWNERSHIP_CONFLICT", + ); +}); + +test("commit guard rejects changed source carrier and checkpoint", () => { + const prepared = request(); + assert.doesNotThrow(() => assertContinuationCommitAllowed(worker(), prepared)); + + assert.throws( + () => assertContinuationCommitAllowed(worker({ carrierConversationFingerprint: "c".repeat(64) }), prepared), + (error: unknown) => error instanceof ChatSwarmError && error.code === "CAS_DRIFT", + ); + + assert.throws( + () => assertContinuationCommitAllowed(worker({ checkpoint: { summary: "changed" } }), prepared), + (error: unknown) => error instanceof ChatSwarmError && error.code === "CAS_DRIFT", + ); +}); + +test("commit guard rejects malformed target epoch and non-pending request", () => { + assert.throws( + () => assertContinuationCommitAllowed(worker(), request({ targetEpoch: 7 })), + (error: unknown) => error instanceof ChatSwarmError && error.code === "INVALID_STATE", + ); + + assert.throws( + () => assertContinuationCommitAllowed(worker(), request({ status: "APPROVED" })), + (error: unknown) => error instanceof ChatSwarmError && error.code === "INVALID_STATE", + ); +}); From c5215a8246020318f8b5eb2de03f4a945b4c7113 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 12:30:27 +0800 Subject: [PATCH 04/55] test(chat-swarm): run continuation domain regressions --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 61a87ddb5..d4a8b21e8 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "postinstall": "node scripts/fix-node-pty-permissions.mjs", "test:node-pty-permissions": "node --import tsx --test src/node-pty-postinstall.test.ts", "start": "node dist/cli.js serve", - "test": "npm run test:node-pty-permissions && npm run test:carrier-binding && tsx src/coordination-reader-loader.test.ts && tsx src/control-plane-ownership.test.ts && tsx src/control-plane-handoff.test.ts && tsx src/control-plane-continuation.test.ts && tsx src/deployment-convergence.test.ts && tsx src/current-completion-matrix.test.ts && tsx src/control-plane-consumer.test.ts && tsx src/local-agent-cline-catalog.test.ts && tsx src/chat-swarm-contract.test.ts && tsx src/chat-swarm-store.test.ts && tsx src/chat-swarm-runtime-owner.test.ts && tsx src/local-agent-opencode-mcp-catalog.test.ts && tsx src/chat-swarm-coordinator.test.ts && tsx src/chat-swarm-peer-runtime.test.ts && tsx src/chat-swarm-tools.test.ts && tsx src/chat-swarm-lifecycle.test.ts && tsx src/chat-swarm-carrier.test.ts && tsx src/chat-swarm-peer-admission.test.ts && tsx src/chat-swarm-task-ledger.test.ts && tsx src/git-worktrees.test.ts && tsx src/execution-protocol.test.ts && tsx src/durable-operations-ci.test.ts && tsx src/git-candidate.test.ts && tsx src/config.test.ts && tsx src/onboarding.test.ts && tsx src/cli-workspace.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions-ci.test.ts && tsx src/codex-goal-sessions-ci.test.ts && tsx src/mcp-sessions.test.ts && tsx src/cutover-state.test.ts && tsx src/cutover-state-recovery-guard.test.ts && tsx src/cutover-build-ready.test.ts && tsx src/cutover-orchestration.test.ts && tsx src/mcp-cutover.test.ts && tsx src/cutover-restart.test.ts && tsx src/cutover-http.test.ts && tsx src/cutover-recovery.test.ts && tsx src/cutover-binding-repair.test.ts && tsx src/capability-manifest.test.ts && tsx src/server-shutdown.test.ts && tsx src/codex-runtime.test.ts && tsx src/local-agent-config.test.ts && tsx src/local-agent-catalog.test.ts && tsx src/local-agent-presentation.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-daemon-lifecycle.test.ts && tsx src/local-agent-daemon-protocol.test.ts && tsx src/local-agent-daemon.test.ts && tsx src/local-agent-codex.test.ts && tsx src/local-agent-opencode.test.ts && tsx src/local-agent-opencode-catalog.test.ts && tsx src/local-agent-acp.test.ts && tsx src/local-agent-grok.test.ts && tsx src/local-agent-pi-sandbox.test.ts && tsx src/local-agent-pi.test.ts && tsx src/local-agent-claude.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-profile-source.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-toolchains.test.ts && tsx src/local-agent-idle-policy.test.ts && tsx src/local-agent-capacity.test.ts && tsx src/local-agent-execution-contract.test.ts && tsx src/local-agent-continuation.test.ts && tsx src/provider-scratch.test.ts && tsx src/git-integration-ci.test.ts && tsx src/repository-intelligence.test.ts && tsx src/local-agent-store.test.ts && tsx src/local-agent-manager.test.ts && tsx src/roots.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/conversation-isolation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/server-ci.test.ts && tsx src/oauth-store.test.ts && tsx src/cli-ci.test.ts && tsx src/oauth-json-and-child-reap.test.ts && tsx src/local-agent-errors.test.ts && tsx src/provider-output-redaction.test.ts && tsx src/host-operation-policy.test.ts && tsx src/host-operations.test.ts && tsx src/host-operations-http.test.ts", + "test": "npm run test:node-pty-permissions && npm run test:carrier-binding && tsx src/coordination-reader-loader.test.ts && tsx src/control-plane-ownership.test.ts && tsx src/control-plane-handoff.test.ts && tsx src/control-plane-continuation.test.ts && tsx src/deployment-convergence.test.ts && tsx src/current-completion-matrix.test.ts && tsx src/control-plane-consumer.test.ts && tsx src/local-agent-cline-catalog.test.ts && tsx src/chat-swarm-contract.test.ts && tsx src/chat-swarm-store.test.ts && tsx src/chat-swarm-runtime-owner.test.ts && tsx src/local-agent-opencode-mcp-catalog.test.ts && tsx src/chat-swarm-coordinator.test.ts && tsx src/chat-swarm-peer-runtime.test.ts && tsx src/chat-swarm-tools.test.ts && tsx src/chat-swarm-lifecycle.test.ts && tsx src/chat-swarm-carrier.test.ts && tsx src/chat-swarm-continuation-domain.test.ts && tsx src/chat-swarm-peer-admission.test.ts && tsx src/chat-swarm-task-ledger.test.ts && tsx src/git-worktrees.test.ts && tsx src/execution-protocol.test.ts && tsx src/durable-operations-ci.test.ts && tsx src/git-candidate.test.ts && tsx src/config.test.ts && tsx src/onboarding.test.ts && tsx src/cli-workspace.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions-ci.test.ts && tsx src/codex-goal-sessions-ci.test.ts && tsx src/mcp-sessions.test.ts && tsx src/cutover-state.test.ts && tsx src/cutover-state-recovery-guard.test.ts && tsx src/cutover-build-ready.test.ts && tsx src/cutover-orchestration.test.ts && tsx src/mcp-cutover.test.ts && tsx src/cutover-restart.test.ts && tsx src/cutover-http.test.ts && tsx src/cutover-recovery.test.ts && tsx src/cutover-binding-repair.test.ts && tsx src/capability-manifest.test.ts && tsx src/server-shutdown.test.ts && tsx src/codex-runtime.test.ts && tsx src/local-agent-config.test.ts && tsx src/local-agent-catalog.test.ts && tsx src/local-agent-presentation.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-daemon-lifecycle.test.ts && tsx src/local-agent-daemon-protocol.test.ts && tsx src/local-agent-daemon.test.ts && tsx src/local-agent-codex.test.ts && tsx src/local-agent-opencode.test.ts && tsx src/local-agent-opencode-catalog.test.ts && tsx src/local-agent-acp.test.ts && tsx src/local-agent-grok.test.ts && tsx src/local-agent-pi-sandbox.test.ts && tsx src/local-agent-pi.test.ts && tsx src/local-agent-claude.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-profile-source.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-toolchains.test.ts && tsx src/local-agent-idle-policy.test.ts && tsx src/local-agent-capacity.test.ts && tsx src/local-agent-execution-contract.test.ts && tsx src/local-agent-continuation.test.ts && tsx src/provider-scratch.test.ts && tsx src/git-integration-ci.test.ts && tsx src/repository-intelligence.test.ts && tsx src/local-agent-store.test.ts && tsx src/local-agent-manager.test.ts && tsx src/roots.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/conversation-isolation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/server-ci.test.ts && tsx src/oauth-store.test.ts && tsx src/cli-ci.test.ts && tsx src/oauth-json-and-child-reap.test.ts && tsx src/local-agent-errors.test.ts && tsx src/provider-output-redaction.test.ts && tsx src/host-operation-policy.test.ts && tsx src/host-operations.test.ts && tsx src/host-operations-http.test.ts", "typecheck": "tsc -p tsconfig.json --noEmit", "test:carrier-binding": "tsx src/carrier-binding.test.ts && tsx src/carrier-binding-http.test.ts", "test:local-agent-sessions": "node --import tsx --test src/local-agent-sessions.test.ts" @@ -85,4 +85,4 @@ "optionalDependencies": { "node-pty": "1.1.0" } -} +} \ No newline at end of file From b57a8f944e89b269bf95e5b96e0768ac4ff3d01f Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 13:51:44 +0800 Subject: [PATCH 05/55] fix(chat-swarm): bind continuation target to authenticated carrier --- src/chat-swarm-continuation-contract.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/chat-swarm-continuation-contract.ts b/src/chat-swarm-continuation-contract.ts index 132bea46d..e48d4087e 100644 --- a/src/chat-swarm-continuation-contract.ts +++ b/src/chat-swarm-continuation-contract.ts @@ -1,4 +1,9 @@ -export const CHAT_SWARM_CONTINUATION_STATES = ["PENDING", "APPROVED", "EXPIRED"] as const; +export const CHAT_SWARM_CONTINUATION_STATES = [ + "PENDING", + "APPROVED", + "EXPIRED", + "RECONCILE_REQUIRED", +] as const; export type ChatSwarmContinuationState = (typeof CHAT_SWARM_CONTINUATION_STATES)[number]; @@ -25,14 +30,12 @@ export interface CreateContinuationRequestInput { workerId: string; attemptKey: string; sourceEpoch: number; - targetCarrierFingerprint: string; ttlSeconds?: number; } export interface ApproveContinuationRequestInput { swarmId: string; requestId: string; - ownerIdentityFingerprint: string; expectedRequestVersion: number; expectedSwarmVersion: number; } From ad9fec430f67209df38fcf1dbf724c9233b9ee87 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 13:52:00 +0800 Subject: [PATCH 06/55] fix(chat-swarm): separate authenticated target from caller input --- src/chat-swarm-continuation-domain.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/chat-swarm-continuation-domain.ts b/src/chat-swarm-continuation-domain.ts index 5b347b572..5e63962e5 100644 --- a/src/chat-swarm-continuation-domain.ts +++ b/src/chat-swarm-continuation-domain.ts @@ -33,6 +33,7 @@ export interface PreparedContinuationMaterial { export function prepareContinuationMaterial( worker: WorkerContinuationSnapshot, input: CreateContinuationRequestInput, + authenticatedTargetCarrierFingerprint: string, ): PreparedContinuationMaterial { if (worker.swarmId !== input.swarmId || worker.workerId !== input.workerId) { throw new ChatSwarmError("OWNERSHIP_CONFLICT", "worker does not match requested swarm identity"); @@ -48,7 +49,7 @@ export function prepareContinuationMaterial( "source carrier fingerprint", ); const targetCarrierFingerprint = requireFingerprint( - input.targetCarrierFingerprint, + authenticatedTargetCarrierFingerprint, "target carrier fingerprint", ); if (sourceCarrierFingerprint === targetCarrierFingerprint) { From 8d15f2cc74a8b81c0a6cca0dc4dd5d1b772ff48c Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 13:52:17 +0800 Subject: [PATCH 07/55] test(chat-swarm): prove target identity is externally bound --- src/chat-swarm-continuation-domain.test.ts | 56 +++++++++++----------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/chat-swarm-continuation-domain.test.ts b/src/chat-swarm-continuation-domain.test.ts index eab82ee35..621b61e0b 100644 --- a/src/chat-swarm-continuation-domain.test.ts +++ b/src/chat-swarm-continuation-domain.test.ts @@ -23,14 +23,17 @@ function worker(overrides: Partial = {}): WorkerCont }; } -function request(overrides: Partial = {}): ChatSwarmContinuationRequest { - const material = prepareContinuationMaterial(worker(), { +function createInput(attemptKey = "cont-1", sourceEpoch = 4) { + return { swarmId: "swarm-1", workerId: "worker-1", - attemptKey: "cont-1", - sourceEpoch: 4, - targetCarrierFingerprint: target, - }); + attemptKey, + sourceEpoch, + }; +} + +function request(overrides: Partial = {}): ChatSwarmContinuationRequest { + const material = prepareContinuationMaterial(worker(), createInput(), target); return { id: "contreq-1", swarmId: "swarm-1", @@ -50,46 +53,43 @@ function request(overrides: Partial = {}): ChatSwa }; } -test("prepare binds exact epoch, carriers and checkpoint", () => { - const material = prepareContinuationMaterial(worker(), { - swarmId: "swarm-1", - workerId: "worker-1", - attemptKey: "cont-1", - sourceEpoch: 4, - targetCarrierFingerprint: target, - }); +test("prepare binds exact epoch, authenticated target carrier and checkpoint", () => { + const material = prepareContinuationMaterial(worker(), createInput(), target); assert.equal(material.sourceEpoch, 4); assert.equal(material.targetEpoch, 5); assert.equal(material.sourceCarrierFingerprint, source); assert.equal(material.targetCarrierFingerprint, target); assert.match(material.checkpointHash, /^[0-9a-f]{64}$/); assert.match(material.requestHash, /^[0-9a-f]{64}$/); + + const otherTarget = "c".repeat(64); + const other = prepareContinuationMaterial(worker(), createInput(), otherTarget); + assert.notEqual(material.requestHash, other.requestHash); }); test("prepare rejects unsafe active worker and stale epoch", () => { assert.throws( - () => prepareContinuationMaterial(worker({ lifecycleState: "BUSY", currentTaskId: "task-live" }), { - swarmId: "swarm-1", - workerId: "worker-1", - attemptKey: "cont-2", - sourceEpoch: 4, - targetCarrierFingerprint: target, - }), + () => prepareContinuationMaterial( + worker({ lifecycleState: "BUSY", currentTaskId: "task-live" }), + createInput("cont-2"), + target, + ), (error: unknown) => error instanceof ChatSwarmError && error.code === "RECONCILIATION_REQUIRED", ); assert.throws( - () => prepareContinuationMaterial(worker(), { - swarmId: "swarm-1", - workerId: "worker-1", - attemptKey: "cont-3", - sourceEpoch: 3, - targetCarrierFingerprint: target, - }), + () => prepareContinuationMaterial(worker(), createInput("cont-3", 3), target), (error: unknown) => error instanceof ChatSwarmError && error.code === "OWNERSHIP_CONFLICT", ); }); +test("prepare rejects reuse of the current carrier as target", () => { + assert.throws( + () => prepareContinuationMaterial(worker(), createInput("cont-same"), source), + (error: unknown) => error instanceof ChatSwarmError && error.code === "INVALID_INPUT", + ); +}); + test("commit guard rejects changed source carrier and checkpoint", () => { const prepared = request(); assert.doesNotThrow(() => assertContinuationCommitAllowed(worker(), prepared)); From 2f7a45341e776a6252a5a84eb812f942451f4d9a Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 13:53:37 +0800 Subject: [PATCH 08/55] feat(chat-swarm): persist continuation in existing durable operation ledger --- src/chat-swarm-continuation-store.ts | 535 +++++++++++++++++++++++++++ 1 file changed, 535 insertions(+) create mode 100644 src/chat-swarm-continuation-store.ts diff --git a/src/chat-swarm-continuation-store.ts b/src/chat-swarm-continuation-store.ts new file mode 100644 index 000000000..1d6cf07ee --- /dev/null +++ b/src/chat-swarm-continuation-store.ts @@ -0,0 +1,535 @@ +import { createHash } from "node:crypto"; +import { resolve } from "node:path"; +import { openDatabase, type DatabaseHandle } from "./db/client.js"; +import { + assertBounded, + ChatSwarmError, + MAX_ID_BYTES, + newId, +} from "./chat-swarm-contract.js"; +import type { + ApproveContinuationRequestInput, + ChatSwarmContinuationRequest, + CreateContinuationRequestInput, +} from "./chat-swarm-continuation-contract.js"; +import { + assertContinuationCommitAllowed, + prepareContinuationMaterial, + type WorkerContinuationSnapshot, +} from "./chat-swarm-continuation-domain.js"; + +const KIND = "chat_swarm_continuation"; +const REQUEST_SCHEMA = "devspace.chat_swarm_continuation.v1"; +const RECEIPT_SCHEMA = "devspace.chat_swarm_continuation_receipt.v1"; +const SHA256 = /^[0-9a-f]{64}$/; +const DEFAULT_TTL_SECONDS = 15 * 60; +const MAX_TTL_SECONDS = 60 * 60; + +type Row = Record; + +interface DurableRow { + operation_id: string; + attempt_key: string; + request_hash: string; + kind: string; + authority_mode: string; + scope_root: string; + status: string; + request_json: string; + receipt_json: string | null; + error_code: string | null; + error_message: string | null; + created_at: string; + updated_at: string; +} + +interface PersistedRequest { + schema: typeof REQUEST_SCHEMA; + swarmId: string; + workerId: string; + attemptKey: string; + sourceEpoch: number; + targetEpoch: number; + sourceCarrierFingerprint: string; + targetCarrierFingerprint: string; + checkpointHash: string; + requestedAt: string; + expiresAt: string; +} + +interface PersistedReceipt { + schema: typeof RECEIPT_SCHEMA; + approvedAt: string; + targetEpoch: number; + targetCarrierFingerprint: string; + checkpointHash: string; +} + +export class ChatSwarmContinuationStore { + private readonly database: DatabaseHandle; + private readonly scopeRoot: string; + + constructor( + stateDir: string, + private readonly clock: () => Date = () => new Date(), + ) { + this.database = openDatabase(stateDir); + this.scopeRoot = resolve(stateDir); + } + + close(): void { + this.database.close(); + } + + createRequest( + authenticatedTargetCarrierFingerprint: string, + input: CreateContinuationRequestInput, + ): { request: ChatSwarmContinuationRequest; created: boolean } { + assertFingerprint(authenticatedTargetCarrierFingerprint, "authenticated target carrier fingerprint"); + assertBounded(input.swarmId, MAX_ID_BYTES, "swarmId"); + assertBounded(input.workerId, MAX_ID_BYTES, "workerId"); + assertBounded(input.attemptKey, MAX_ID_BYTES, "attemptKey"); + const ttlSeconds = input.ttlSeconds ?? DEFAULT_TTL_SECONDS; + if (!Number.isInteger(ttlSeconds) || ttlSeconds < 1 || ttlSeconds > MAX_TTL_SECONDS) { + throw new ChatSwarmError("INVALID_INPUT", `ttlSeconds must be between 1 and ${MAX_TTL_SECONDS}`); + } + + const tx = this.database.sqlite.transaction(() => { + const swarm = this.requireActiveSwarm(input.swarmId); + void swarm; + const worker = this.workerSnapshot(input.workerId); + const material = prepareContinuationMaterial( + worker, + input, + authenticatedTargetCarrierFingerprint, + ); + this.assertTargetCarrierAvailable(authenticatedTargetCarrierFingerprint, input.workerId); + + const durableAttemptKey = stableAttemptKey(input.swarmId, input.workerId, input.attemptKey); + const existing = this.getDurableByAttempt(durableAttemptKey); + if (existing) { + if (existing.kind !== KIND || existing.request_hash !== material.requestHash) { + throw new ChatSwarmError("REPLAY_CONFLICT", "continuation attemptKey is bound to different material"); + } + return { request: this.toRequest(existing), created: false }; + } + + const now = this.nowIso(); + for (const pending of this.listPendingDurable()) { + const request = this.toRequest(pending); + if (request.workerId !== input.workerId) continue; + if (Date.parse(request.expiresAt) <= Date.parse(now)) { + this.expireDurable(pending.operation_id, now); + continue; + } + throw new ChatSwarmError("OWNERSHIP_CONFLICT", "worker already has a pending continuation request"); + } + + const requestedAt = now; + const expiresAt = new Date(this.clock().getTime() + ttlSeconds * 1000).toISOString(); + const request: PersistedRequest = { + schema: REQUEST_SCHEMA, + swarmId: input.swarmId, + workerId: input.workerId, + attemptKey: input.attemptKey, + sourceEpoch: material.sourceEpoch, + targetEpoch: material.targetEpoch, + sourceCarrierFingerprint: material.sourceCarrierFingerprint, + targetCarrierFingerprint: material.targetCarrierFingerprint, + checkpointHash: material.checkpointHash, + requestedAt, + expiresAt, + }; + const operationId = newId("continuation"); + this.database.sqlite.prepare(` + insert into durable_operations ( + operation_id,attempt_key,request_hash,kind,authority_mode,scope_root, + workspace_id,status,retry_safe,request_json,receipt_json,error_code, + error_message,created_at,updated_at + ) values (?,?,?,?,?,?,null,'started','false',?,null,null,null,?,?) + `).run( + operationId, + durableAttemptKey, + material.requestHash, + KIND, + "OWNER_DIRECT", + this.scopeRoot, + JSON.stringify(request), + requestedAt, + requestedAt, + ); + return { request: this.getRequest(operationId)!, created: true }; + }); + + return tx.immediate(); + } + + approveRequest( + ownerIdentityFingerprint: string, + input: ApproveContinuationRequestInput, + ): ChatSwarmContinuationRequest { + assertFingerprint(ownerIdentityFingerprint, "owner identity fingerprint"); + assertBounded(input.swarmId, MAX_ID_BYTES, "swarmId"); + assertBounded(input.requestId, MAX_ID_BYTES, "requestId"); + if (!Number.isInteger(input.expectedRequestVersion) || input.expectedRequestVersion < 1) { + throw new ChatSwarmError("INVALID_INPUT", "expectedRequestVersion must be a positive integer"); + } + if (!Number.isInteger(input.expectedSwarmVersion) || input.expectedSwarmVersion < 1) { + throw new ChatSwarmError("INVALID_INPUT", "expectedSwarmVersion must be a positive integer"); + } + + const tx = this.database.sqlite.transaction(() => { + const durable = this.requireDurable(input.requestId); + const request = this.toRequest(durable); + if (request.swarmId !== input.swarmId) { + throw new ChatSwarmError("OWNERSHIP_CONFLICT", "continuation request belongs to another swarm"); + } + + if (request.status === "APPROVED") { + this.assertCommittedBinding(request); + return request; + } + if (request.status === "EXPIRED") { + throw new ChatSwarmError("EXPIRED", "continuation request has expired"); + } + if (request.status === "RECONCILE_REQUIRED") { + throw new ChatSwarmError("RECONCILIATION_REQUIRED", "continuation outcome requires explicit reconciliation"); + } + if (request.version !== input.expectedRequestVersion) { + throw new ChatSwarmError("VERSION_CONFLICT", `expected request version ${input.expectedRequestVersion} but found ${request.version}`); + } + + const now = this.nowIso(); + if (Date.parse(request.expiresAt) <= Date.parse(now)) { + this.expireDurable(request.id, now); + throw new ChatSwarmError("EXPIRED", "continuation request has expired"); + } + + const swarm = this.requireActiveSwarm(request.swarmId); + if (String(swarm.owner_identity_fingerprint) !== ownerIdentityFingerprint) { + throw new ChatSwarmError("OWNERSHIP_CONFLICT", "controller identity does not own swarm"); + } + const swarmRevision = Number(swarm.revision ?? 1); + if (swarmRevision !== input.expectedSwarmVersion) { + throw new ChatSwarmError("CAS_DRIFT", `swarm revision mismatch: expected ${input.expectedSwarmVersion} but found ${swarmRevision}`); + } + + const worker = this.workerSnapshot(request.workerId); + assertContinuationCommitAllowed(worker, request); + this.assertTargetCarrierAvailable(request.targetCarrierFingerprint, request.workerId); + + const workerUpdate = this.database.sqlite.prepare(` + update chat_swarm_workers + set carrier_conversation_fingerprint=?,continuation_epoch=?,lease_json=null,updated_at=? + where id=? and swarm_id=? and continuation_epoch=? + and carrier_conversation_fingerprint=? and lifecycle_state='AVAILABLE' + and current_task_id is null + `).run( + request.targetCarrierFingerprint, + request.targetEpoch, + now, + request.workerId, + request.swarmId, + request.sourceEpoch, + request.sourceCarrierFingerprint, + ); + if (workerUpdate.changes !== 1) { + throw new ChatSwarmError("CAS_DRIFT", "worker binding changed during continuation transfer"); + } + + const receipt: PersistedReceipt = { + schema: RECEIPT_SCHEMA, + approvedAt: now, + targetEpoch: request.targetEpoch, + targetCarrierFingerprint: request.targetCarrierFingerprint, + checkpointHash: request.checkpointHash, + }; + const operationUpdate = this.database.sqlite.prepare(` + update durable_operations + set status='succeeded',retry_safe='false',receipt_json=?,error_code=null, + error_message=null,updated_at=? + where operation_id=? and kind=? and status='started' + `).run(JSON.stringify(receipt), now, request.id, KIND); + if (operationUpdate.changes !== 1) { + throw new ChatSwarmError("VERSION_CONFLICT", "continuation request changed during transfer"); + } + + const swarmUpdate = this.database.sqlite.prepare(` + update chat_swarms set revision=revision+1,updated_at=? + where id=? and revision=? and status='ACTIVE' + `).run(now, request.swarmId, swarmRevision); + if (swarmUpdate.changes !== 1) { + throw new ChatSwarmError("CAS_DRIFT", "swarm revision changed during continuation transfer"); + } + + return this.getRequest(request.id)!; + }); + + return tx.immediate(); + } + + getRequest(requestId: string): ChatSwarmContinuationRequest | undefined { + const row = this.database.sqlite.prepare( + "select * from durable_operations where operation_id=? and kind=? limit 1", + ).get(requestId, KIND) as DurableRow | undefined; + return row ? this.toRequest(row) : undefined; + } + + getLatestForTarget( + swarmId: string, + authenticatedTargetCarrierFingerprint: string, + ): ChatSwarmContinuationRequest | undefined { + assertFingerprint(authenticatedTargetCarrierFingerprint, "authenticated target carrier fingerprint"); + const rows = this.database.sqlite.prepare( + "select * from durable_operations where kind=? and scope_root=? order by created_at desc,operation_id desc", + ).all(KIND, this.scopeRoot) as DurableRow[]; + for (const row of rows) { + const request = this.toRequest(row); + if ( + request.swarmId === swarmId && + request.targetCarrierFingerprint === authenticatedTargetCarrierFingerprint + ) return request; + } + return undefined; + } + + recoverAfterRestart(): number { + const now = this.nowIso(); + const result = this.database.sqlite.prepare(` + update durable_operations + set status='outcome_unknown',retry_safe='false',error_code='RECONCILIATION_REQUIRED', + error_message='DevSpace restarted while continuation was pending; reconcile exact binding before approval.', + updated_at=? + where kind=? and status='started' + `).run(now, KIND); + return Number(result.changes); + } + + reconcileUnknownNoEffect( + ownerIdentityFingerprint: string, + requestId: string, + ): ChatSwarmContinuationRequest { + assertFingerprint(ownerIdentityFingerprint, "owner identity fingerprint"); + const tx = this.database.sqlite.transaction(() => { + const durable = this.requireDurable(requestId); + const request = this.toRequest(durable); + if (request.status !== "RECONCILE_REQUIRED") { + throw new ChatSwarmError("INVALID_STATE", "continuation request does not require reconciliation"); + } + const swarm = this.requireActiveSwarm(request.swarmId); + if (String(swarm.owner_identity_fingerprint) !== ownerIdentityFingerprint) { + throw new ChatSwarmError("OWNERSHIP_CONFLICT", "controller identity does not own swarm"); + } + const worker = this.workerSnapshot(request.workerId); + const pendingView: ChatSwarmContinuationRequest = { ...request, status: "PENDING" }; + assertContinuationCommitAllowed(worker, pendingView); + const now = this.nowIso(); + if (Date.parse(request.expiresAt) <= Date.parse(now)) { + this.expireDurable(request.id, now); + throw new ChatSwarmError("EXPIRED", "continuation request expired during reconciliation"); + } + const result = this.database.sqlite.prepare(` + update durable_operations + set status='started',error_code=null,error_message=null,updated_at=? + where operation_id=? and kind=? and status='outcome_unknown' + `).run(now, request.id, KIND); + if (result.changes !== 1) { + throw new ChatSwarmError("CAS_DRIFT", "continuation reconciliation changed concurrently"); + } + return this.getRequest(request.id)!; + }); + return tx.immediate(); + } + + private requireActiveSwarm(swarmId: string): Row { + const row = this.database.sqlite.prepare( + "select * from chat_swarms where id=? and status='ACTIVE'", + ).get(swarmId) as Row | undefined; + if (!row) throw new ChatSwarmError("NOT_FOUND", "swarm not found or not active"); + return row; + } + + private workerSnapshot(workerId: string): WorkerContinuationSnapshot { + const row = this.database.sqlite.prepare( + "select * from chat_swarm_workers where id=?", + ).get(workerId) as Row | undefined; + if (!row) throw new ChatSwarmError("NOT_FOUND", "worker not found"); + let checkpoint: Record | undefined; + if (row.checkpoint_json != null) { + try { + const parsed = JSON.parse(String(row.checkpoint_json)); + if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") throw new Error("bad checkpoint"); + checkpoint = parsed as Record; + } catch { + throw new ChatSwarmError("INVALID_STATE", "corrupt persisted worker checkpoint"); + } + } + return { + swarmId: String(row.swarm_id), + workerId: String(row.id), + lifecycleState: String(row.lifecycle_state) as WorkerContinuationSnapshot["lifecycleState"], + currentTaskId: row.current_task_id == null ? undefined : String(row.current_task_id), + continuationEpoch: Number(row.continuation_epoch), + carrierConversationFingerprint: row.carrier_conversation_fingerprint == null + ? undefined + : String(row.carrier_conversation_fingerprint), + checkpoint, + }; + } + + private assertTargetCarrierAvailable(targetCarrierFingerprint: string, workerId: string): void { + const row = this.database.sqlite.prepare(` + select id from chat_swarm_workers + where carrier_conversation_fingerprint=? and lifecycle_state <> 'DISABLED' + limit 1 + `).get(targetCarrierFingerprint) as Row | undefined; + if (row && String(row.id) !== workerId) { + throw new ChatSwarmError("OWNERSHIP_CONFLICT", "target carrier is already bound to another worker"); + } + } + + private getDurableByAttempt(durableAttemptKey: string): DurableRow | undefined { + return this.database.sqlite.prepare( + "select * from durable_operations where scope_root=? and attempt_key=? limit 1", + ).get(this.scopeRoot, durableAttemptKey) as DurableRow | undefined; + } + + private listPendingDurable(): DurableRow[] { + return this.database.sqlite.prepare( + "select * from durable_operations where kind=? and scope_root=? and status='started'", + ).all(KIND, this.scopeRoot) as DurableRow[]; + } + + private requireDurable(requestId: string): DurableRow { + const row = this.database.sqlite.prepare( + "select * from durable_operations where operation_id=? and kind=? limit 1", + ).get(requestId, KIND) as DurableRow | undefined; + if (!row) throw new ChatSwarmError("REQUEST_NOT_FOUND", "continuation request not found"); + return row; + } + + private expireDurable(requestId: string, now: string): void { + this.database.sqlite.prepare(` + update durable_operations + set status='failed',retry_safe='false',error_code='EXPIRED', + error_message='Continuation request expired before transfer.',updated_at=? + where operation_id=? and kind=? and status='started' + `).run(now, requestId, KIND); + } + + private assertCommittedBinding(request: ChatSwarmContinuationRequest): void { + const worker = this.workerSnapshot(request.workerId); + if ( + worker.continuationEpoch !== request.targetEpoch || + worker.carrierConversationFingerprint !== request.targetCarrierFingerprint + ) { + throw new ChatSwarmError("RECONCILIATION_REQUIRED", "approved continuation does not match current worker binding"); + } + } + + private toRequest(row: DurableRow): ChatSwarmContinuationRequest { + if (row.kind !== KIND || row.authority_mode !== "OWNER_DIRECT") { + throw new ChatSwarmError("INVALID_STATE", "durable continuation operation authority is malformed"); + } + let persisted: PersistedRequest; + try { + persisted = JSON.parse(row.request_json) as PersistedRequest; + } catch { + throw new ChatSwarmError("INVALID_STATE", "corrupt persisted continuation request"); + } + validatePersistedRequest(persisted); + const requestHash = continuationMaterialHash(persisted); + if (requestHash !== row.request_hash) { + throw new ChatSwarmError("INVALID_STATE", "persisted continuation request hash mismatch"); + } + + let status: ChatSwarmContinuationRequest["status"]; + if (row.status === "started") status = "PENDING"; + else if (row.status === "succeeded") status = "APPROVED"; + else if (row.status === "failed" && row.error_code === "EXPIRED") status = "EXPIRED"; + else if (row.status === "outcome_unknown") status = "RECONCILE_REQUIRED"; + else throw new ChatSwarmError("INVALID_STATE", `unsupported durable continuation status '${row.status}'`); + + let approvedAt: string | undefined; + if (row.receipt_json) { + try { + const receipt = JSON.parse(row.receipt_json) as PersistedReceipt; + if (receipt.schema !== RECEIPT_SCHEMA) throw new Error("receipt schema mismatch"); + if ( + receipt.targetEpoch !== persisted.targetEpoch || + receipt.targetCarrierFingerprint !== persisted.targetCarrierFingerprint || + receipt.checkpointHash !== persisted.checkpointHash + ) throw new Error("receipt binding mismatch"); + approvedAt = receipt.approvedAt; + } catch { + throw new ChatSwarmError("INVALID_STATE", "corrupt persisted continuation receipt"); + } + } + + return { + id: row.operation_id, + swarmId: persisted.swarmId, + workerId: persisted.workerId, + attemptKey: persisted.attemptKey, + requestHash: row.request_hash, + sourceEpoch: persisted.sourceEpoch, + targetEpoch: persisted.targetEpoch, + sourceCarrierFingerprint: persisted.sourceCarrierFingerprint, + targetCarrierFingerprint: persisted.targetCarrierFingerprint, + checkpointHash: persisted.checkpointHash, + version: row.status === "started" ? 1 : 2, + status, + requestedAt: persisted.requestedAt, + expiresAt: persisted.expiresAt, + approvedAt, + }; + } + + private nowIso(): string { + return this.clock().toISOString(); + } +} + +function stableAttemptKey(swarmId: string, workerId: string, attemptKey: string): string { + return `continuation:${createHash("sha256") + .update(JSON.stringify({ swarmId, workerId, attemptKey })) + .digest("hex")}`; +} + +function continuationMaterialHash(request: PersistedRequest): string { + return createHash("sha256").update(JSON.stringify({ + checkpointHash: request.checkpointHash, + sourceCarrierFingerprint: request.sourceCarrierFingerprint, + sourceEpoch: request.sourceEpoch, + swarmId: request.swarmId, + targetCarrierFingerprint: request.targetCarrierFingerprint, + targetEpoch: request.targetEpoch, + workerId: request.workerId, + })).digest("hex"); +} + +function validatePersistedRequest(request: PersistedRequest): void { + if (!request || request.schema !== REQUEST_SCHEMA) { + throw new ChatSwarmError("INVALID_STATE", "persisted continuation request schema mismatch"); + } + for (const [label, value] of [ + ["source carrier fingerprint", request.sourceCarrierFingerprint], + ["target carrier fingerprint", request.targetCarrierFingerprint], + ["checkpoint hash", request.checkpointHash], + ] as const) assertFingerprint(value, label); + if (!Number.isSafeInteger(request.sourceEpoch) || request.sourceEpoch < 0 || request.targetEpoch !== request.sourceEpoch + 1) { + throw new ChatSwarmError("INVALID_STATE", "persisted continuation epoch binding is malformed"); + } + if (!request.swarmId || !request.workerId || !request.attemptKey) { + throw new ChatSwarmError("INVALID_STATE", "persisted continuation identity is incomplete"); + } + if (!Number.isFinite(Date.parse(request.requestedAt)) || !Number.isFinite(Date.parse(request.expiresAt))) { + throw new ChatSwarmError("INVALID_STATE", "persisted continuation timestamps are malformed"); + } +} + +function assertFingerprint(value: string, label: string): void { + if (!SHA256.test(value)) { + throw new ChatSwarmError("INVALID_INPUT", `${label} must be a SHA-256 fingerprint`); + } +} From edfcf3be98bcc800aaabdfa44c755b12f7f00ad7 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 13:55:42 +0800 Subject: [PATCH 09/55] fix(chat-swarm): make continuation request revisions monotonic --- src/chat-swarm-continuation-store.ts | 84 ++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 18 deletions(-) diff --git a/src/chat-swarm-continuation-store.ts b/src/chat-swarm-continuation-store.ts index 1d6cf07ee..4554fa4e1 100644 --- a/src/chat-swarm-continuation-store.ts +++ b/src/chat-swarm-continuation-store.ts @@ -45,6 +45,7 @@ interface DurableRow { interface PersistedRequest { schema: typeof REQUEST_SCHEMA; + version: number; swarmId: string; workerId: string; attemptKey: string; @@ -95,8 +96,7 @@ export class ChatSwarmContinuationStore { } const tx = this.database.sqlite.transaction(() => { - const swarm = this.requireActiveSwarm(input.swarmId); - void swarm; + this.requireActiveSwarm(input.swarmId); const worker = this.workerSnapshot(input.workerId); const material = prepareContinuationMaterial( worker, @@ -129,6 +129,7 @@ export class ChatSwarmContinuationStore { const expiresAt = new Date(this.clock().getTime() + ttlSeconds * 1000).toISOString(); const request: PersistedRequest = { schema: REQUEST_SCHEMA, + version: 1, swarmId: input.swarmId, workerId: input.workerId, attemptKey: input.attemptKey, @@ -244,12 +245,19 @@ export class ChatSwarmContinuationStore { targetCarrierFingerprint: request.targetCarrierFingerprint, checkpointHash: request.checkpointHash, }; + const nextRequest = persistedFromRequest(request, request.version + 1); const operationUpdate = this.database.sqlite.prepare(` update durable_operations - set status='succeeded',retry_safe='false',receipt_json=?,error_code=null, + set status='succeeded',retry_safe='false',request_json=?,receipt_json=?,error_code=null, error_message=null,updated_at=? where operation_id=? and kind=? and status='started' - `).run(JSON.stringify(receipt), now, request.id, KIND); + `).run( + JSON.stringify(nextRequest), + JSON.stringify(receipt), + now, + request.id, + KIND, + ); if (operationUpdate.changes !== 1) { throw new ChatSwarmError("VERSION_CONFLICT", "continuation request changed during transfer"); } @@ -295,14 +303,25 @@ export class ChatSwarmContinuationStore { recoverAfterRestart(): number { const now = this.nowIso(); - const result = this.database.sqlite.prepare(` - update durable_operations - set status='outcome_unknown',retry_safe='false',error_code='RECONCILIATION_REQUIRED', - error_message='DevSpace restarted while continuation was pending; reconcile exact binding before approval.', - updated_at=? - where kind=? and status='started' - `).run(now, KIND); - return Number(result.changes); + const tx = this.database.sqlite.transaction(() => { + const rows = this.database.sqlite.prepare( + "select * from durable_operations where kind=? and scope_root=? and status='started'", + ).all(KIND, this.scopeRoot) as DurableRow[]; + for (const row of rows) { + const request = this.toRequest(row); + const nextRequest = persistedFromRequest(request, request.version + 1); + this.database.sqlite.prepare(` + update durable_operations + set status='outcome_unknown',retry_safe='false',request_json=?, + error_code='RECONCILIATION_REQUIRED', + error_message='DevSpace restarted while continuation was pending; reconcile exact binding before approval.', + updated_at=? + where operation_id=? and kind=? and status='started' + `).run(JSON.stringify(nextRequest), now, row.operation_id, KIND); + } + return rows.length; + }); + return tx.immediate(); } reconcileUnknownNoEffect( @@ -328,11 +347,12 @@ export class ChatSwarmContinuationStore { this.expireDurable(request.id, now); throw new ChatSwarmError("EXPIRED", "continuation request expired during reconciliation"); } + const nextRequest = persistedFromRequest(request, request.version + 1); const result = this.database.sqlite.prepare(` update durable_operations - set status='started',error_code=null,error_message=null,updated_at=? + set status='started',request_json=?,error_code=null,error_message=null,updated_at=? where operation_id=? and kind=? and status='outcome_unknown' - `).run(now, request.id, KIND); + `).run(JSON.stringify(nextRequest), now, request.id, KIND); if (result.changes !== 1) { throw new ChatSwarmError("CAS_DRIFT", "continuation reconciliation changed concurrently"); } @@ -409,12 +429,16 @@ export class ChatSwarmContinuationStore { } private expireDurable(requestId: string, now: string): void { + const durable = this.requireDurable(requestId); + const request = this.toRequest(durable); + if (request.status !== "PENDING") return; + const nextRequest = persistedFromRequest(request, request.version + 1); this.database.sqlite.prepare(` update durable_operations - set status='failed',retry_safe='false',error_code='EXPIRED', + set status='failed',retry_safe='false',request_json=?,error_code='EXPIRED', error_message='Continuation request expired before transfer.',updated_at=? where operation_id=? and kind=? and status='started' - `).run(now, requestId, KIND); + `).run(JSON.stringify(nextRequest), now, requestId, KIND); } private assertCommittedBinding(request: ChatSwarmContinuationRequest): void { @@ -458,7 +482,8 @@ export class ChatSwarmContinuationStore { if ( receipt.targetEpoch !== persisted.targetEpoch || receipt.targetCarrierFingerprint !== persisted.targetCarrierFingerprint || - receipt.checkpointHash !== persisted.checkpointHash + receipt.checkpointHash !== persisted.checkpointHash || + !Number.isFinite(Date.parse(receipt.approvedAt)) ) throw new Error("receipt binding mismatch"); approvedAt = receipt.approvedAt; } catch { @@ -477,7 +502,7 @@ export class ChatSwarmContinuationStore { sourceCarrierFingerprint: persisted.sourceCarrierFingerprint, targetCarrierFingerprint: persisted.targetCarrierFingerprint, checkpointHash: persisted.checkpointHash, - version: row.status === "started" ? 1 : 2, + version: persisted.version, status, requestedAt: persisted.requestedAt, expiresAt: persisted.expiresAt, @@ -508,10 +533,33 @@ function continuationMaterialHash(request: PersistedRequest): string { })).digest("hex"); } +function persistedFromRequest( + request: ChatSwarmContinuationRequest, + version: number, +): PersistedRequest { + return { + schema: REQUEST_SCHEMA, + version, + swarmId: request.swarmId, + workerId: request.workerId, + attemptKey: request.attemptKey, + sourceEpoch: request.sourceEpoch, + targetEpoch: request.targetEpoch, + sourceCarrierFingerprint: request.sourceCarrierFingerprint, + targetCarrierFingerprint: request.targetCarrierFingerprint, + checkpointHash: request.checkpointHash, + requestedAt: request.requestedAt, + expiresAt: request.expiresAt, + }; +} + function validatePersistedRequest(request: PersistedRequest): void { if (!request || request.schema !== REQUEST_SCHEMA) { throw new ChatSwarmError("INVALID_STATE", "persisted continuation request schema mismatch"); } + if (!Number.isSafeInteger(request.version) || request.version < 1) { + throw new ChatSwarmError("INVALID_STATE", "persisted continuation request version is malformed"); + } for (const [label, value] of [ ["source carrier fingerprint", request.sourceCarrierFingerprint], ["target carrier fingerprint", request.targetCarrierFingerprint], From 74aa8467602ada75da2d714f3d8e1076379afa16 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 13:56:31 +0800 Subject: [PATCH 10/55] test(chat-swarm): exercise durable continuation transfer --- src/chat-swarm-continuation-store.test.ts | 246 ++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 src/chat-swarm-continuation-store.test.ts diff --git a/src/chat-swarm-continuation-store.test.ts b/src/chat-swarm-continuation-store.test.ts new file mode 100644 index 000000000..daf827e1a --- /dev/null +++ b/src/chat-swarm-continuation-store.test.ts @@ -0,0 +1,246 @@ +import assert from "node:assert/strict"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { ChatSwarmError } from "./chat-swarm-contract.js"; +import { ChatSwarmCoordinator } from "./chat-swarm-coordinator.js"; +import { ChatSwarmStore } from "./chat-swarm-store.js"; +import { ChatSwarmContinuationStore } from "./chat-swarm-continuation-store.js"; +import { resolveChatSwarmIdentity } from "./request-meta.js"; + +function fixture() { + const root = mkdtempSync(join(tmpdir(), "swarm-continuation-store-")); + const swarmStore = new ChatSwarmStore(root); + const coordinator = new ChatSwarmCoordinator(swarmStore); + const ownerMeta = { "openai/session": "continuation-owner" }; + const sourceMeta = { "openai/conversation_id": "continuation-source" }; + const targetMeta = { "openai/conversation_id": "continuation-target" }; + const ownerFingerprint = resolveChatSwarmIdentity(ownerMeta).fingerprint; + const targetFingerprint = resolveChatSwarmIdentity(targetMeta).fingerprint; + const swarm = coordinator.createSwarm(ownerMeta, { workerLimit: 3, metadata: { test: true } }); + const worker = coordinator.joinWorker(sourceMeta, swarm.id, { + label: "Worker-01", + runtimeKind: "mcp_peer", + }); + coordinator.checkpoint( + sourceMeta, + worker.id, + 0, + new Date(Date.now() + 60 * 60 * 1000).toISOString(), + { lastTaskId: "task-safe", summary: "safe checkpoint" }, + ); + return { + root, + swarmStore, + coordinator, + ownerMeta, + sourceMeta, + targetMeta, + ownerFingerprint, + targetFingerprint, + swarm, + worker, + }; +} + +function cleanup(f: ReturnType, continuation?: ChatSwarmContinuationStore) { + continuation?.close(); + f.swarmStore.close(); + rmSync(f.root, { recursive: true, force: true }); +} + +function createInput(f: ReturnType, attemptKey = "continuation-attempt-1") { + return { + swarmId: f.swarm.id, + workerId: f.worker.id, + attemptKey, + sourceEpoch: 0, + }; +} + +test("durable continuation request replays exactly and rejects changed target material", () => { + const f = fixture(); + const continuation = new ChatSwarmContinuationStore(f.root); + try { + const first = continuation.createRequest(f.targetFingerprint, createInput(f)); + assert.equal(first.created, true); + assert.equal(first.request.status, "PENDING"); + assert.equal(first.request.version, 1); + assert.equal(first.request.sourceEpoch, 0); + assert.equal(first.request.targetEpoch, 1); + + const replay = continuation.createRequest(f.targetFingerprint, createInput(f)); + assert.equal(replay.created, false); + assert.equal(replay.request.id, first.request.id); + assert.equal(replay.request.requestHash, first.request.requestHash); + + const otherTarget = resolveChatSwarmIdentity({ "openai/conversation_id": "continuation-target-other" }).fingerprint; + assert.throws( + () => continuation.createRequest(otherTarget, createInput(f)), + (error: unknown) => error instanceof ChatSwarmError && error.code === "REPLAY_CONFLICT", + ); + + assert.throws( + () => continuation.createRequest(f.targetFingerprint, createInput(f, "continuation-attempt-2")), + (error: unknown) => error instanceof ChatSwarmError && error.code === "OWNERSHIP_CONFLICT", + ); + } finally { + cleanup(f, continuation); + } +}); + +test("owner approval atomically transfers epoch and fences the old carrier", () => { + const f = fixture(); + const continuation = new ChatSwarmContinuationStore(f.root); + try { + const created = continuation.createRequest(f.targetFingerprint, createInput(f)); + const approved = continuation.approveRequest(f.ownerFingerprint, { + swarmId: f.swarm.id, + requestId: created.request.id, + expectedRequestVersion: 1, + expectedSwarmVersion: 1, + }); + assert.equal(approved.status, "APPROVED"); + assert.equal(approved.version, 2); + assert.equal(approved.targetEpoch, 1); + assert.ok(approved.approvedAt); + + const rebound = f.swarmStore.getWorker(f.worker.id)!; + assert.equal(rebound.continuationEpoch, 1); + assert.equal(rebound.carrierConversationFingerprint, f.targetFingerprint); + assert.equal(f.coordinator.peerStatus(f.targetMeta, f.swarm.id).state, "BOUND"); + + assert.throws( + () => f.coordinator.nextTask(f.sourceMeta, f.worker.id), + (error: unknown) => error instanceof ChatSwarmError && error.code === "OWNERSHIP_CONFLICT", + ); + + const replay = continuation.approveRequest(f.ownerFingerprint, { + swarmId: f.swarm.id, + requestId: created.request.id, + expectedRequestVersion: 1, + expectedSwarmVersion: 1, + }); + assert.equal(replay.status, "APPROVED"); + assert.equal(replay.version, 2); + assert.equal(f.swarmStore.getSwarm(f.swarm.id)!.revision, 2); + } finally { + cleanup(f, continuation); + } +}); + +test("checkpoint drift and active work fail closed before epoch transfer", () => { + const f = fixture(); + const continuation = new ChatSwarmContinuationStore(f.root); + try { + const created = continuation.createRequest(f.targetFingerprint, createInput(f)); + f.coordinator.checkpoint( + f.sourceMeta, + f.worker.id, + 0, + new Date(Date.now() + 60 * 60 * 1000).toISOString(), + { lastTaskId: "task-safe", summary: "changed checkpoint" }, + ); + assert.throws( + () => continuation.approveRequest(f.ownerFingerprint, { + swarmId: f.swarm.id, + requestId: created.request.id, + expectedRequestVersion: 1, + expectedSwarmVersion: 1, + }), + (error: unknown) => error instanceof ChatSwarmError && error.code === "CAS_DRIFT", + ); + } finally { + cleanup(f, continuation); + } + + const busy = fixture(); + const continuationBusy = new ChatSwarmContinuationStore(busy.root); + try { + busy.coordinator.dispatch(busy.ownerMeta, { + swarmId: busy.swarm.id, + taskKey: "busy-task", + prompt: "bounded read task", + preferredWorkerId: busy.worker.id, + }); + assert.throws( + () => continuationBusy.createRequest(busy.targetFingerprint, createInput(busy)), + (error: unknown) => error instanceof ChatSwarmError && error.code === "RECONCILIATION_REQUIRED", + ); + } finally { + cleanup(busy, continuationBusy); + } +}); + +test("restart marks pending continuation unknown and exact no-effect reconciliation restores same request", () => { + const f = fixture(); + const continuation = new ChatSwarmContinuationStore(f.root); + try { + const created = continuation.createRequest(f.targetFingerprint, createInput(f)); + assert.equal(continuation.recoverAfterRestart(), 1); + const unknown = continuation.getRequest(created.request.id)!; + assert.equal(unknown.status, "RECONCILE_REQUIRED"); + assert.equal(unknown.version, 2); + + assert.throws( + () => continuation.approveRequest(f.ownerFingerprint, { + swarmId: f.swarm.id, + requestId: created.request.id, + expectedRequestVersion: 2, + expectedSwarmVersion: 1, + }), + (error: unknown) => error instanceof ChatSwarmError && error.code === "RECONCILIATION_REQUIRED", + ); + + const reconciled = continuation.reconcileUnknownNoEffect(f.ownerFingerprint, created.request.id); + assert.equal(reconciled.status, "PENDING"); + assert.equal(reconciled.version, 3); + assert.equal(reconciled.id, created.request.id); + + const approved = continuation.approveRequest(f.ownerFingerprint, { + swarmId: f.swarm.id, + requestId: created.request.id, + expectedRequestVersion: 3, + expectedSwarmVersion: 1, + }); + assert.equal(approved.status, "APPROVED"); + assert.equal(approved.version, 4); + } finally { + cleanup(f, continuation); + } +}); + +test("expired continuation cannot transfer and target readback remains bounded to exact target", () => { + const f = fixture(); + let now = new Date("2026-09-13T05:00:00.000Z"); + const continuation = new ChatSwarmContinuationStore(f.root, () => now); + try { + const created = continuation.createRequest(f.targetFingerprint, { + ...createInput(f), + ttlSeconds: 1, + }); + const targetRead = continuation.getLatestForTarget(f.swarm.id, f.targetFingerprint); + assert.equal(targetRead?.id, created.request.id); + + const otherFingerprint = resolveChatSwarmIdentity({ "openai/conversation_id": "not-the-target" }).fingerprint; + assert.equal(continuation.getLatestForTarget(f.swarm.id, otherFingerprint), undefined); + + now = new Date("2026-09-13T05:00:02.000Z"); + assert.throws( + () => continuation.approveRequest(f.ownerFingerprint, { + swarmId: f.swarm.id, + requestId: created.request.id, + expectedRequestVersion: 1, + expectedSwarmVersion: 1, + }), + (error: unknown) => error instanceof ChatSwarmError && error.code === "EXPIRED", + ); + const expired = continuation.getRequest(created.request.id)!; + assert.equal(expired.status, "EXPIRED"); + assert.equal(expired.version, 2); + assert.equal(f.swarmStore.getWorker(f.worker.id)!.continuationEpoch, 0); + } finally { + cleanup(f, continuation); + } +}); From 411c327fd34e222b7e5669fe0a2c6595b0b07957 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 13:57:02 +0800 Subject: [PATCH 11/55] test(chat-swarm): include durable continuation regressions --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d4a8b21e8..0e7643b8c 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "postinstall": "node scripts/fix-node-pty-permissions.mjs", "test:node-pty-permissions": "node --import tsx --test src/node-pty-postinstall.test.ts", "start": "node dist/cli.js serve", - "test": "npm run test:node-pty-permissions && npm run test:carrier-binding && tsx src/coordination-reader-loader.test.ts && tsx src/control-plane-ownership.test.ts && tsx src/control-plane-handoff.test.ts && tsx src/control-plane-continuation.test.ts && tsx src/deployment-convergence.test.ts && tsx src/current-completion-matrix.test.ts && tsx src/control-plane-consumer.test.ts && tsx src/local-agent-cline-catalog.test.ts && tsx src/chat-swarm-contract.test.ts && tsx src/chat-swarm-store.test.ts && tsx src/chat-swarm-runtime-owner.test.ts && tsx src/local-agent-opencode-mcp-catalog.test.ts && tsx src/chat-swarm-coordinator.test.ts && tsx src/chat-swarm-peer-runtime.test.ts && tsx src/chat-swarm-tools.test.ts && tsx src/chat-swarm-lifecycle.test.ts && tsx src/chat-swarm-carrier.test.ts && tsx src/chat-swarm-continuation-domain.test.ts && tsx src/chat-swarm-peer-admission.test.ts && tsx src/chat-swarm-task-ledger.test.ts && tsx src/git-worktrees.test.ts && tsx src/execution-protocol.test.ts && tsx src/durable-operations-ci.test.ts && tsx src/git-candidate.test.ts && tsx src/config.test.ts && tsx src/onboarding.test.ts && tsx src/cli-workspace.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions-ci.test.ts && tsx src/codex-goal-sessions-ci.test.ts && tsx src/mcp-sessions.test.ts && tsx src/cutover-state.test.ts && tsx src/cutover-state-recovery-guard.test.ts && tsx src/cutover-build-ready.test.ts && tsx src/cutover-orchestration.test.ts && tsx src/mcp-cutover.test.ts && tsx src/cutover-restart.test.ts && tsx src/cutover-http.test.ts && tsx src/cutover-recovery.test.ts && tsx src/cutover-binding-repair.test.ts && tsx src/capability-manifest.test.ts && tsx src/server-shutdown.test.ts && tsx src/codex-runtime.test.ts && tsx src/local-agent-config.test.ts && tsx src/local-agent-catalog.test.ts && tsx src/local-agent-presentation.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-daemon-lifecycle.test.ts && tsx src/local-agent-daemon-protocol.test.ts && tsx src/local-agent-daemon.test.ts && tsx src/local-agent-codex.test.ts && tsx src/local-agent-opencode.test.ts && tsx src/local-agent-opencode-catalog.test.ts && tsx src/local-agent-acp.test.ts && tsx src/local-agent-grok.test.ts && tsx src/local-agent-pi-sandbox.test.ts && tsx src/local-agent-pi.test.ts && tsx src/local-agent-claude.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-profile-source.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-toolchains.test.ts && tsx src/local-agent-idle-policy.test.ts && tsx src/local-agent-capacity.test.ts && tsx src/local-agent-execution-contract.test.ts && tsx src/local-agent-continuation.test.ts && tsx src/provider-scratch.test.ts && tsx src/git-integration-ci.test.ts && tsx src/repository-intelligence.test.ts && tsx src/local-agent-store.test.ts && tsx src/local-agent-manager.test.ts && tsx src/roots.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/conversation-isolation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/server-ci.test.ts && tsx src/oauth-store.test.ts && tsx src/cli-ci.test.ts && tsx src/oauth-json-and-child-reap.test.ts && tsx src/local-agent-errors.test.ts && tsx src/provider-output-redaction.test.ts && tsx src/host-operation-policy.test.ts && tsx src/host-operations.test.ts && tsx src/host-operations-http.test.ts", + "test": "npm run test:node-pty-permissions && npm run test:carrier-binding && tsx src/coordination-reader-loader.test.ts && tsx src/control-plane-ownership.test.ts && tsx src/control-plane-handoff.test.ts && tsx src/control-plane-continuation.test.ts && tsx src/deployment-convergence.test.ts && tsx src/current-completion-matrix.test.ts && tsx src/control-plane-consumer.test.ts && tsx src/local-agent-cline-catalog.test.ts && tsx src/chat-swarm-contract.test.ts && tsx src/chat-swarm-store.test.ts && tsx src/chat-swarm-runtime-owner.test.ts && tsx src/local-agent-opencode-mcp-catalog.test.ts && tsx src/chat-swarm-coordinator.test.ts && tsx src/chat-swarm-peer-runtime.test.ts && tsx src/chat-swarm-tools.test.ts && tsx src/chat-swarm-lifecycle.test.ts && tsx src/chat-swarm-carrier.test.ts && tsx src/chat-swarm-continuation-domain.test.ts && tsx src/chat-swarm-continuation-store.test.ts && tsx src/chat-swarm-peer-admission.test.ts && tsx src/chat-swarm-task-ledger.test.ts && tsx src/git-worktrees.test.ts && tsx src/execution-protocol.test.ts && tsx src/durable-operations-ci.test.ts && tsx src/git-candidate.test.ts && tsx src/config.test.ts && tsx src/onboarding.test.ts && tsx src/cli-workspace.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions-ci.test.ts && tsx src/codex-goal-sessions-ci.test.ts && tsx src/mcp-sessions.test.ts && tsx src/cutover-state.test.ts && tsx src/cutover-state-recovery-guard.test.ts && tsx src/cutover-build-ready.test.ts && tsx src/cutover-orchestration.test.ts && tsx src/mcp-cutover.test.ts && tsx src/cutover-restart.test.ts && tsx src/cutover-http.test.ts && tsx src/cutover-recovery.test.ts && tsx src/cutover-binding-repair.test.ts && tsx src/capability-manifest.test.ts && tsx src/server-shutdown.test.ts && tsx src/codex-runtime.test.ts && tsx src/local-agent-config.test.ts && tsx src/local-agent-catalog.test.ts && tsx src/local-agent-presentation.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-daemon-lifecycle.test.ts && tsx src/local-agent-daemon-protocol.test.ts && tsx src/local-agent-daemon.test.ts && tsx src/local-agent-codex.test.ts && tsx src/local-agent-opencode.test.ts && tsx src/local-agent-opencode-catalog.test.ts && tsx src/local-agent-acp.test.ts && tsx src/local-agent-grok.test.ts && tsx src/local-agent-pi-sandbox.test.ts && tsx src/local-agent-pi.test.ts && tsx src/local-agent-claude.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-profile-source.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-toolchains.test.ts && tsx src/local-agent-idle-policy.test.ts && tsx src/local-agent-capacity.test.ts && tsx src/local-agent-execution-contract.test.ts && tsx src/local-agent-continuation.test.ts && tsx src/provider-scratch.test.ts && tsx src/git-integration-ci.test.ts && tsx src/repository-intelligence.test.ts && tsx src/local-agent-store.test.ts && tsx src/local-agent-manager.test.ts && tsx src/roots.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/conversation-isolation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/server-ci.test.ts && tsx src/oauth-store.test.ts && tsx src/cli-ci.test.ts && tsx src/oauth-json-and-child-reap.test.ts && tsx src/local-agent-errors.test.ts && tsx src/provider-output-redaction.test.ts && tsx src/host-operation-policy.test.ts && tsx src/host-operations.test.ts && tsx src/host-operations-http.test.ts", "typecheck": "tsc -p tsconfig.json --noEmit", "test:carrier-binding": "tsx src/carrier-binding.test.ts && tsx src/carrier-binding-http.test.ts", "test:local-agent-sessions": "node --import tsx --test src/local-agent-sessions.test.ts" From 43d2603ef43a0a6b21db6ec9c86e8d388bc50a05 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 13:57:20 +0800 Subject: [PATCH 12/55] feat(chat-swarm): bind continuation actions to authenticated identities --- src/chat-swarm-continuation-coordinator.ts | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/chat-swarm-continuation-coordinator.ts diff --git a/src/chat-swarm-continuation-coordinator.ts b/src/chat-swarm-continuation-coordinator.ts new file mode 100644 index 000000000..35472a8c6 --- /dev/null +++ b/src/chat-swarm-continuation-coordinator.ts @@ -0,0 +1,38 @@ +import type { + ApproveContinuationRequestInput, + ChatSwarmContinuationRequest, + CreateContinuationRequestInput, +} from "./chat-swarm-continuation-contract.js"; +import { ChatSwarmContinuationStore } from "./chat-swarm-continuation-store.js"; +import { resolveChatSwarmIdentity } from "./request-meta.js"; + +export class ChatSwarmContinuationCoordinator { + constructor(readonly store: ChatSwarmContinuationStore) {} + + request( + meta: unknown, + input: CreateContinuationRequestInput, + ): { request: ChatSwarmContinuationRequest; created: boolean } { + const identity = resolveChatSwarmIdentity(meta); + return this.store.createRequest(identity.fingerprint, input); + } + + targetStatus(meta: unknown, swarmId: string): ChatSwarmContinuationRequest | undefined { + const identity = resolveChatSwarmIdentity(meta); + return this.store.getLatestForTarget(swarmId, identity.fingerprint); + } + + approve(meta: unknown, input: ApproveContinuationRequestInput): ChatSwarmContinuationRequest { + const identity = resolveChatSwarmIdentity(meta); + return this.store.approveRequest(identity.fingerprint, input); + } + + reconcileNoEffect(meta: unknown, requestId: string): ChatSwarmContinuationRequest { + const identity = resolveChatSwarmIdentity(meta); + return this.store.reconcileUnknownNoEffect(identity.fingerprint, requestId); + } + + recoverAfterRestart(): number { + return this.store.recoverAfterRestart(); + } +} From 1d9d7bd63c57c5a02e50e3ad6702dbab8904c2da Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 13:57:41 +0800 Subject: [PATCH 13/55] test(chat-swarm): bind continuation control to authenticated caller identity --- ...hat-swarm-continuation-coordinator.test.ts | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 src/chat-swarm-continuation-coordinator.test.ts diff --git a/src/chat-swarm-continuation-coordinator.test.ts b/src/chat-swarm-continuation-coordinator.test.ts new file mode 100644 index 000000000..713fc9dd8 --- /dev/null +++ b/src/chat-swarm-continuation-coordinator.test.ts @@ -0,0 +1,123 @@ +import assert from "node:assert/strict"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { ChatSwarmError } from "./chat-swarm-contract.js"; +import { ChatSwarmContinuationCoordinator } from "./chat-swarm-continuation-coordinator.js"; +import { ChatSwarmContinuationStore } from "./chat-swarm-continuation-store.js"; +import { ChatSwarmCoordinator } from "./chat-swarm-coordinator.js"; +import { ChatSwarmStore } from "./chat-swarm-store.js"; + +function fixture() { + const root = mkdtempSync(join(tmpdir(), "swarm-continuation-coordinator-")); + const swarmStore = new ChatSwarmStore(root); + const swarmCoordinator = new ChatSwarmCoordinator(swarmStore); + const continuationStore = new ChatSwarmContinuationStore(root); + const continuation = new ChatSwarmContinuationCoordinator(continuationStore); + const ownerMeta = { "openai/session": "continuation-owner" }; + const attackerMeta = { "openai/session": "continuation-attacker" }; + const sourceMeta = { "openai/conversation_id": "continuation-source" }; + const targetMeta = { "openai/conversation_id": "continuation-target" }; + const otherTargetMeta = { "openai/conversation_id": "continuation-other-target" }; + const swarm = swarmCoordinator.createSwarm(ownerMeta, { workerLimit: 3, metadata: { test: true } }); + const worker = swarmCoordinator.joinWorker(sourceMeta, swarm.id, { + label: "Worker-01", + runtimeKind: "mcp_peer", + }); + swarmCoordinator.checkpoint( + sourceMeta, + worker.id, + 0, + new Date(Date.now() + 60 * 60 * 1000).toISOString(), + { summary: "safe checkpoint" }, + ); + return { + root, + swarmStore, + continuationStore, + continuation, + ownerMeta, + attackerMeta, + sourceMeta, + targetMeta, + otherTargetMeta, + swarm, + worker, + }; +} + +function cleanup(f: ReturnType) { + f.continuationStore.close(); + f.swarmStore.close(); + rmSync(f.root, { recursive: true, force: true }); +} + +test("target request identity comes from authenticated metadata and exact target can read it", () => { + const f = fixture(); + try { + const created = f.continuation.request(f.targetMeta, { + swarmId: f.swarm.id, + workerId: f.worker.id, + attemptKey: "target-bound-1", + sourceEpoch: 0, + }); + assert.equal(created.created, true); + assert.equal(f.continuation.targetStatus(f.targetMeta, f.swarm.id)?.id, created.request.id); + assert.equal(f.continuation.targetStatus(f.otherTargetMeta, f.swarm.id), undefined); + + assert.throws( + () => f.continuation.request(f.sourceMeta, { + swarmId: f.swarm.id, + workerId: f.worker.id, + attemptKey: "source-cannot-replace-itself", + sourceEpoch: 0, + }), + (error: unknown) => error instanceof ChatSwarmError && error.code === "INVALID_INPUT", + ); + } finally { + cleanup(f); + } +}); + +test("only exact owner may approve, including idempotent approved readback", () => { + const f = fixture(); + try { + const created = f.continuation.request(f.targetMeta, { + swarmId: f.swarm.id, + workerId: f.worker.id, + attemptKey: "owner-bound-1", + sourceEpoch: 0, + }); + + assert.throws( + () => f.continuation.approve(f.attackerMeta, { + swarmId: f.swarm.id, + requestId: created.request.id, + expectedRequestVersion: 1, + expectedSwarmVersion: 1, + }), + (error: unknown) => error instanceof ChatSwarmError && error.code === "OWNERSHIP_CONFLICT", + ); + + const approved = f.continuation.approve(f.ownerMeta, { + swarmId: f.swarm.id, + requestId: created.request.id, + expectedRequestVersion: 1, + expectedSwarmVersion: 1, + }); + assert.equal(approved.status, "APPROVED"); + + assert.throws( + () => f.continuation.approve(f.attackerMeta, { + swarmId: f.swarm.id, + requestId: created.request.id, + expectedRequestVersion: 1, + expectedSwarmVersion: 1, + }), + (error: unknown) => error instanceof ChatSwarmError && error.code === "OWNERSHIP_CONFLICT", + ); + } finally { + cleanup(f); + } +}); From 18b5c38701f3f469a764643afc232dad495b3b1b Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 13:59:49 +0800 Subject: [PATCH 14/55] fix(chat-swarm): enforce owner before continuation approval readback --- src/chat-swarm-continuation-coordinator.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/chat-swarm-continuation-coordinator.ts b/src/chat-swarm-continuation-coordinator.ts index 35472a8c6..c401975e3 100644 --- a/src/chat-swarm-continuation-coordinator.ts +++ b/src/chat-swarm-continuation-coordinator.ts @@ -4,10 +4,14 @@ import type { CreateContinuationRequestInput, } from "./chat-swarm-continuation-contract.js"; import { ChatSwarmContinuationStore } from "./chat-swarm-continuation-store.js"; +import { ChatSwarmCoordinator } from "./chat-swarm-coordinator.js"; import { resolveChatSwarmIdentity } from "./request-meta.js"; export class ChatSwarmContinuationCoordinator { - constructor(readonly store: ChatSwarmContinuationStore) {} + constructor( + readonly store: ChatSwarmContinuationStore, + readonly swarmCoordinator: ChatSwarmCoordinator, + ) {} request( meta: unknown, @@ -23,11 +27,17 @@ export class ChatSwarmContinuationCoordinator { } approve(meta: unknown, input: ApproveContinuationRequestInput): ChatSwarmContinuationRequest { + this.swarmCoordinator.assertOwnerForLifecycle(meta, input.swarmId); const identity = resolveChatSwarmIdentity(meta); return this.store.approveRequest(identity.fingerprint, input); } - reconcileNoEffect(meta: unknown, requestId: string): ChatSwarmContinuationRequest { + reconcileNoEffect( + meta: unknown, + swarmId: string, + requestId: string, + ): ChatSwarmContinuationRequest { + this.swarmCoordinator.assertOwnerForLifecycle(meta, swarmId); const identity = resolveChatSwarmIdentity(meta); return this.store.reconcileUnknownNoEffect(identity.fingerprint, requestId); } From ec9c132236fafb1600ca1b3e63ad853b4a65f26c Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:00:10 +0800 Subject: [PATCH 15/55] test(chat-swarm): exercise coordinator owner boundary --- src/chat-swarm-continuation-coordinator.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chat-swarm-continuation-coordinator.test.ts b/src/chat-swarm-continuation-coordinator.test.ts index 713fc9dd8..fe4a70ffd 100644 --- a/src/chat-swarm-continuation-coordinator.test.ts +++ b/src/chat-swarm-continuation-coordinator.test.ts @@ -14,7 +14,7 @@ function fixture() { const swarmStore = new ChatSwarmStore(root); const swarmCoordinator = new ChatSwarmCoordinator(swarmStore); const continuationStore = new ChatSwarmContinuationStore(root); - const continuation = new ChatSwarmContinuationCoordinator(continuationStore); + const continuation = new ChatSwarmContinuationCoordinator(continuationStore, swarmCoordinator); const ownerMeta = { "openai/session": "continuation-owner" }; const attackerMeta = { "openai/session": "continuation-attacker" }; const sourceMeta = { "openai/conversation_id": "continuation-source" }; From 94a61dd42bc495fe320cd0b609b98bca3facdc82 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:00:41 +0800 Subject: [PATCH 16/55] test(chat-swarm): include continuation coordinator authority tests --- src/chat-swarm-continuation-domain.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/chat-swarm-continuation-domain.test.ts b/src/chat-swarm-continuation-domain.test.ts index 621b61e0b..1519f5c7e 100644 --- a/src/chat-swarm-continuation-domain.test.ts +++ b/src/chat-swarm-continuation-domain.test.ts @@ -1,5 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; +import "./chat-swarm-continuation-coordinator.test.js"; import { ChatSwarmError } from "./chat-swarm-contract.js"; import { assertContinuationCommitAllowed, From a911639d5ec1d979336ddd90d33c2a7c467deaa8 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:04:34 +0800 Subject: [PATCH 17/55] fix(chat-swarm): persist expiry and arbitrate continuation at commit --- src/chat-swarm-continuation-store.ts | 120 +++++++++++++++++---------- 1 file changed, 78 insertions(+), 42 deletions(-) diff --git a/src/chat-swarm-continuation-store.ts b/src/chat-swarm-continuation-store.ts index 4554fa4e1..c13395409 100644 --- a/src/chat-swarm-continuation-store.ts +++ b/src/chat-swarm-continuation-store.ts @@ -24,6 +24,7 @@ const RECEIPT_SCHEMA = "devspace.chat_swarm_continuation_receipt.v1"; const SHA256 = /^[0-9a-f]{64}$/; const DEFAULT_TTL_SECONDS = 15 * 60; const MAX_TTL_SECONDS = 60 * 60; +const MAX_PENDING_PER_WORKER = 10; type Row = Record; @@ -66,6 +67,10 @@ interface PersistedReceipt { checkpointHash: string; } +type ApprovalOutcome = + | { ok: true; request: ChatSwarmContinuationRequest } + | { ok: false; code: "EXPIRED" | "RECONCILIATION_REQUIRED"; message: string }; + export class ChatSwarmContinuationStore { private readonly database: DatabaseHandle; private readonly scopeRoot: string; @@ -115,14 +120,10 @@ export class ChatSwarmContinuationStore { } const now = this.nowIso(); - for (const pending of this.listPendingDurable()) { - const request = this.toRequest(pending); - if (request.workerId !== input.workerId) continue; - if (Date.parse(request.expiresAt) <= Date.parse(now)) { - this.expireDurable(pending.operation_id, now); - continue; - } - throw new ChatSwarmError("OWNERSHIP_CONFLICT", "worker already has a pending continuation request"); + this.persistExpiredPendingForWorker(input.workerId, now); + const pendingCount = this.listPendingDurableForWorker(input.workerId).length; + if (pendingCount >= MAX_PENDING_PER_WORKER) { + throw new ChatSwarmError("CAPACITY_FULL", "too many pending continuation requests for worker"); } const requestedAt = now; @@ -179,40 +180,52 @@ export class ChatSwarmContinuationStore { throw new ChatSwarmError("INVALID_INPUT", "expectedSwarmVersion must be a positive integer"); } - const tx = this.database.sqlite.transaction(() => { + const tx = this.database.sqlite.transaction((): ApprovalOutcome => { const durable = this.requireDurable(input.requestId); const request = this.toRequest(durable); if (request.swarmId !== input.swarmId) { throw new ChatSwarmError("OWNERSHIP_CONFLICT", "continuation request belongs to another swarm"); } + const swarm = this.requireActiveSwarm(request.swarmId); + if (String(swarm.owner_identity_fingerprint) !== ownerIdentityFingerprint) { + throw new ChatSwarmError("OWNERSHIP_CONFLICT", "controller identity does not own swarm"); + } + if (request.status === "APPROVED") { this.assertCommittedBinding(request); - return request; + return { ok: true, request }; } if (request.status === "EXPIRED") { - throw new ChatSwarmError("EXPIRED", "continuation request has expired"); + this.persistExpired(input.requestId, this.nowIso()); + return { ok: false, code: "EXPIRED", message: "continuation request has expired" }; } if (request.status === "RECONCILE_REQUIRED") { - throw new ChatSwarmError("RECONCILIATION_REQUIRED", "continuation outcome requires explicit reconciliation"); + return { + ok: false, + code: "RECONCILIATION_REQUIRED", + message: "continuation outcome requires explicit reconciliation", + }; } if (request.version !== input.expectedRequestVersion) { - throw new ChatSwarmError("VERSION_CONFLICT", `expected request version ${input.expectedRequestVersion} but found ${request.version}`); + throw new ChatSwarmError( + "VERSION_CONFLICT", + `expected request version ${input.expectedRequestVersion} but found ${request.version}`, + ); } const now = this.nowIso(); if (Date.parse(request.expiresAt) <= Date.parse(now)) { - this.expireDurable(request.id, now); - throw new ChatSwarmError("EXPIRED", "continuation request has expired"); + this.persistExpired(request.id, now); + return { ok: false, code: "EXPIRED", message: "continuation request has expired" }; } - const swarm = this.requireActiveSwarm(request.swarmId); - if (String(swarm.owner_identity_fingerprint) !== ownerIdentityFingerprint) { - throw new ChatSwarmError("OWNERSHIP_CONFLICT", "controller identity does not own swarm"); - } const swarmRevision = Number(swarm.revision ?? 1); if (swarmRevision !== input.expectedSwarmVersion) { - throw new ChatSwarmError("CAS_DRIFT", `swarm revision mismatch: expected ${input.expectedSwarmVersion} but found ${swarmRevision}`); + throw new ChatSwarmError( + "CAS_DRIFT", + `swarm revision mismatch: expected ${input.expectedSwarmVersion} but found ${swarmRevision}`, + ); } const worker = this.workerSnapshot(request.workerId); @@ -270,10 +283,12 @@ export class ChatSwarmContinuationStore { throw new ChatSwarmError("CAS_DRIFT", "swarm revision changed during continuation transfer"); } - return this.getRequest(request.id)!; + return { ok: true, request: this.getRequest(request.id)! }; }); - return tx.immediate(); + const outcome = tx.immediate(); + if (!outcome.ok) throw new ChatSwarmError(outcome.code, outcome.message); + return outcome.request; } getRequest(requestId: string): ChatSwarmContinuationRequest | undefined { @@ -329,7 +344,7 @@ export class ChatSwarmContinuationStore { requestId: string, ): ChatSwarmContinuationRequest { assertFingerprint(ownerIdentityFingerprint, "owner identity fingerprint"); - const tx = this.database.sqlite.transaction(() => { + const tx = this.database.sqlite.transaction((): ApprovalOutcome => { const durable = this.requireDurable(requestId); const request = this.toRequest(durable); if (request.status !== "RECONCILE_REQUIRED") { @@ -344,8 +359,8 @@ export class ChatSwarmContinuationStore { assertContinuationCommitAllowed(worker, pendingView); const now = this.nowIso(); if (Date.parse(request.expiresAt) <= Date.parse(now)) { - this.expireDurable(request.id, now); - throw new ChatSwarmError("EXPIRED", "continuation request expired during reconciliation"); + this.persistExpired(request.id, now); + return { ok: false, code: "EXPIRED", message: "continuation request expired during reconciliation" }; } const nextRequest = persistedFromRequest(request, request.version + 1); const result = this.database.sqlite.prepare(` @@ -356,9 +371,12 @@ export class ChatSwarmContinuationStore { if (result.changes !== 1) { throw new ChatSwarmError("CAS_DRIFT", "continuation reconciliation changed concurrently"); } - return this.getRequest(request.id)!; + return { ok: true, request: this.getRequest(request.id)! }; }); - return tx.immediate(); + + const outcome = tx.immediate(); + if (!outcome.ok) throw new ChatSwarmError(outcome.code, outcome.message); + return outcome.request; } private requireActiveSwarm(swarmId: string): Row { @@ -414,10 +432,18 @@ export class ChatSwarmContinuationStore { ).get(this.scopeRoot, durableAttemptKey) as DurableRow | undefined; } - private listPendingDurable(): DurableRow[] { - return this.database.sqlite.prepare( + private listPendingDurableForWorker(workerId: string): DurableRow[] { + const rows = this.database.sqlite.prepare( "select * from durable_operations where kind=? and scope_root=? and status='started'", ).all(KIND, this.scopeRoot) as DurableRow[]; + return rows.filter((row) => readPersistedRequest(row).workerId === workerId); + } + + private persistExpiredPendingForWorker(workerId: string, now: string): void { + for (const row of this.listPendingDurableForWorker(workerId)) { + const request = readPersistedRequest(row); + if (Date.parse(request.expiresAt) <= Date.parse(now)) this.persistExpired(row.operation_id, now); + } } private requireDurable(requestId: string): DurableRow { @@ -428,16 +454,16 @@ export class ChatSwarmContinuationStore { return row; } - private expireDurable(requestId: string, now: string): void { + private persistExpired(requestId: string, now: string): void { const durable = this.requireDurable(requestId); + if (durable.status !== "started" && durable.status !== "outcome_unknown") return; const request = this.toRequest(durable); - if (request.status !== "PENDING") return; const nextRequest = persistedFromRequest(request, request.version + 1); this.database.sqlite.prepare(` update durable_operations set status='failed',retry_safe='false',request_json=?,error_code='EXPIRED', error_message='Continuation request expired before transfer.',updated_at=? - where operation_id=? and kind=? and status='started' + where operation_id=? and kind=? and status in ('started','outcome_unknown') `).run(JSON.stringify(nextRequest), now, requestId, KIND); } @@ -455,21 +481,16 @@ export class ChatSwarmContinuationStore { if (row.kind !== KIND || row.authority_mode !== "OWNER_DIRECT") { throw new ChatSwarmError("INVALID_STATE", "durable continuation operation authority is malformed"); } - let persisted: PersistedRequest; - try { - persisted = JSON.parse(row.request_json) as PersistedRequest; - } catch { - throw new ChatSwarmError("INVALID_STATE", "corrupt persisted continuation request"); - } - validatePersistedRequest(persisted); + const persisted = readPersistedRequest(row); const requestHash = continuationMaterialHash(persisted); if (requestHash !== row.request_hash) { throw new ChatSwarmError("INVALID_STATE", "persisted continuation request hash mismatch"); } let status: ChatSwarmContinuationRequest["status"]; - if (row.status === "started") status = "PENDING"; - else if (row.status === "succeeded") status = "APPROVED"; + if (row.status === "started") { + status = Date.parse(persisted.expiresAt) <= this.clock().getTime() ? "EXPIRED" : "PENDING"; + } else if (row.status === "succeeded") status = "APPROVED"; else if (row.status === "failed" && row.error_code === "EXPIRED") status = "EXPIRED"; else if (row.status === "outcome_unknown") status = "RECONCILE_REQUIRED"; else throw new ChatSwarmError("INVALID_STATE", `unsupported durable continuation status '${row.status}'`); @@ -553,6 +574,17 @@ function persistedFromRequest( }; } +function readPersistedRequest(row: DurableRow): PersistedRequest { + let request: PersistedRequest; + try { + request = JSON.parse(row.request_json) as PersistedRequest; + } catch { + throw new ChatSwarmError("INVALID_STATE", "corrupt persisted continuation request"); + } + validatePersistedRequest(request); + return request; +} + function validatePersistedRequest(request: PersistedRequest): void { if (!request || request.schema !== REQUEST_SCHEMA) { throw new ChatSwarmError("INVALID_STATE", "persisted continuation request schema mismatch"); @@ -565,7 +597,11 @@ function validatePersistedRequest(request: PersistedRequest): void { ["target carrier fingerprint", request.targetCarrierFingerprint], ["checkpoint hash", request.checkpointHash], ] as const) assertFingerprint(value, label); - if (!Number.isSafeInteger(request.sourceEpoch) || request.sourceEpoch < 0 || request.targetEpoch !== request.sourceEpoch + 1) { + if ( + !Number.isSafeInteger(request.sourceEpoch) || + request.sourceEpoch < 0 || + request.targetEpoch !== request.sourceEpoch + 1 + ) { throw new ChatSwarmError("INVALID_STATE", "persisted continuation epoch binding is malformed"); } if (!request.swarmId || !request.workerId || !request.attemptKey) { From 1252dd0dfc7db750e6fd5f12c50c21d5650732a5 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:05:13 +0800 Subject: [PATCH 18/55] test(chat-swarm): cover bounded parallel continuation arbitration --- src/chat-swarm-continuation-store.test.ts | 55 ++++++++++++++++++++--- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/src/chat-swarm-continuation-store.test.ts b/src/chat-swarm-continuation-store.test.ts index daf827e1a..a0afc5e8f 100644 --- a/src/chat-swarm-continuation-store.test.ts +++ b/src/chat-swarm-continuation-store.test.ts @@ -59,7 +59,7 @@ function createInput(f: ReturnType, attemptKey = "continuation-a }; } -test("durable continuation request replays exactly and rejects changed target material", () => { +test("durable continuation request replays exactly and changed target conflicts on the same attempt", () => { const f = fixture(); const continuation = new ChatSwarmContinuationStore(f.root); try { @@ -75,16 +75,58 @@ test("durable continuation request replays exactly and rejects changed target ma assert.equal(replay.request.id, first.request.id); assert.equal(replay.request.requestHash, first.request.requestHash); - const otherTarget = resolveChatSwarmIdentity({ "openai/conversation_id": "continuation-target-other" }).fingerprint; + const otherTarget = resolveChatSwarmIdentity({ + "openai/conversation_id": "continuation-target-other", + }).fingerprint; assert.throws( () => continuation.createRequest(otherTarget, createInput(f)), (error: unknown) => error instanceof ChatSwarmError && error.code === "REPLAY_CONFLICT", ); + const second = continuation.createRequest( + otherTarget, + createInput(f, "continuation-attempt-2"), + ); + assert.equal(second.created, true); + assert.notEqual(second.request.id, first.request.id); + assert.equal(second.request.sourceEpoch, 0); + assert.equal(second.request.targetEpoch, 1); + } finally { + cleanup(f, continuation); + } +}); + +test("parallel prepared targets are bounded by commit-time epoch CAS", () => { + const f = fixture(); + const continuation = new ChatSwarmContinuationStore(f.root); + try { + const targetA = f.targetFingerprint; + const targetB = resolveChatSwarmIdentity({ + "openai/conversation_id": "continuation-target-b", + }).fingerprint; + const requestA = continuation.createRequest(targetA, createInput(f, "parallel-a")).request; + const requestB = continuation.createRequest(targetB, createInput(f, "parallel-b")).request; + + const winner = continuation.approveRequest(f.ownerFingerprint, { + swarmId: f.swarm.id, + requestId: requestA.id, + expectedRequestVersion: 1, + expectedSwarmVersion: 1, + }); + assert.equal(winner.status, "APPROVED"); + assert.equal(f.swarmStore.getWorker(f.worker.id)!.carrierConversationFingerprint, targetA); + assert.throws( - () => continuation.createRequest(f.targetFingerprint, createInput(f, "continuation-attempt-2")), - (error: unknown) => error instanceof ChatSwarmError && error.code === "OWNERSHIP_CONFLICT", + () => continuation.approveRequest(f.ownerFingerprint, { + swarmId: f.swarm.id, + requestId: requestB.id, + expectedRequestVersion: 1, + expectedSwarmVersion: 2, + }), + (error: unknown) => error instanceof ChatSwarmError && error.code === "CAS_DRIFT", ); + assert.equal(f.swarmStore.getWorker(f.worker.id)!.continuationEpoch, 1); + assert.equal(f.swarmStore.getWorker(f.worker.id)!.carrierConversationFingerprint, targetA); } finally { cleanup(f, continuation); } @@ -223,10 +265,13 @@ test("expired continuation cannot transfer and target readback remains bounded t const targetRead = continuation.getLatestForTarget(f.swarm.id, f.targetFingerprint); assert.equal(targetRead?.id, created.request.id); - const otherFingerprint = resolveChatSwarmIdentity({ "openai/conversation_id": "not-the-target" }).fingerprint; + const otherFingerprint = resolveChatSwarmIdentity({ + "openai/conversation_id": "not-the-target", + }).fingerprint; assert.equal(continuation.getLatestForTarget(f.swarm.id, otherFingerprint), undefined); now = new Date("2026-09-13T05:00:02.000Z"); + assert.equal(continuation.getRequest(created.request.id)?.status, "EXPIRED"); assert.throws( () => continuation.approveRequest(f.ownerFingerprint, { swarmId: f.swarm.id, From 61c71c3bf3c41fdbb6afa62eccad449777ca3617 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:06:19 +0800 Subject: [PATCH 19/55] feat(chat-swarm): host continuation inside existing lifecycle --- src/chat-swarm-lifecycle.ts | 128 +++++++++++++++++++++++++++++++----- 1 file changed, 112 insertions(+), 16 deletions(-) diff --git a/src/chat-swarm-lifecycle.ts b/src/chat-swarm-lifecycle.ts index c59df7335..06f21a65d 100644 --- a/src/chat-swarm-lifecycle.ts +++ b/src/chat-swarm-lifecycle.ts @@ -1,7 +1,14 @@ import { ChatSwarmError } from "./chat-swarm-contract.js"; import { ChatSwarmCoordinator } from "./chat-swarm-coordinator.js"; import { ChatSwarmStore } from "./chat-swarm-store.js"; -import { ChatSwarmCarrierManager, type ChatSwarmCarrierAdapter, type CarrierResult, type CarrierStatusResult } from "./chat-swarm-carrier.js"; +import { + ChatSwarmCarrierManager, + type ChatSwarmCarrierAdapter, + type CarrierResult, + type CarrierStatusResult, +} from "./chat-swarm-carrier.js"; +import { ChatSwarmContinuationCoordinator } from "./chat-swarm-continuation-coordinator.js"; +import { ChatSwarmContinuationStore } from "./chat-swarm-continuation-store.js"; export type ChatSwarmLifecycleMode = "normal" | "drain" | "reconcile-only"; export type ChatSwarmLifecycleAction = @@ -19,7 +26,11 @@ export type ChatSwarmLifecycleAction = | "inspect" | "tasks" | "join_request" - | "approve_join"; + | "approve_join" + | "continuation_request" + | "continuation_status" + | "continuation_approve" + | "continuation_reconcile"; export interface ChatSwarmLifecycleOptions { stateDir: string; @@ -33,14 +44,16 @@ export interface ChatSwarmAdmissionContext { } /** - * Owns one process-level Swarm store/coordinator pair. Construction opens the - * durable store but deliberately does not perform restart recovery; the server - * must call recoverAfterRestart once it has established its startup authority. + * Owns one process-level Swarm state domain. Construction opens the durable + * stores but deliberately does not perform restart recovery; the server must + * call recoverAfterStartup once it has established its startup authority. */ export class ChatSwarmLifecycle { readonly enabled: boolean; readonly store?: ChatSwarmStore; readonly coordinator?: ChatSwarmCoordinator; + readonly continuationStore?: ChatSwarmContinuationStore; + readonly continuationCoordinator?: ChatSwarmContinuationCoordinator; private readonly modeProvider: () => ChatSwarmLifecycleMode; private closed = false; private readonly carrierManager?: ChatSwarmCarrierManager; @@ -50,29 +63,91 @@ export class ChatSwarmLifecycle { this.modeProvider = options.mode ?? (() => "normal"); if (this.enabled) { const store = new ChatSwarmStore(options.stateDir); + const coordinator = new ChatSwarmCoordinator(store); + const continuationStore = new ChatSwarmContinuationStore(options.stateDir); this.store = store; - this.coordinator = new ChatSwarmCoordinator(store); - if (options.carrierAdapter) this.carrierManager = new ChatSwarmCarrierManager(store, this.coordinator, options.carrierAdapter); + this.coordinator = coordinator; + this.continuationStore = continuationStore; + this.continuationCoordinator = new ChatSwarmContinuationCoordinator( + continuationStore, + coordinator, + ); + if (options.carrierAdapter) { + this.carrierManager = new ChatSwarmCarrierManager( + store, + coordinator, + options.carrierAdapter, + ); + } } } recoverAfterStartup(): number { this.requireEnabled(); this.store!.fenceCarrierOperations(); - return this.store!.recoverAfterRestart(); + const taskRecoveryCount = this.store!.recoverAfterRestart(); + this.continuationCoordinator!.recoverAfterRestart(); + return taskRecoveryCount; } - async ensureCarriers(meta: unknown, input: { swarmId: string; capacity: number; adapterConfigHash: string }): Promise { this.requireCarrierEffects(); return this.carrierManager!.ensure(meta, input.swarmId, input.capacity, input.adapterConfigHash); } - carrierStatus(meta: unknown, swarmId: string): CarrierStatusResult { this.requireEnabled(); if (!this.carrierManager) throw new ChatSwarmError("INVALID_STATE", "carrier adapter is unavailable"); return this.carrierManager.status(meta, swarmId); } - async carrierWake(meta: unknown, input: { swarmId: string; workerId: string; expectedEpoch: number; taskId?: string; adapterConfigHash: string }): Promise { this.requireCarrierEffects(); return this.carrierManager!.wake(meta, input); } + async ensureCarriers( + meta: unknown, + input: { swarmId: string; capacity: number; adapterConfigHash: string }, + ): Promise { + this.requireCarrierEffects(); + return this.carrierManager!.ensure( + meta, + input.swarmId, + input.capacity, + input.adapterConfigHash, + ); + } + + carrierStatus(meta: unknown, swarmId: string): CarrierStatusResult { + this.requireEnabled(); + if (!this.carrierManager) { + throw new ChatSwarmError("INVALID_STATE", "carrier adapter is unavailable"); + } + return this.carrierManager.status(meta, swarmId); + } + + async carrierWake( + meta: unknown, + input: { + swarmId: string; + workerId: string; + expectedEpoch: number; + taskId?: string; + adapterConfigHash: string; + }, + ): Promise { + this.requireCarrierEffects(); + return this.carrierManager!.wake(meta, input); + } - admit(action: ChatSwarmLifecycleAction, context: ChatSwarmAdmissionContext = {}): void { + admit( + action: ChatSwarmLifecycleAction, + context: ChatSwarmAdmissionContext = {}, + ): void { this.requireEnabled(); const mode = this.modeProvider(); if (mode === "normal") return; if (action === "next" && context.existingTask === true) return; - if (["status", "collect", "cancel", "reconcile", "submit", "peer_status", "inspect", "tasks"].includes(action)) return; + if ( + [ + "status", + "collect", + "cancel", + "reconcile", + "submit", + "peer_status", + "inspect", + "tasks", + "continuation_status", + "continuation_reconcile", + ].includes(action) + ) return; throw new ChatSwarmError( "INVALID_STATE", @@ -83,14 +158,35 @@ export class ChatSwarmLifecycle { close(): void { if (this.closed) return; this.closed = true; + this.continuationStore?.close(); this.store?.close(); } private requireEnabled(): void { - if (!this.enabled || !this.store || !this.coordinator) { + if ( + !this.enabled || + !this.store || + !this.coordinator || + !this.continuationStore || + !this.continuationCoordinator + ) { throw new ChatSwarmError("INVALID_STATE", "chat swarm feature is disabled"); } - if (this.closed) throw new ChatSwarmError("INVALID_STATE", "chat swarm lifecycle is closed"); + if (this.closed) { + throw new ChatSwarmError("INVALID_STATE", "chat swarm lifecycle is closed"); + } + } + + private requireCarrierEffects(): void { + this.requireEnabled(); + if (this.modeProvider() !== "normal") { + throw new ChatSwarmError( + "INVALID_STATE", + "carrier effects are unavailable while lifecycle is not normal", + ); + } + if (!this.carrierManager) { + throw new ChatSwarmError("INVALID_STATE", "carrier adapter is unavailable"); + } } - private requireCarrierEffects(): void { this.requireEnabled(); if (this.modeProvider() !== "normal") throw new ChatSwarmError("INVALID_STATE", "carrier effects are unavailable while lifecycle is not normal"); if (!this.carrierManager) throw new ChatSwarmError("INVALID_STATE", "carrier adapter is unavailable"); } } From 1c5d6a53df6249e457d59f82e1d2359e6e98c503 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:14:27 +0800 Subject: [PATCH 20/55] test(chat-swarm): verify lifecycle continuation recovery and drain admission --- src/chat-swarm-lifecycle.test.ts | 157 +++++++++++++++++++++++++++++-- 1 file changed, 148 insertions(+), 9 deletions(-) diff --git a/src/chat-swarm-lifecycle.test.ts b/src/chat-swarm-lifecycle.test.ts index de43fba06..a930ac316 100644 --- a/src/chat-swarm-lifecycle.test.ts +++ b/src/chat-swarm-lifecycle.test.ts @@ -42,16 +42,79 @@ test("shares one coordinator and keeps startup recovery explicit", () => { } }); +test("startup recovery fences pending continuation without changing worker epoch", () => { + const f = fixture(); + try { + const owner = { "openai/session": "continuation-owner" }; + const source = { "openai/conversation_id": "continuation-source" }; + const target = { "openai/conversation_id": "continuation-target" }; + const swarm = f.lifecycle.coordinator!.createSwarm(owner, { workerLimit: 2 }); + const worker = f.lifecycle.coordinator!.joinWorker(source, swarm.id, { + label: "peer", + runtimeKind: "mcp_peer", + }); + f.lifecycle.coordinator!.checkpoint( + source, + worker.id, + 0, + new Date(Date.now() + 60 * 60 * 1000).toISOString(), + { summary: "safe checkpoint" }, + ); + const request = f.lifecycle.continuationCoordinator!.request(target, { + swarmId: swarm.id, + workerId: worker.id, + attemptKey: "restart-fence", + sourceEpoch: 0, + }).request; + assert.equal(request.status, "PENDING"); + + const second = new ChatSwarmLifecycle({ stateDir: f.root }); + try { + assert.equal(second.recoverAfterStartup(), 0); + const recovered = f.lifecycle.continuationStore!.getRequest(request.id)!; + assert.equal(recovered.status, "RECONCILE_REQUIRED"); + assert.equal(recovered.version, 2); + assert.equal(f.lifecycle.store!.getWorker(worker.id)!.continuationEpoch, 0); + assert.equal( + f.lifecycle.store!.getWorker(worker.id)!.carrierConversationFingerprint, + worker.carrierConversationFingerprint, + ); + } finally { + second.close(); + } + } finally { + cleanup(f); + } +}); + test("drain permits current-task next and uncertainty-safe completion, but rejects new claims", () => { const f = fixture(); try { f.setMode("drain"); - for (const action of ["create", "join", "dispatch", "close"] as const) { - assert.throws(() => f.lifecycle.admit(action), (error: unknown) => error instanceof ChatSwarmError && error.code === "INVALID_STATE"); + for (const action of [ + "create", + "join", + "dispatch", + "close", + "continuation_request", + "continuation_approve", + ] as const) { + assert.throws( + () => f.lifecycle.admit(action), + (error: unknown) => error instanceof ChatSwarmError && error.code === "INVALID_STATE", + ); } assert.doesNotThrow(() => f.lifecycle.admit("next", { existingTask: true })); assert.throws(() => f.lifecycle.admit("next", { existingTask: false }), ChatSwarmError); - for (const action of ["submit", "status", "collect", "cancel", "reconcile"] as const) { + for (const action of [ + "submit", + "status", + "collect", + "cancel", + "reconcile", + "continuation_status", + "continuation_reconcile", + ] as const) { assert.doesNotThrow(() => f.lifecycle.admit(action)); } } finally { @@ -63,10 +126,26 @@ test("reconcile-only permits inspection and explicit recovery actions only", () const f = fixture(); try { f.setMode("reconcile-only"); - for (const action of ["status", "collect", "cancel", "reconcile", "submit"] as const) { + for (const action of [ + "status", + "collect", + "cancel", + "reconcile", + "submit", + "continuation_status", + "continuation_reconcile", + ] as const) { assert.doesNotThrow(() => f.lifecycle.admit(action)); } - for (const action of ["create", "join", "dispatch", "next", "close"] as const) { + for (const action of [ + "create", + "join", + "dispatch", + "next", + "close", + "continuation_request", + "continuation_approve", + ] as const) { assert.throws(() => f.lifecycle.admit(action), ChatSwarmError); } } finally { @@ -80,6 +159,8 @@ test("disabled lifecycle has no store and closes safely", () => { try { assert.equal(lifecycle.store, undefined); assert.equal(lifecycle.coordinator, undefined); + assert.equal(lifecycle.continuationStore, undefined); + assert.equal(lifecycle.continuationCoordinator, undefined); assert.throws(() => lifecycle.recoverAfterStartup(), ChatSwarmError); assert.throws(() => lifecycle.admit("status"), ChatSwarmError); } finally { @@ -89,8 +170,66 @@ test("disabled lifecycle has no store and closes safely", () => { }); test("carrier effects require injected adapter, owner, normal mode, and explicit startup recovery", async () => { - const root = mkdtempSync(join(tmpdir(), "devspace-chat-swarm-carrier-lifecycle-")); let mode: ChatSwarmLifecycleMode = "normal"; - const adapter: ChatSwarmCarrierAdapter = { kind: "fake", capabilities: () => ({ boundedWait: "UNSUPPORTED", eventWake: "UNSUPPORTED", resultReadback: "UNSUPPORTED", durableReplay: "SUPPORTED" }), ensureExisting: async (input) => ({ disposition: "READY", operationId: input.operationId, swarmId: input.swarmId, workerId: input.workerId, expectedEpoch: input.expectedEpoch, carrierKind: input.carrierKind, carrierFingerprint: input.carrierFingerprint, remoteMayContinue: false }), wake: async (input) => ({ disposition: "UNSUPPORTED", operationId: input.operationId, swarmId: input.swarmId, workerId: input.workerId, expectedEpoch: input.expectedEpoch, carrierKind: input.carrierKind, carrierFingerprint: input.carrierFingerprint, remoteMayContinue: false }) }; - const lifecycle = new ChatSwarmLifecycle({ stateDir: root, mode: () => mode, carrierAdapter: adapter }); const owner = { "openai/session": "owner" }; - try { const swarm = lifecycle.coordinator!.createSwarm(owner, { workerLimit: 1 }); lifecycle.store!.createWorker({ swarmId: swarm.id, label: "peer", runtimeKind: "mcp_peer", carrierConversationFingerprint: "a".repeat(64) }); const status = lifecycle.carrierStatus(owner, swarm.id); assert.equal(status.workers.length, 1); mode = "drain"; await assert.rejects(lifecycle.ensureCarriers(owner, { swarmId: swarm.id, capacity: 1, adapterConfigHash: "b".repeat(64) }), /carrier effects are unavailable/); mode = "normal"; assert.equal(lifecycle.recoverAfterStartup(), 0); } finally { lifecycle.close(); rmSync(root, { recursive: true, force: true }); } + const root = mkdtempSync(join(tmpdir(), "devspace-chat-swarm-carrier-lifecycle-")); + let mode: ChatSwarmLifecycleMode = "normal"; + const adapter: ChatSwarmCarrierAdapter = { + kind: "fake", + capabilities: () => ({ + boundedWait: "UNSUPPORTED", + eventWake: "UNSUPPORTED", + resultReadback: "UNSUPPORTED", + durableReplay: "SUPPORTED", + }), + ensureExisting: async (input) => ({ + disposition: "READY", + operationId: input.operationId, + swarmId: input.swarmId, + workerId: input.workerId, + expectedEpoch: input.expectedEpoch, + carrierKind: input.carrierKind, + carrierFingerprint: input.carrierFingerprint, + remoteMayContinue: false, + }), + wake: async (input) => ({ + disposition: "UNSUPPORTED", + operationId: input.operationId, + swarmId: input.swarmId, + workerId: input.workerId, + expectedEpoch: input.expectedEpoch, + carrierKind: input.carrierKind, + carrierFingerprint: input.carrierFingerprint, + remoteMayContinue: false, + }), + }; + const lifecycle = new ChatSwarmLifecycle({ + stateDir: root, + mode: () => mode, + carrierAdapter: adapter, + }); + const owner = { "openai/session": "owner" }; + try { + const swarm = lifecycle.coordinator!.createSwarm(owner, { workerLimit: 1 }); + lifecycle.store!.createWorker({ + swarmId: swarm.id, + label: "peer", + runtimeKind: "mcp_peer", + carrierConversationFingerprint: "a".repeat(64), + }); + const status = lifecycle.carrierStatus(owner, swarm.id); + assert.equal(status.workers.length, 1); + mode = "drain"; + await assert.rejects( + lifecycle.ensureCarriers(owner, { + swarmId: swarm.id, + capacity: 1, + adapterConfigHash: "b".repeat(64), + }), + /carrier effects are unavailable/, + ); + mode = "normal"; + assert.equal(lifecycle.recoverAfterStartup(), 0); + } finally { + lifecycle.close(); + rmSync(root, { recursive: true, force: true }); + } }); From 2517825ef7f78795dc6a1d9a3a3c7e308e795c60 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:17:35 +0800 Subject: [PATCH 21/55] feat(chat-swarm): represent superseded continuation targets --- src/chat-swarm-continuation-contract.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/chat-swarm-continuation-contract.ts b/src/chat-swarm-continuation-contract.ts index e48d4087e..55d506bfa 100644 --- a/src/chat-swarm-continuation-contract.ts +++ b/src/chat-swarm-continuation-contract.ts @@ -2,6 +2,7 @@ export const CHAT_SWARM_CONTINUATION_STATES = [ "PENDING", "APPROVED", "EXPIRED", + "SUPERSEDED", "RECONCILE_REQUIRED", ] as const; From 75f2a9acaac675418dc6d60e13e9190d2cb86c2a Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:18:34 +0800 Subject: [PATCH 22/55] fix(chat-swarm): supersede losing continuation targets atomically --- src/chat-swarm-continuation-store.ts | 40 +++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/chat-swarm-continuation-store.ts b/src/chat-swarm-continuation-store.ts index c13395409..973b9dbbd 100644 --- a/src/chat-swarm-continuation-store.ts +++ b/src/chat-swarm-continuation-store.ts @@ -69,7 +69,11 @@ interface PersistedReceipt { type ApprovalOutcome = | { ok: true; request: ChatSwarmContinuationRequest } - | { ok: false; code: "EXPIRED" | "RECONCILIATION_REQUIRED"; message: string }; + | { + ok: false; + code: "EXPIRED" | "RECONCILIATION_REQUIRED" | "OWNERSHIP_CONFLICT"; + message: string; + }; export class ChatSwarmContinuationStore { private readonly database: DatabaseHandle; @@ -200,6 +204,13 @@ export class ChatSwarmContinuationStore { this.persistExpired(input.requestId, this.nowIso()); return { ok: false, code: "EXPIRED", message: "continuation request has expired" }; } + if (request.status === "SUPERSEDED") { + return { + ok: false, + code: "OWNERSHIP_CONFLICT", + message: "another continuation target already advanced this worker epoch", + }; + } if (request.status === "RECONCILE_REQUIRED") { return { ok: false, @@ -283,6 +294,7 @@ export class ChatSwarmContinuationStore { throw new ChatSwarmError("CAS_DRIFT", "swarm revision changed during continuation transfer"); } + this.supersedeCompetingRequests(request, now); return { ok: true, request: this.getRequest(request.id)! }; }); @@ -446,6 +458,31 @@ export class ChatSwarmContinuationStore { } } + private supersedeCompetingRequests( + winner: ChatSwarmContinuationRequest, + now: string, + ): void { + for (const row of this.listPendingDurableForWorker(winner.workerId)) { + if (row.operation_id === winner.id) continue; + const other = this.toRequest(row); + if ( + other.sourceEpoch !== winner.sourceEpoch || + other.sourceCarrierFingerprint !== winner.sourceCarrierFingerprint + ) continue; + if (Date.parse(other.expiresAt) <= Date.parse(now)) { + this.persistExpired(other.id, now); + continue; + } + const nextRequest = persistedFromRequest(other, other.version + 1); + this.database.sqlite.prepare(` + update durable_operations + set status='failed',retry_safe='false',request_json=?,error_code='SUPERSEDED', + error_message='Another continuation target atomically advanced this worker epoch.',updated_at=? + where operation_id=? and kind=? and status='started' + `).run(JSON.stringify(nextRequest), now, other.id, KIND); + } + } + private requireDurable(requestId: string): DurableRow { const row = this.database.sqlite.prepare( "select * from durable_operations where operation_id=? and kind=? limit 1", @@ -492,6 +529,7 @@ export class ChatSwarmContinuationStore { status = Date.parse(persisted.expiresAt) <= this.clock().getTime() ? "EXPIRED" : "PENDING"; } else if (row.status === "succeeded") status = "APPROVED"; else if (row.status === "failed" && row.error_code === "EXPIRED") status = "EXPIRED"; + else if (row.status === "failed" && row.error_code === "SUPERSEDED") status = "SUPERSEDED"; else if (row.status === "outcome_unknown") status = "RECONCILE_REQUIRED"; else throw new ChatSwarmError("INVALID_STATE", `unsupported durable continuation status '${row.status}'`); From 91a9863c5ccfc1086051289135280002a470fe9d Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:19:09 +0800 Subject: [PATCH 23/55] test(chat-swarm): prove losing continuation targets are terminally superseded --- src/chat-swarm-continuation-store.test.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/chat-swarm-continuation-store.test.ts b/src/chat-swarm-continuation-store.test.ts index a0afc5e8f..d522100f8 100644 --- a/src/chat-swarm-continuation-store.test.ts +++ b/src/chat-swarm-continuation-store.test.ts @@ -96,7 +96,7 @@ test("durable continuation request replays exactly and changed target conflicts } }); -test("parallel prepared targets are bounded by commit-time epoch CAS", () => { +test("parallel prepared targets terminate losers as SUPERSEDED at winner commit", () => { const f = fixture(); const continuation = new ChatSwarmContinuationStore(f.root); try { @@ -116,15 +116,21 @@ test("parallel prepared targets are bounded by commit-time epoch CAS", () => { assert.equal(winner.status, "APPROVED"); assert.equal(f.swarmStore.getWorker(f.worker.id)!.carrierConversationFingerprint, targetA); + const loser = continuation.getRequest(requestB.id)!; + assert.equal(loser.status, "SUPERSEDED"); + assert.equal(loser.version, 2); assert.throws( () => continuation.approveRequest(f.ownerFingerprint, { swarmId: f.swarm.id, requestId: requestB.id, - expectedRequestVersion: 1, + expectedRequestVersion: 2, expectedSwarmVersion: 2, }), - (error: unknown) => error instanceof ChatSwarmError && error.code === "CAS_DRIFT", + (error: unknown) => error instanceof ChatSwarmError && error.code === "OWNERSHIP_CONFLICT", ); + + assert.equal(continuation.recoverAfterRestart(), 0); + assert.equal(continuation.getRequest(requestB.id)?.status, "SUPERSEDED"); assert.equal(f.swarmStore.getWorker(f.worker.id)!.continuationEpoch, 1); assert.equal(f.swarmStore.getWorker(f.worker.id)!.carrierConversationFingerprint, targetA); } finally { From 82e8db52f1a76de778341552756a40d4d600136c Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:21:57 +0800 Subject: [PATCH 24/55] fix(chat-swarm): normalize generic restart fencing exactly once --- src/chat-swarm-continuation-store.ts | 32 +++++++++++++++++++--------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/chat-swarm-continuation-store.ts b/src/chat-swarm-continuation-store.ts index 973b9dbbd..1c747ccc3 100644 --- a/src/chat-swarm-continuation-store.ts +++ b/src/chat-swarm-continuation-store.ts @@ -25,6 +25,8 @@ const SHA256 = /^[0-9a-f]{64}$/; const DEFAULT_TTL_SECONDS = 15 * 60; const MAX_TTL_SECONDS = 60 * 60; const MAX_PENDING_PER_WORKER = 10; +const CONTINUATION_RESTART_MESSAGE = + "DevSpace restarted while continuation was pending; reconcile exact binding before approval."; type Row = Record; @@ -331,22 +333,32 @@ export class ChatSwarmContinuationStore { recoverAfterRestart(): number { const now = this.nowIso(); const tx = this.database.sqlite.transaction(() => { - const rows = this.database.sqlite.prepare( - "select * from durable_operations where kind=? and scope_root=? and status='started'", - ).all(KIND, this.scopeRoot) as DurableRow[]; + const rows = this.database.sqlite.prepare(` + select * from durable_operations + where kind=? and scope_root=? and status in ('started','outcome_unknown') + `).all(KIND, this.scopeRoot) as DurableRow[]; + let changed = 0; for (const row of rows) { + if (row.status === "outcome_unknown" && row.error_message === CONTINUATION_RESTART_MESSAGE) { + continue; + } const request = this.toRequest(row); const nextRequest = persistedFromRequest(request, request.version + 1); - this.database.sqlite.prepare(` + const result = this.database.sqlite.prepare(` update durable_operations set status='outcome_unknown',retry_safe='false',request_json=?, - error_code='RECONCILIATION_REQUIRED', - error_message='DevSpace restarted while continuation was pending; reconcile exact binding before approval.', - updated_at=? - where operation_id=? and kind=? and status='started' - `).run(JSON.stringify(nextRequest), now, row.operation_id, KIND); + error_code='RECONCILIATION_REQUIRED',error_message=?,updated_at=? + where operation_id=? and kind=? and status in ('started','outcome_unknown') + `).run( + JSON.stringify(nextRequest), + CONTINUATION_RESTART_MESSAGE, + now, + row.operation_id, + KIND, + ); + changed += Number(result.changes); } - return rows.length; + return changed; }); return tx.immediate(); } From b86c6676c46c29b22a5625bf6f46fbe4ae0490ee Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:22:33 +0800 Subject: [PATCH 25/55] test(chat-swarm): mirror formal server restart ordering --- src/chat-swarm-lifecycle.test.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/chat-swarm-lifecycle.test.ts b/src/chat-swarm-lifecycle.test.ts index a930ac316..f2d28a0bb 100644 --- a/src/chat-swarm-lifecycle.test.ts +++ b/src/chat-swarm-lifecycle.test.ts @@ -6,6 +6,7 @@ import test from "node:test"; import { ChatSwarmError } from "./chat-swarm-contract.js"; import { ChatSwarmLifecycle, type ChatSwarmLifecycleMode } from "./chat-swarm-lifecycle.js"; import type { ChatSwarmCarrierAdapter } from "./chat-swarm-carrier.js"; +import { DurableOperationStore } from "./durable-operations.js"; function fixture() { const root = mkdtempSync(join(tmpdir(), "devspace-chat-swarm-lifecycle-")); @@ -42,7 +43,7 @@ test("shares one coordinator and keeps startup recovery explicit", () => { } }); -test("startup recovery fences pending continuation without changing worker epoch", () => { +test("startup recovery normalizes continuation after generic durable-operation fencing", () => { const f = fixture(); try { const owner = { "openai/session": "continuation-owner" }; @@ -67,6 +68,17 @@ test("startup recovery fences pending continuation without changing worker epoch sourceEpoch: 0, }).request; assert.equal(request.status, "PENDING"); + assert.equal(request.version, 1); + + const genericDurableStore = new DurableOperationStore(f.root); + try { + assert.equal(genericDurableStore.markInterruptedUnknown(), 1); + } finally { + genericDurableStore.close(); + } + const genericFenced = f.lifecycle.continuationStore!.getRequest(request.id)!; + assert.equal(genericFenced.status, "RECONCILE_REQUIRED"); + assert.equal(genericFenced.version, 1); const second = new ChatSwarmLifecycle({ stateDir: f.root }); try { @@ -79,6 +91,8 @@ test("startup recovery fences pending continuation without changing worker epoch f.lifecycle.store!.getWorker(worker.id)!.carrierConversationFingerprint, worker.carrierConversationFingerprint, ); + assert.equal(second.continuationCoordinator!.recoverAfterRestart(), 0); + assert.equal(f.lifecycle.continuationStore!.getRequest(request.id)!.version, 2); } finally { second.close(); } From 3e70b264e3bc30e109ed5a7f4d3da52f47b00d2e Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:24:35 +0800 Subject: [PATCH 26/55] fix(chat-swarm): exact-bind continuation reconcile swarm --- src/chat-swarm-continuation-coordinator.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/chat-swarm-continuation-coordinator.ts b/src/chat-swarm-continuation-coordinator.ts index c401975e3..f1cad52cc 100644 --- a/src/chat-swarm-continuation-coordinator.ts +++ b/src/chat-swarm-continuation-coordinator.ts @@ -1,3 +1,4 @@ +import { ChatSwarmError } from "./chat-swarm-contract.js"; import type { ApproveContinuationRequestInput, ChatSwarmContinuationRequest, @@ -38,6 +39,13 @@ export class ChatSwarmContinuationCoordinator { requestId: string, ): ChatSwarmContinuationRequest { this.swarmCoordinator.assertOwnerForLifecycle(meta, swarmId); + const request = this.store.getRequest(requestId); + if (!request || request.swarmId !== swarmId) { + throw new ChatSwarmError( + "OWNERSHIP_CONFLICT", + "continuation request does not belong to asserted swarm", + ); + } const identity = resolveChatSwarmIdentity(meta); return this.store.reconcileUnknownNoEffect(identity.fingerprint, requestId); } From 1e1010bb07a4352e881a1b5ca13fc749e306c137 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:24:47 +0800 Subject: [PATCH 27/55] fix(chat-swarm): keep expiry terminal across restart reconciliation --- src/chat-swarm-continuation-store.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/chat-swarm-continuation-store.ts b/src/chat-swarm-continuation-store.ts index 1c747ccc3..5d5c3f2b9 100644 --- a/src/chat-swarm-continuation-store.ts +++ b/src/chat-swarm-continuation-store.ts @@ -339,6 +339,12 @@ export class ChatSwarmContinuationStore { `).all(KIND, this.scopeRoot) as DurableRow[]; let changed = 0; for (const row of rows) { + const persisted = readPersistedRequest(row); + if (Date.parse(persisted.expiresAt) <= Date.parse(now)) { + this.persistExpired(row.operation_id, now); + changed += 1; + continue; + } if (row.status === "outcome_unknown" && row.error_message === CONTINUATION_RESTART_MESSAGE) { continue; } @@ -378,14 +384,14 @@ export class ChatSwarmContinuationStore { if (String(swarm.owner_identity_fingerprint) !== ownerIdentityFingerprint) { throw new ChatSwarmError("OWNERSHIP_CONFLICT", "controller identity does not own swarm"); } - const worker = this.workerSnapshot(request.workerId); - const pendingView: ChatSwarmContinuationRequest = { ...request, status: "PENDING" }; - assertContinuationCommitAllowed(worker, pendingView); const now = this.nowIso(); if (Date.parse(request.expiresAt) <= Date.parse(now)) { this.persistExpired(request.id, now); return { ok: false, code: "EXPIRED", message: "continuation request expired during reconciliation" }; } + const worker = this.workerSnapshot(request.workerId); + const pendingView: ChatSwarmContinuationRequest = { ...request, status: "PENDING" }; + assertContinuationCommitAllowed(worker, pendingView); const nextRequest = persistedFromRequest(request, request.version + 1); const result = this.database.sqlite.prepare(` update durable_operations From 70bed16b795c232166e3a8bbf26e9ea449c2fe0b Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:24:52 +0800 Subject: [PATCH 28/55] test(chat-swarm): reject cross-swarm continuation reconcile --- ...hat-swarm-continuation-coordinator.test.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/chat-swarm-continuation-coordinator.test.ts b/src/chat-swarm-continuation-coordinator.test.ts index fe4a70ffd..6ddcf4795 100644 --- a/src/chat-swarm-continuation-coordinator.test.ts +++ b/src/chat-swarm-continuation-coordinator.test.ts @@ -121,3 +121,27 @@ test("only exact owner may approve, including idempotent approved readback", () cleanup(f); } }); + +test("same owner cannot reconcile a continuation through a different swarm id", () => { + const f = fixture(); + try { + const created = f.continuation.request(f.targetMeta, { + swarmId: f.swarm.id, + workerId: f.worker.id, + attemptKey: "cross-swarm-reconcile", + sourceEpoch: 0, + }); + const otherSwarm = f.continuation.swarmCoordinator.createSwarm(f.ownerMeta, { + workerLimit: 1, + metadata: { test: "other-swarm" }, + }); + + assert.throws( + () => f.continuation.reconcileNoEffect(f.ownerMeta, otherSwarm.id, created.request.id), + (error: unknown) => error instanceof ChatSwarmError && error.code === "OWNERSHIP_CONFLICT", + ); + assert.equal(f.continuationStore.getRequest(created.request.id)?.status, "PENDING"); + } finally { + cleanup(f); + } +}); From 2bd93c2f644d7118f2423706e15da3cdcbb3572d Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:26:31 +0800 Subject: [PATCH 29/55] fix(chat-swarm): expose bound continuation ttl --- src/chat-swarm-continuation-contract.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/chat-swarm-continuation-contract.ts b/src/chat-swarm-continuation-contract.ts index 55d506bfa..fd46d172c 100644 --- a/src/chat-swarm-continuation-contract.ts +++ b/src/chat-swarm-continuation-contract.ts @@ -19,6 +19,7 @@ export interface ChatSwarmContinuationRequest { sourceCarrierFingerprint: string; targetCarrierFingerprint: string; checkpointHash: string; + ttlSeconds: number; version: number; status: ChatSwarmContinuationState; requestedAt: string; From 8c54cf2b786c9dff8af1579e359955354c957032 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:29:27 +0800 Subject: [PATCH 30/55] fix(chat-swarm): bind continuation replay to ttl and attempt identity --- src/chat-swarm-continuation-store.ts | 37 +++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/chat-swarm-continuation-store.ts b/src/chat-swarm-continuation-store.ts index 5d5c3f2b9..588f92cf9 100644 --- a/src/chat-swarm-continuation-store.ts +++ b/src/chat-swarm-continuation-store.ts @@ -57,6 +57,7 @@ interface PersistedRequest { sourceCarrierFingerprint: string; targetCarrierFingerprint: string; checkpointHash: string; + ttlSeconds: number; requestedAt: string; expiresAt: string; } @@ -115,11 +116,22 @@ export class ChatSwarmContinuationStore { authenticatedTargetCarrierFingerprint, ); this.assertTargetCarrierAvailable(authenticatedTargetCarrierFingerprint, input.workerId); + const requestHash = createHash("sha256").update(JSON.stringify({ + attemptKey: input.attemptKey, + checkpointHash: material.checkpointHash, + sourceCarrierFingerprint: material.sourceCarrierFingerprint, + sourceEpoch: material.sourceEpoch, + swarmId: input.swarmId, + targetCarrierFingerprint: material.targetCarrierFingerprint, + targetEpoch: material.targetEpoch, + ttlSeconds, + workerId: input.workerId, + })).digest("hex"); const durableAttemptKey = stableAttemptKey(input.swarmId, input.workerId, input.attemptKey); const existing = this.getDurableByAttempt(durableAttemptKey); if (existing) { - if (existing.kind !== KIND || existing.request_hash !== material.requestHash) { + if (existing.kind !== KIND || existing.request_hash !== requestHash) { throw new ChatSwarmError("REPLAY_CONFLICT", "continuation attemptKey is bound to different material"); } return { request: this.toRequest(existing), created: false }; @@ -145,6 +157,7 @@ export class ChatSwarmContinuationStore { sourceCarrierFingerprint: material.sourceCarrierFingerprint, targetCarrierFingerprint: material.targetCarrierFingerprint, checkpointHash: material.checkpointHash, + ttlSeconds, requestedAt, expiresAt, }; @@ -158,7 +171,7 @@ export class ChatSwarmContinuationStore { `).run( operationId, durableAttemptKey, - material.requestHash, + requestHash, KIND, "OWNER_DIRECT", this.scopeRoot, @@ -537,6 +550,12 @@ export class ChatSwarmContinuationStore { throw new ChatSwarmError("INVALID_STATE", "durable continuation operation authority is malformed"); } const persisted = readPersistedRequest(row); + if (row.scope_root !== this.scopeRoot) { + throw new ChatSwarmError("INVALID_STATE", "durable continuation scope root mismatch"); + } + if (stableAttemptKey(persisted.swarmId, persisted.workerId, persisted.attemptKey) !== row.attempt_key) { + throw new ChatSwarmError("INVALID_STATE", "persisted continuation attempt identity mismatch"); + } const requestHash = continuationMaterialHash(persisted); if (requestHash !== row.request_hash) { throw new ChatSwarmError("INVALID_STATE", "persisted continuation request hash mismatch"); @@ -579,6 +598,7 @@ export class ChatSwarmContinuationStore { sourceCarrierFingerprint: persisted.sourceCarrierFingerprint, targetCarrierFingerprint: persisted.targetCarrierFingerprint, checkpointHash: persisted.checkpointHash, + ttlSeconds: persisted.ttlSeconds, version: persisted.version, status, requestedAt: persisted.requestedAt, @@ -600,12 +620,14 @@ function stableAttemptKey(swarmId: string, workerId: string, attemptKey: string) function continuationMaterialHash(request: PersistedRequest): string { return createHash("sha256").update(JSON.stringify({ + attemptKey: request.attemptKey, checkpointHash: request.checkpointHash, sourceCarrierFingerprint: request.sourceCarrierFingerprint, sourceEpoch: request.sourceEpoch, swarmId: request.swarmId, targetCarrierFingerprint: request.targetCarrierFingerprint, targetEpoch: request.targetEpoch, + ttlSeconds: request.ttlSeconds, workerId: request.workerId, })).digest("hex"); } @@ -625,6 +647,7 @@ function persistedFromRequest( sourceCarrierFingerprint: request.sourceCarrierFingerprint, targetCarrierFingerprint: request.targetCarrierFingerprint, checkpointHash: request.checkpointHash, + ttlSeconds: request.ttlSeconds, requestedAt: request.requestedAt, expiresAt: request.expiresAt, }; @@ -648,6 +671,9 @@ function validatePersistedRequest(request: PersistedRequest): void { if (!Number.isSafeInteger(request.version) || request.version < 1) { throw new ChatSwarmError("INVALID_STATE", "persisted continuation request version is malformed"); } + if (!Number.isInteger(request.ttlSeconds) || request.ttlSeconds < 1 || request.ttlSeconds > MAX_TTL_SECONDS) { + throw new ChatSwarmError("INVALID_STATE", "persisted continuation ttl is malformed"); + } for (const [label, value] of [ ["source carrier fingerprint", request.sourceCarrierFingerprint], ["target carrier fingerprint", request.targetCarrierFingerprint], @@ -663,9 +689,14 @@ function validatePersistedRequest(request: PersistedRequest): void { if (!request.swarmId || !request.workerId || !request.attemptKey) { throw new ChatSwarmError("INVALID_STATE", "persisted continuation identity is incomplete"); } - if (!Number.isFinite(Date.parse(request.requestedAt)) || !Number.isFinite(Date.parse(request.expiresAt))) { + const requestedMs = Date.parse(request.requestedAt); + const expiresMs = Date.parse(request.expiresAt); + if (!Number.isFinite(requestedMs) || !Number.isFinite(expiresMs)) { throw new ChatSwarmError("INVALID_STATE", "persisted continuation timestamps are malformed"); } + if (expiresMs - requestedMs !== request.ttlSeconds * 1000) { + throw new ChatSwarmError("INVALID_STATE", "persisted continuation expiry does not match ttl"); + } } function assertFingerprint(value: string, label: string): void { From 7e1bcecd869fdc75bc2bb4096582b0659a8a622b Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:30:23 +0800 Subject: [PATCH 31/55] test(chat-swarm): reject ttl drift and persisted attempt tamper --- src/chat-swarm-continuation-store.test.ts | 40 ++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/chat-swarm-continuation-store.test.ts b/src/chat-swarm-continuation-store.test.ts index d522100f8..19e3767d1 100644 --- a/src/chat-swarm-continuation-store.test.ts +++ b/src/chat-swarm-continuation-store.test.ts @@ -7,6 +7,7 @@ import { ChatSwarmError } from "./chat-swarm-contract.js"; import { ChatSwarmCoordinator } from "./chat-swarm-coordinator.js"; import { ChatSwarmStore } from "./chat-swarm-store.js"; import { ChatSwarmContinuationStore } from "./chat-swarm-continuation-store.js"; +import { openDatabase } from "./db/client.js"; import { resolveChatSwarmIdentity } from "./request-meta.js"; function fixture() { @@ -59,7 +60,7 @@ function createInput(f: ReturnType, attemptKey = "continuation-a }; } -test("durable continuation request replays exactly and changed target conflicts on the same attempt", () => { +test("durable continuation request replays exactly and changed material conflicts on the same attempt", () => { const f = fixture(); const continuation = new ChatSwarmContinuationStore(f.root); try { @@ -69,12 +70,21 @@ test("durable continuation request replays exactly and changed target conflicts assert.equal(first.request.version, 1); assert.equal(first.request.sourceEpoch, 0); assert.equal(first.request.targetEpoch, 1); + assert.equal(first.request.ttlSeconds, 15 * 60); const replay = continuation.createRequest(f.targetFingerprint, createInput(f)); assert.equal(replay.created, false); assert.equal(replay.request.id, first.request.id); assert.equal(replay.request.requestHash, first.request.requestHash); + assert.throws( + () => continuation.createRequest(f.targetFingerprint, { + ...createInput(f), + ttlSeconds: 1, + }), + (error: unknown) => error instanceof ChatSwarmError && error.code === "REPLAY_CONFLICT", + ); + const otherTarget = resolveChatSwarmIdentity({ "openai/conversation_id": "continuation-target-other", }).fingerprint; @@ -295,3 +305,31 @@ test("expired continuation cannot transfer and target readback remains bounded t cleanup(f, continuation); } }); + +test("persisted continuation attempt identity tamper fails closed", () => { + const f = fixture(); + const continuation = new ChatSwarmContinuationStore(f.root); + try { + const created = continuation.createRequest(f.targetFingerprint, createInput(f, "tamper-attempt")); + const database = openDatabase(f.root); + try { + const row = database.sqlite.prepare( + "select request_json from durable_operations where operation_id=?", + ).get(created.request.id) as { request_json: string }; + const request = JSON.parse(row.request_json) as Record; + request.attemptKey = "tampered-attempt"; + database.sqlite.prepare( + "update durable_operations set request_json=? where operation_id=?", + ).run(JSON.stringify(request), created.request.id); + } finally { + database.close(); + } + + assert.throws( + () => continuation.getRequest(created.request.id), + (error: unknown) => error instanceof ChatSwarmError && error.code === "INVALID_STATE", + ); + } finally { + cleanup(f, continuation); + } +}); From d4ba7b80ed054d44761a3ec89d5ec6c59c296148 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:30:57 +0800 Subject: [PATCH 32/55] test(chat-swarm): bind domain fixture ttl --- src/chat-swarm-continuation-domain.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/chat-swarm-continuation-domain.test.ts b/src/chat-swarm-continuation-domain.test.ts index 1519f5c7e..86ea1c003 100644 --- a/src/chat-swarm-continuation-domain.test.ts +++ b/src/chat-swarm-continuation-domain.test.ts @@ -46,6 +46,7 @@ function request(overrides: Partial = {}): ChatSwa sourceCarrierFingerprint: material.sourceCarrierFingerprint, targetCarrierFingerprint: material.targetCarrierFingerprint, checkpointHash: material.checkpointHash, + ttlSeconds: 15 * 60, version: 1, status: "PENDING", requestedAt: "2026-09-13T00:00:00.000Z", From 510adc4dd8e7f18fc29f50f96443497fe7049f30 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:33:11 +0800 Subject: [PATCH 33/55] fix(chat-swarm): derive continuation expiry from one clock read --- src/chat-swarm-continuation-store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chat-swarm-continuation-store.ts b/src/chat-swarm-continuation-store.ts index 588f92cf9..b6251be0f 100644 --- a/src/chat-swarm-continuation-store.ts +++ b/src/chat-swarm-continuation-store.ts @@ -145,7 +145,7 @@ export class ChatSwarmContinuationStore { } const requestedAt = now; - const expiresAt = new Date(this.clock().getTime() + ttlSeconds * 1000).toISOString(); + const expiresAt = new Date(Date.parse(requestedAt) + ttlSeconds * 1000).toISOString(); const request: PersistedRequest = { schema: REQUEST_SCHEMA, version: 1, From e52c713a365d4313293243a1b689e723df76baee Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:35:58 +0800 Subject: [PATCH 34/55] fix(chat-swarm): fence new continuation while outcome unresolved --- src/chat-swarm-continuation-store.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/chat-swarm-continuation-store.ts b/src/chat-swarm-continuation-store.ts index b6251be0f..44ddd75a3 100644 --- a/src/chat-swarm-continuation-store.ts +++ b/src/chat-swarm-continuation-store.ts @@ -139,6 +139,7 @@ export class ChatSwarmContinuationStore { const now = this.nowIso(); this.persistExpiredPendingForWorker(input.workerId, now); + this.assertNoUnresolvedContinuation(input.workerId, now); const pendingCount = this.listPendingDurableForWorker(input.workerId).length; if (pendingCount >= MAX_PENDING_PER_WORKER) { throw new ChatSwarmError("CAPACITY_FULL", "too many pending continuation requests for worker"); @@ -482,6 +483,24 @@ export class ChatSwarmContinuationStore { return rows.filter((row) => readPersistedRequest(row).workerId === workerId); } + private assertNoUnresolvedContinuation(workerId: string, now: string): void { + const rows = this.database.sqlite.prepare( + "select * from durable_operations where kind=? and scope_root=? and status='outcome_unknown'", + ).all(KIND, this.scopeRoot) as DurableRow[]; + for (const row of rows) { + const request = this.toRequest(row); + if (request.workerId !== workerId) continue; + if (Date.parse(request.expiresAt) <= Date.parse(now)) { + this.persistExpired(request.id, now); + continue; + } + throw new ChatSwarmError( + "RECONCILIATION_REQUIRED", + "worker has an unresolved continuation outcome; reconcile it before creating another replacement", + ); + } + } + private persistExpiredPendingForWorker(workerId: string, now: string): void { for (const row of this.listPendingDurableForWorker(workerId)) { const request = readPersistedRequest(row); From 7495740b0c47a29f871b2f2515beb18a07c23e90 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:37:38 +0800 Subject: [PATCH 35/55] test(chat-swarm): block second replacement while continuation unresolved --- ...t-swarm-continuation-restart-fence.test.ts | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/chat-swarm-continuation-restart-fence.test.ts diff --git a/src/chat-swarm-continuation-restart-fence.test.ts b/src/chat-swarm-continuation-restart-fence.test.ts new file mode 100644 index 000000000..0b472d844 --- /dev/null +++ b/src/chat-swarm-continuation-restart-fence.test.ts @@ -0,0 +1,75 @@ +import assert from "node:assert/strict"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { ChatSwarmError } from "./chat-swarm-contract.js"; +import { ChatSwarmContinuationStore } from "./chat-swarm-continuation-store.js"; +import { ChatSwarmCoordinator } from "./chat-swarm-coordinator.js"; +import { ChatSwarmStore } from "./chat-swarm-store.js"; +import { resolveChatSwarmIdentity } from "./request-meta.js"; + +test("unresolved continuation fences any second replacement attempt", () => { + const root = mkdtempSync(join(tmpdir(), "swarm-continuation-restart-fence-")); + const swarmStore = new ChatSwarmStore(root); + const continuation = new ChatSwarmContinuationStore(root); + const coordinator = new ChatSwarmCoordinator(swarmStore); + const ownerMeta = { "openai/session": "continuation-owner" }; + const sourceMeta = { "openai/conversation_id": "continuation-source" }; + const targetA = resolveChatSwarmIdentity({ "openai/conversation_id": "continuation-target-a" }).fingerprint; + const targetB = resolveChatSwarmIdentity({ "openai/conversation_id": "continuation-target-b" }).fingerprint; + const ownerFingerprint = resolveChatSwarmIdentity(ownerMeta).fingerprint; + + try { + const swarm = coordinator.createSwarm(ownerMeta, { workerLimit: 2, metadata: { test: true } }); + const worker = coordinator.joinWorker(sourceMeta, swarm.id, { + label: "Worker-01", + runtimeKind: "mcp_peer", + }); + coordinator.checkpoint( + sourceMeta, + worker.id, + 0, + new Date(Date.now() + 60 * 60 * 1000).toISOString(), + { summary: "safe checkpoint" }, + ); + + const first = continuation.createRequest(targetA, { + swarmId: swarm.id, + workerId: worker.id, + attemptKey: "first-replacement", + sourceEpoch: 0, + }); + assert.equal(continuation.recoverAfterRestart(), 1); + assert.equal(continuation.getRequest(first.request.id)?.status, "RECONCILE_REQUIRED"); + + assert.throws( + () => continuation.createRequest(targetB, { + swarmId: swarm.id, + workerId: worker.id, + attemptKey: "second-replacement", + sourceEpoch: 0, + }), + (error: unknown) => error instanceof ChatSwarmError && error.code === "RECONCILIATION_REQUIRED", + ); + assert.equal(continuation.getLatestForTarget(swarm.id, targetB), undefined); + assert.equal(swarmStore.getWorker(worker.id)?.continuationEpoch, 0); + assert.notEqual(swarmStore.getWorker(worker.id)?.carrierConversationFingerprint, targetB); + + const reconciled = continuation.reconcileUnknownNoEffect(ownerFingerprint, first.request.id); + assert.equal(reconciled.status, "PENDING"); + + const second = continuation.createRequest(targetB, { + swarmId: swarm.id, + workerId: worker.id, + attemptKey: "second-replacement", + sourceEpoch: 0, + }); + assert.equal(second.created, true); + assert.equal(second.request.status, "PENDING"); + } finally { + continuation.close(); + swarmStore.close(); + rmSync(root, { recursive: true, force: true }); + } +}); From 1adbb53913901c13b5b169a1d327b7efd007d15e Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:38:09 +0800 Subject: [PATCH 36/55] test(chat-swarm): include unresolved continuation restart fence --- src/chat-swarm-continuation-domain.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/chat-swarm-continuation-domain.test.ts b/src/chat-swarm-continuation-domain.test.ts index 86ea1c003..e24c9a0ac 100644 --- a/src/chat-swarm-continuation-domain.test.ts +++ b/src/chat-swarm-continuation-domain.test.ts @@ -1,6 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; import "./chat-swarm-continuation-coordinator.test.js"; +import "./chat-swarm-continuation-restart-fence.test.js"; import { ChatSwarmError } from "./chat-swarm-contract.js"; import { assertContinuationCommitAllowed, From 09af4504af3cdad8ca0c3ecf78f4ef43906f3c81 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:39:23 +0800 Subject: [PATCH 37/55] refactor(chat-swarm): keep one authoritative continuation request hash --- src/chat-swarm-continuation-domain.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/chat-swarm-continuation-domain.ts b/src/chat-swarm-continuation-domain.ts index 5e63962e5..96c0ed3b5 100644 --- a/src/chat-swarm-continuation-domain.ts +++ b/src/chat-swarm-continuation-domain.ts @@ -1,4 +1,3 @@ -import { createHash } from "node:crypto"; import { canonicalize, ChatSwarmError, @@ -22,7 +21,6 @@ export interface WorkerContinuationSnapshot { } export interface PreparedContinuationMaterial { - requestHash: string; sourceEpoch: number; targetEpoch: number; sourceCarrierFingerprint: string; @@ -62,20 +60,8 @@ export function prepareContinuationMaterial( const checkpointJson = JSON.stringify(canonicalize(worker.checkpoint)); const checkpointHash = hashContent(checkpointJson); const targetEpoch = input.sourceEpoch + 1; - const requestHash = createHash("sha256") - .update(JSON.stringify(canonicalize({ - swarmId: input.swarmId, - workerId: input.workerId, - sourceEpoch: input.sourceEpoch, - targetEpoch, - sourceCarrierFingerprint, - targetCarrierFingerprint, - checkpointHash, - }))) - .digest("hex"); return { - requestHash, sourceEpoch: input.sourceEpoch, targetEpoch, sourceCarrierFingerprint, From 2807a975c635c4e4a8c8a38ed1301738fb742caa Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:39:47 +0800 Subject: [PATCH 38/55] test(chat-swarm): keep request hash authority in durable store --- src/chat-swarm-continuation-domain.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/chat-swarm-continuation-domain.test.ts b/src/chat-swarm-continuation-domain.test.ts index e24c9a0ac..a4c70b5e4 100644 --- a/src/chat-swarm-continuation-domain.test.ts +++ b/src/chat-swarm-continuation-domain.test.ts @@ -41,7 +41,7 @@ function request(overrides: Partial = {}): ChatSwa swarmId: "swarm-1", workerId: "worker-1", attemptKey: "cont-1", - requestHash: material.requestHash, + requestHash: "d".repeat(64), sourceEpoch: material.sourceEpoch, targetEpoch: material.targetEpoch, sourceCarrierFingerprint: material.sourceCarrierFingerprint, @@ -63,11 +63,10 @@ test("prepare binds exact epoch, authenticated target carrier and checkpoint", ( assert.equal(material.sourceCarrierFingerprint, source); assert.equal(material.targetCarrierFingerprint, target); assert.match(material.checkpointHash, /^[0-9a-f]{64}$/); - assert.match(material.requestHash, /^[0-9a-f]{64}$/); const otherTarget = "c".repeat(64); const other = prepareContinuationMaterial(worker(), createInput(), otherTarget); - assert.notEqual(material.requestHash, other.requestHash); + assert.notEqual(material.targetCarrierFingerprint, other.targetCarrierFingerprint); }); test("prepare rejects unsafe active worker and stale epoch", () => { From f8e756d2d4c9db660f248d00ad7526c97711d62b Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:42:16 +0800 Subject: [PATCH 39/55] test(chat-swarm): keep retired carrier fenced from continuation target --- ...t-swarm-continuation-stale-carrier.test.ts | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 src/chat-swarm-continuation-stale-carrier.test.ts diff --git a/src/chat-swarm-continuation-stale-carrier.test.ts b/src/chat-swarm-continuation-stale-carrier.test.ts new file mode 100644 index 000000000..d60f87e8c --- /dev/null +++ b/src/chat-swarm-continuation-stale-carrier.test.ts @@ -0,0 +1,86 @@ +import assert from "node:assert/strict"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { ChatSwarmError } from "./chat-swarm-contract.js"; +import { ChatSwarmContinuationStore } from "./chat-swarm-continuation-store.js"; +import { ChatSwarmCoordinator } from "./chat-swarm-coordinator.js"; +import { ChatSwarmStore } from "./chat-swarm-store.js"; +import { resolveChatSwarmIdentity } from "./request-meta.js"; + +test("successful continuation permanently fences the retired source carrier", () => { + const root = mkdtempSync(join(tmpdir(), "swarm-continuation-stale-carrier-")); + const swarmStore = new ChatSwarmStore(root); + const continuation = new ChatSwarmContinuationStore(root); + const coordinator = new ChatSwarmCoordinator(swarmStore); + const ownerMeta = { "openai/session": "continuation-owner" }; + const carrierAMeta = { "openai/conversation_id": "continuation-carrier-a" }; + const carrierBMeta = { "openai/conversation_id": "continuation-carrier-b" }; + const carrierA = resolveChatSwarmIdentity(carrierAMeta).fingerprint; + const carrierB = resolveChatSwarmIdentity(carrierBMeta).fingerprint; + const carrierC = resolveChatSwarmIdentity({ "openai/conversation_id": "continuation-carrier-c" }).fingerprint; + const ownerFingerprint = resolveChatSwarmIdentity(ownerMeta).fingerprint; + + try { + const swarm = coordinator.createSwarm(ownerMeta, { workerLimit: 2, metadata: { test: true } }); + const worker = coordinator.joinWorker(carrierAMeta, swarm.id, { + label: "Worker-01", + runtimeKind: "mcp_peer", + }); + coordinator.checkpoint( + carrierAMeta, + worker.id, + 0, + new Date(Date.now() + 60 * 60 * 1000).toISOString(), + { summary: "epoch zero checkpoint" }, + ); + + const first = continuation.createRequest(carrierB, { + swarmId: swarm.id, + workerId: worker.id, + attemptKey: "a-to-b", + sourceEpoch: 0, + }); + continuation.approveRequest(ownerFingerprint, { + swarmId: swarm.id, + requestId: first.request.id, + expectedRequestVersion: 1, + expectedSwarmVersion: 1, + }); + assert.equal(swarmStore.getWorker(worker.id)?.carrierConversationFingerprint, carrierB); + assert.equal(swarmStore.getWorker(worker.id)?.continuationEpoch, 1); + + coordinator.checkpoint( + carrierBMeta, + worker.id, + 1, + new Date(Date.now() + 60 * 60 * 1000).toISOString(), + { summary: "epoch one checkpoint" }, + ); + + assert.throws( + () => continuation.createRequest(carrierA, { + swarmId: swarm.id, + workerId: worker.id, + attemptKey: "b-back-to-retired-a", + sourceEpoch: 1, + }), + (error: unknown) => error instanceof ChatSwarmError && error.code === "OWNERSHIP_CONFLICT", + ); + assert.equal(continuation.getLatestForTarget(swarm.id, carrierA), undefined); + + const fresh = continuation.createRequest(carrierC, { + swarmId: swarm.id, + workerId: worker.id, + attemptKey: "b-to-fresh-c", + sourceEpoch: 1, + }); + assert.equal(fresh.created, true); + assert.equal(fresh.request.targetCarrierFingerprint, carrierC); + } finally { + continuation.close(); + swarmStore.close(); + rmSync(root, { recursive: true, force: true }); + } +}); From 4594e1efc83e81cf8b903f5754bb1d8cee196173 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:42:40 +0800 Subject: [PATCH 40/55] test(chat-swarm): include retired carrier continuation fence --- src/chat-swarm-continuation-domain.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/chat-swarm-continuation-domain.test.ts b/src/chat-swarm-continuation-domain.test.ts index a4c70b5e4..cfd336f51 100644 --- a/src/chat-swarm-continuation-domain.test.ts +++ b/src/chat-swarm-continuation-domain.test.ts @@ -2,6 +2,7 @@ import assert from "node:assert/strict"; import test from "node:test"; import "./chat-swarm-continuation-coordinator.test.js"; import "./chat-swarm-continuation-restart-fence.test.js"; +import "./chat-swarm-continuation-stale-carrier.test.js"; import { ChatSwarmError } from "./chat-swarm-contract.js"; import { assertContinuationCommitAllowed, From f09da748e42d494ae05436252c19bd52bd267f4f Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:44:26 +0800 Subject: [PATCH 41/55] fix(chat-swarm): fence retired continuation carriers --- src/chat-swarm-continuation-store.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/chat-swarm-continuation-store.ts b/src/chat-swarm-continuation-store.ts index 44ddd75a3..68e4db459 100644 --- a/src/chat-swarm-continuation-store.ts +++ b/src/chat-swarm-continuation-store.ts @@ -116,6 +116,7 @@ export class ChatSwarmContinuationStore { authenticatedTargetCarrierFingerprint, ); this.assertTargetCarrierAvailable(authenticatedTargetCarrierFingerprint, input.workerId); + this.assertTargetCarrierNotRetired(authenticatedTargetCarrierFingerprint, input.workerId); const requestHash = createHash("sha256").update(JSON.stringify({ attemptKey: input.attemptKey, checkpointHash: material.checkpointHash, @@ -258,6 +259,7 @@ export class ChatSwarmContinuationStore { const worker = this.workerSnapshot(request.workerId); assertContinuationCommitAllowed(worker, request); this.assertTargetCarrierAvailable(request.targetCarrierFingerprint, request.workerId); + this.assertTargetCarrierNotRetired(request.targetCarrierFingerprint, request.workerId); const workerUpdate = this.database.sqlite.prepare(` update chat_swarm_workers @@ -470,6 +472,24 @@ export class ChatSwarmContinuationStore { } } + private assertTargetCarrierNotRetired(targetCarrierFingerprint: string, workerId: string): void { + const rows = this.database.sqlite.prepare( + "select * from durable_operations where kind=? and scope_root=? and status='succeeded'", + ).all(KIND, this.scopeRoot) as DurableRow[]; + for (const row of rows) { + const request = this.toRequest(row); + if ( + request.workerId === workerId && + request.sourceCarrierFingerprint === targetCarrierFingerprint + ) { + throw new ChatSwarmError( + "OWNERSHIP_CONFLICT", + "target carrier is retired by an accepted continuation and cannot be rebound", + ); + } + } + } + private getDurableByAttempt(durableAttemptKey: string): DurableRow | undefined { return this.database.sqlite.prepare( "select * from durable_operations where scope_root=? and attempt_key=? limit 1", From cbd4743e8aafc402d8b8a1318dfba9e2a9074635 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:46:32 +0800 Subject: [PATCH 42/55] fix(chat-swarm): make terminal continuation replay state-independent --- src/chat-swarm-continuation-store.ts | 37 +++++++++++++++++++++------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/chat-swarm-continuation-store.ts b/src/chat-swarm-continuation-store.ts index 68e4db459..e0bbe1105 100644 --- a/src/chat-swarm-continuation-store.ts +++ b/src/chat-swarm-continuation-store.ts @@ -108,6 +108,34 @@ export class ChatSwarmContinuationStore { } const tx = this.database.sqlite.transaction(() => { + const durableAttemptKey = stableAttemptKey(input.swarmId, input.workerId, input.attemptKey); + const existing = this.getDurableByAttempt(durableAttemptKey); + if (existing) { + if (existing.kind !== KIND) { + throw new ChatSwarmError("REPLAY_CONFLICT", "continuation attemptKey is bound to different material"); + } + const persisted = readPersistedRequest(existing); + const replayHash = continuationMaterialHash({ + ...persisted, + swarmId: input.swarmId, + workerId: input.workerId, + attemptKey: input.attemptKey, + sourceEpoch: input.sourceEpoch, + targetEpoch: input.sourceEpoch + 1, + targetCarrierFingerprint: authenticatedTargetCarrierFingerprint, + ttlSeconds, + }); + if (existing.request_hash !== replayHash) { + throw new ChatSwarmError("REPLAY_CONFLICT", "continuation attemptKey is bound to different material"); + } + const replay = this.toRequest(existing); + if (replay.status === "EXPIRED" && (existing.status === "started" || existing.status === "outcome_unknown")) { + this.persistExpired(replay.id, this.nowIso()); + return { request: this.getRequest(replay.id)!, created: false }; + } + return { request: replay, created: false }; + } + this.requireActiveSwarm(input.swarmId); const worker = this.workerSnapshot(input.workerId); const material = prepareContinuationMaterial( @@ -129,15 +157,6 @@ export class ChatSwarmContinuationStore { workerId: input.workerId, })).digest("hex"); - const durableAttemptKey = stableAttemptKey(input.swarmId, input.workerId, input.attemptKey); - const existing = this.getDurableByAttempt(durableAttemptKey); - if (existing) { - if (existing.kind !== KIND || existing.request_hash !== requestHash) { - throw new ChatSwarmError("REPLAY_CONFLICT", "continuation attemptKey is bound to different material"); - } - return { request: this.toRequest(existing), created: false }; - } - const now = this.nowIso(); this.persistExpiredPendingForWorker(input.workerId, now); this.assertNoUnresolvedContinuation(input.workerId, now); From 077017c7e938da68fc79c8db215e87069a41e83a Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:46:56 +0800 Subject: [PATCH 43/55] test(chat-swarm): preserve terminal continuation replay identity --- ...swarm-continuation-terminal-replay.test.ts | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 src/chat-swarm-continuation-terminal-replay.test.ts diff --git a/src/chat-swarm-continuation-terminal-replay.test.ts b/src/chat-swarm-continuation-terminal-replay.test.ts new file mode 100644 index 000000000..de1a17810 --- /dev/null +++ b/src/chat-swarm-continuation-terminal-replay.test.ts @@ -0,0 +1,134 @@ +import assert from "node:assert/strict"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { ChatSwarmError } from "./chat-swarm-contract.js"; +import { ChatSwarmContinuationStore } from "./chat-swarm-continuation-store.js"; +import { ChatSwarmCoordinator } from "./chat-swarm-coordinator.js"; +import { ChatSwarmStore } from "./chat-swarm-store.js"; +import { resolveChatSwarmIdentity } from "./request-meta.js"; + +function fixture(clock?: () => Date) { + const root = mkdtempSync(join(tmpdir(), "swarm-continuation-terminal-replay-")); + const swarmStore = new ChatSwarmStore(root); + const continuation = new ChatSwarmContinuationStore(root, clock); + const coordinator = new ChatSwarmCoordinator(swarmStore); + const ownerMeta = { "openai/session": "continuation-owner" }; + const sourceMeta = { "openai/conversation_id": "continuation-source" }; + const targetA = resolveChatSwarmIdentity({ "openai/conversation_id": "continuation-target-a" }).fingerprint; + const targetB = resolveChatSwarmIdentity({ "openai/conversation_id": "continuation-target-b" }).fingerprint; + const ownerFingerprint = resolveChatSwarmIdentity(ownerMeta).fingerprint; + const swarm = coordinator.createSwarm(ownerMeta, { workerLimit: 2, metadata: { test: true } }); + const worker = coordinator.joinWorker(sourceMeta, swarm.id, { + label: "Worker-01", + runtimeKind: "mcp_peer", + }); + coordinator.checkpoint( + sourceMeta, + worker.id, + 0, + new Date(Date.now() + 60 * 60 * 1000).toISOString(), + { summary: "safe checkpoint" }, + ); + return { root, swarmStore, continuation, coordinator, ownerFingerprint, swarm, worker, targetA, targetB }; +} + +function cleanup(f: ReturnType) { + f.continuation.close(); + f.swarmStore.close(); + rmSync(f.root, { recursive: true, force: true }); +} + +test("approved continuation exact replay returns the same durable result after epoch advances", () => { + const f = fixture(); + try { + const input = { + swarmId: f.swarm.id, + workerId: f.worker.id, + attemptKey: "approved-replay", + sourceEpoch: 0, + }; + const created = f.continuation.createRequest(f.targetA, input); + const approved = f.continuation.approveRequest(f.ownerFingerprint, { + swarmId: f.swarm.id, + requestId: created.request.id, + expectedRequestVersion: 1, + expectedSwarmVersion: 1, + }); + assert.equal(approved.status, "APPROVED"); + + const replay = f.continuation.createRequest(f.targetA, input); + assert.equal(replay.created, false); + assert.equal(replay.request.id, created.request.id); + assert.equal(replay.request.status, "APPROVED"); + assert.equal(replay.request.version, approved.version); + + assert.throws( + () => f.continuation.createRequest(f.targetA, { ...input, ttlSeconds: 1 }), + (error: unknown) => error instanceof ChatSwarmError && error.code === "REPLAY_CONFLICT", + ); + } finally { + cleanup(f); + } +}); + +test("superseded continuation exact replay returns the same terminal loser", () => { + const f = fixture(); + try { + const inputA = { + swarmId: f.swarm.id, + workerId: f.worker.id, + attemptKey: "winner-replay", + sourceEpoch: 0, + }; + const inputB = { + swarmId: f.swarm.id, + workerId: f.worker.id, + attemptKey: "loser-replay", + sourceEpoch: 0, + }; + const winner = f.continuation.createRequest(f.targetA, inputA).request; + const loser = f.continuation.createRequest(f.targetB, inputB).request; + f.continuation.approveRequest(f.ownerFingerprint, { + swarmId: f.swarm.id, + requestId: winner.id, + expectedRequestVersion: 1, + expectedSwarmVersion: 1, + }); + assert.equal(f.continuation.getRequest(loser.id)?.status, "SUPERSEDED"); + + const replay = f.continuation.createRequest(f.targetB, inputB); + assert.equal(replay.created, false); + assert.equal(replay.request.id, loser.id); + assert.equal(replay.request.status, "SUPERSEDED"); + } finally { + cleanup(f); + } +}); + +test("expired exact replay durably persists terminal expiry", () => { + let now = new Date("2026-09-13T06:00:00.000Z"); + const f = fixture(() => now); + try { + const input = { + swarmId: f.swarm.id, + workerId: f.worker.id, + attemptKey: "expired-replay", + sourceEpoch: 0, + ttlSeconds: 1, + }; + const created = f.continuation.createRequest(f.targetA, input); + now = new Date("2026-09-13T06:00:02.000Z"); + + const replay = f.continuation.createRequest(f.targetA, input); + assert.equal(replay.created, false); + assert.equal(replay.request.id, created.request.id); + assert.equal(replay.request.status, "EXPIRED"); + assert.equal(replay.request.version, 2); + assert.equal(f.continuation.getRequest(created.request.id)?.status, "EXPIRED"); + assert.equal(f.continuation.recoverAfterRestart(), 0); + } finally { + cleanup(f); + } +}); From a98f031f5a04f986bfc212229b25a3b14de446e7 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:47:17 +0800 Subject: [PATCH 44/55] test(chat-swarm): include terminal continuation replay witnesses --- src/chat-swarm-continuation-domain.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/chat-swarm-continuation-domain.test.ts b/src/chat-swarm-continuation-domain.test.ts index cfd336f51..4d03fa852 100644 --- a/src/chat-swarm-continuation-domain.test.ts +++ b/src/chat-swarm-continuation-domain.test.ts @@ -3,6 +3,7 @@ import test from "node:test"; import "./chat-swarm-continuation-coordinator.test.js"; import "./chat-swarm-continuation-restart-fence.test.js"; import "./chat-swarm-continuation-stale-carrier.test.js"; +import "./chat-swarm-continuation-terminal-replay.test.js"; import { ChatSwarmError } from "./chat-swarm-contract.js"; import { assertContinuationCommitAllowed, From 492a3aa50f59adbf5cd16bd4b769cd90ddc68199 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:48:31 +0800 Subject: [PATCH 45/55] test(chat-swarm): fail closed on corrupt pending continuation state --- ...warm-continuation-corruption-fence.test.ts | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/chat-swarm-continuation-corruption-fence.test.ts diff --git a/src/chat-swarm-continuation-corruption-fence.test.ts b/src/chat-swarm-continuation-corruption-fence.test.ts new file mode 100644 index 000000000..f51d19922 --- /dev/null +++ b/src/chat-swarm-continuation-corruption-fence.test.ts @@ -0,0 +1,74 @@ +import assert from "node:assert/strict"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { ChatSwarmError } from "./chat-swarm-contract.js"; +import { ChatSwarmContinuationStore } from "./chat-swarm-continuation-store.js"; +import { ChatSwarmCoordinator } from "./chat-swarm-coordinator.js"; +import { ChatSwarmStore } from "./chat-swarm-store.js"; +import { openDatabase } from "./db/client.js"; +import { resolveChatSwarmIdentity } from "./request-meta.js"; + +test("corrupt pending continuation identity fences all new continuation work", () => { + const root = mkdtempSync(join(tmpdir(), "swarm-continuation-corruption-fence-")); + const swarmStore = new ChatSwarmStore(root); + const continuation = new ChatSwarmContinuationStore(root); + const coordinator = new ChatSwarmCoordinator(swarmStore); + const ownerMeta = { "openai/session": "continuation-owner" }; + const sourceMeta = { "openai/conversation_id": "continuation-source" }; + const targetA = resolveChatSwarmIdentity({ "openai/conversation_id": "continuation-target-a" }).fingerprint; + const targetB = resolveChatSwarmIdentity({ "openai/conversation_id": "continuation-target-b" }).fingerprint; + + try { + const swarm = coordinator.createSwarm(ownerMeta, { workerLimit: 2, metadata: { test: true } }); + const worker = coordinator.joinWorker(sourceMeta, swarm.id, { + label: "Worker-01", + runtimeKind: "mcp_peer", + }); + coordinator.checkpoint( + sourceMeta, + worker.id, + 0, + new Date(Date.now() + 60 * 60 * 1000).toISOString(), + { summary: "safe checkpoint" }, + ); + + const first = continuation.createRequest(targetA, { + swarmId: swarm.id, + workerId: worker.id, + attemptKey: "original-attempt", + sourceEpoch: 0, + }); + + const database = openDatabase(root); + try { + const row = database.sqlite.prepare( + "select request_json from durable_operations where operation_id=?", + ).get(first.request.id) as { request_json: string }; + const request = JSON.parse(row.request_json) as Record; + request.attemptKey = "tampered-attempt"; + database.sqlite.prepare( + "update durable_operations set request_json=? where operation_id=?", + ).run(JSON.stringify(request), first.request.id); + } finally { + database.close(); + } + + assert.throws( + () => continuation.createRequest(targetB, { + swarmId: swarm.id, + workerId: worker.id, + attemptKey: "new-attempt-must-not-bypass-corruption", + sourceEpoch: 0, + }), + (error: unknown) => error instanceof ChatSwarmError && error.code === "INVALID_STATE", + ); + assert.equal(continuation.getLatestForTarget(swarm.id, targetB), undefined); + assert.equal(swarmStore.getWorker(worker.id)?.continuationEpoch, 0); + } finally { + continuation.close(); + swarmStore.close(); + rmSync(root, { recursive: true, force: true }); + } +}); From b26bb98462b388884a9a98857e55e10992fa0837 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:48:50 +0800 Subject: [PATCH 46/55] test(chat-swarm): include corrupt continuation fail-closed witness --- src/chat-swarm-continuation-domain.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/chat-swarm-continuation-domain.test.ts b/src/chat-swarm-continuation-domain.test.ts index 4d03fa852..0631b1ed8 100644 --- a/src/chat-swarm-continuation-domain.test.ts +++ b/src/chat-swarm-continuation-domain.test.ts @@ -1,6 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; import "./chat-swarm-continuation-coordinator.test.js"; +import "./chat-swarm-continuation-corruption-fence.test.js"; import "./chat-swarm-continuation-restart-fence.test.js"; import "./chat-swarm-continuation-stale-carrier.test.js"; import "./chat-swarm-continuation-terminal-replay.test.js"; From bd37051945df09875fe14a215f043409f2366d12 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:49:57 +0800 Subject: [PATCH 47/55] fix(chat-swarm): validate pending continuation durable identity --- src/chat-swarm-continuation-store.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/chat-swarm-continuation-store.ts b/src/chat-swarm-continuation-store.ts index e0bbe1105..1b47e34e4 100644 --- a/src/chat-swarm-continuation-store.ts +++ b/src/chat-swarm-continuation-store.ts @@ -519,7 +519,7 @@ export class ChatSwarmContinuationStore { const rows = this.database.sqlite.prepare( "select * from durable_operations where kind=? and scope_root=? and status='started'", ).all(KIND, this.scopeRoot) as DurableRow[]; - return rows.filter((row) => readPersistedRequest(row).workerId === workerId); + return rows.filter((row) => this.toRequest(row).workerId === workerId); } private assertNoUnresolvedContinuation(workerId: string, now: string): void { @@ -542,7 +542,7 @@ export class ChatSwarmContinuationStore { private persistExpiredPendingForWorker(workerId: string, now: string): void { for (const row of this.listPendingDurableForWorker(workerId)) { - const request = readPersistedRequest(row); + const request = this.toRequest(row); if (Date.parse(request.expiresAt) <= Date.parse(now)) this.persistExpired(row.operation_id, now); } } From 7e21a2c23b5932ccdbadc6cedbdd4dad12860534 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 14:59:55 +0800 Subject: [PATCH 48/55] ci(chat-swarm): isolate continuation regressions on macOS --- .github/workflows/ci.yml | 66 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03043a9f3..9f657ab25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,6 +64,70 @@ jobs: - name: Doctor run: node dist/cli.js doctor + chat-swarm-continuation: + name: Chat Swarm continuation focused (macOS) + runs-on: macos-latest + timeout-minutes: 10 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - id: coordinator + name: Continuation coordinator + continue-on-error: true + run: npx tsx src/chat-swarm-continuation-coordinator.test.ts + + - id: store + name: Continuation durable store + continue-on-error: true + run: npx tsx src/chat-swarm-continuation-store.test.ts + + - id: restart_fence + name: Continuation restart fence + continue-on-error: true + run: npx tsx src/chat-swarm-continuation-restart-fence.test.ts + + - id: stale_carrier + name: Continuation retired-carrier fence + continue-on-error: true + run: npx tsx src/chat-swarm-continuation-stale-carrier.test.ts + + - id: terminal_replay + name: Continuation terminal replay + continue-on-error: true + run: npx tsx src/chat-swarm-continuation-terminal-replay.test.ts + + - id: corruption_fence + name: Continuation corruption fence + continue-on-error: true + run: npx tsx src/chat-swarm-continuation-corruption-fence.test.ts + + - id: domain + name: Continuation aggregate/domain + continue-on-error: true + run: npx tsx src/chat-swarm-continuation-domain.test.ts + + - name: Require focused continuation green + if: always() + run: | + test "${{ steps.coordinator.outcome }}" = "success" + test "${{ steps.store.outcome }}" = "success" + test "${{ steps.restart_fence.outcome }}" = "success" + test "${{ steps.stale_carrier.outcome }}" = "success" + test "${{ steps.terminal_replay.outcome }}" = "success" + test "${{ steps.corruption_fence.outcome }}" = "success" + test "${{ steps.domain.outcome }}" = "success" + local-agent-sessions: name: Local agent sessions (macOS) runs-on: macos-latest @@ -83,4 +147,4 @@ jobs: run: npm ci - name: Test local agent sessions - run: npm run test:local-agent-sessions + run: npm run test:local-agent-sessions \ No newline at end of file From 7550f74068a2737d9de5e42c1f28b5e6bd7fe8fb Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 15:01:25 +0800 Subject: [PATCH 49/55] ci(chat-swarm): split continuation focused tests by job --- .github/workflows/ci.yml | 67 +++++++++++++--------------------------- 1 file changed, 21 insertions(+), 46 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f657ab25..f73813b3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,9 +65,27 @@ jobs: run: node dist/cli.js doctor chat-swarm-continuation: - name: Chat Swarm continuation focused (macOS) + name: Chat Swarm continuation ${{ matrix.name }} (macOS) runs-on: macos-latest timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + include: + - name: coordinator + file: src/chat-swarm-continuation-coordinator.test.ts + - name: durable-store + file: src/chat-swarm-continuation-store.test.ts + - name: restart-fence + file: src/chat-swarm-continuation-restart-fence.test.ts + - name: retired-carrier + file: src/chat-swarm-continuation-stale-carrier.test.ts + - name: terminal-replay + file: src/chat-swarm-continuation-terminal-replay.test.ts + - name: corruption-fence + file: src/chat-swarm-continuation-corruption-fence.test.ts + - name: aggregate-domain + file: src/chat-swarm-continuation-domain.test.ts steps: - name: Checkout @@ -82,51 +100,8 @@ jobs: - name: Install dependencies run: npm ci - - id: coordinator - name: Continuation coordinator - continue-on-error: true - run: npx tsx src/chat-swarm-continuation-coordinator.test.ts - - - id: store - name: Continuation durable store - continue-on-error: true - run: npx tsx src/chat-swarm-continuation-store.test.ts - - - id: restart_fence - name: Continuation restart fence - continue-on-error: true - run: npx tsx src/chat-swarm-continuation-restart-fence.test.ts - - - id: stale_carrier - name: Continuation retired-carrier fence - continue-on-error: true - run: npx tsx src/chat-swarm-continuation-stale-carrier.test.ts - - - id: terminal_replay - name: Continuation terminal replay - continue-on-error: true - run: npx tsx src/chat-swarm-continuation-terminal-replay.test.ts - - - id: corruption_fence - name: Continuation corruption fence - continue-on-error: true - run: npx tsx src/chat-swarm-continuation-corruption-fence.test.ts - - - id: domain - name: Continuation aggregate/domain - continue-on-error: true - run: npx tsx src/chat-swarm-continuation-domain.test.ts - - - name: Require focused continuation green - if: always() - run: | - test "${{ steps.coordinator.outcome }}" = "success" - test "${{ steps.store.outcome }}" = "success" - test "${{ steps.restart_fence.outcome }}" = "success" - test "${{ steps.stale_carrier.outcome }}" = "success" - test "${{ steps.terminal_replay.outcome }}" = "success" - test "${{ steps.corruption_fence.outcome }}" = "success" - test "${{ steps.domain.outcome }}" = "success" + - name: Run focused continuation test + run: npx tsx ${{ matrix.file }} local-agent-sessions: name: Local agent sessions (macOS) From 503c900c1d3413207e1db7a9b0a98981c94b0281 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 15:02:50 +0800 Subject: [PATCH 50/55] test(chat-swarm): assert corrupt continuation reads fail closed --- src/chat-swarm-continuation-corruption-fence.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/chat-swarm-continuation-corruption-fence.test.ts b/src/chat-swarm-continuation-corruption-fence.test.ts index f51d19922..f4cf8e10d 100644 --- a/src/chat-swarm-continuation-corruption-fence.test.ts +++ b/src/chat-swarm-continuation-corruption-fence.test.ts @@ -64,7 +64,10 @@ test("corrupt pending continuation identity fences all new continuation work", ( }), (error: unknown) => error instanceof ChatSwarmError && error.code === "INVALID_STATE", ); - assert.equal(continuation.getLatestForTarget(swarm.id, targetB), undefined); + assert.throws( + () => continuation.getLatestForTarget(swarm.id, targetB), + (error: unknown) => error instanceof ChatSwarmError && error.code === "INVALID_STATE", + ); assert.equal(swarmStore.getWorker(worker.id)?.continuationEpoch, 0); } finally { continuation.close(); From 2d1aff0385be305cc1087de93d50a9a5f7ee9955 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 15:24:23 +0800 Subject: [PATCH 51/55] fix(chat-swarm): fail closed on corrupt continuation terminal state --- src/chat-swarm-continuation-store.ts | 34 ++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/chat-swarm-continuation-store.ts b/src/chat-swarm-continuation-store.ts index 1b47e34e4..6a6244b89 100644 --- a/src/chat-swarm-continuation-store.ts +++ b/src/chat-swarm-continuation-store.ts @@ -621,17 +621,37 @@ export class ChatSwarmContinuationStore { let status: ChatSwarmContinuationRequest["status"]; if (row.status === "started") { + if (row.receipt_json !== null || row.error_code !== null || row.error_message !== null) { + throw new ChatSwarmError("INVALID_STATE", "pending continuation terminal metadata is malformed"); + } status = Date.parse(persisted.expiresAt) <= this.clock().getTime() ? "EXPIRED" : "PENDING"; - } else if (row.status === "succeeded") status = "APPROVED"; - else if (row.status === "failed" && row.error_code === "EXPIRED") status = "EXPIRED"; - else if (row.status === "failed" && row.error_code === "SUPERSEDED") status = "SUPERSEDED"; - else if (row.status === "outcome_unknown") status = "RECONCILE_REQUIRED"; - else throw new ChatSwarmError("INVALID_STATE", `unsupported durable continuation status '${row.status}'`); + } else if (row.status === "succeeded") { + if (row.receipt_json === null || row.error_code !== null || row.error_message !== null) { + throw new ChatSwarmError("INVALID_STATE", "approved continuation terminal metadata is malformed"); + } + status = "APPROVED"; + } else if (row.status === "failed" && (row.error_code === "EXPIRED" || row.error_code === "SUPERSEDED")) { + if (row.receipt_json !== null || !row.error_message) { + throw new ChatSwarmError("INVALID_STATE", "failed continuation terminal metadata is malformed"); + } + status = row.error_code === "EXPIRED" ? "EXPIRED" : "SUPERSEDED"; + } else if (row.status === "outcome_unknown") { + if ( + row.receipt_json !== null || + row.error_code !== "RECONCILIATION_REQUIRED" || + !row.error_message + ) { + throw new ChatSwarmError("INVALID_STATE", "unresolved continuation terminal metadata is malformed"); + } + status = "RECONCILE_REQUIRED"; + } else { + throw new ChatSwarmError("INVALID_STATE", `unsupported durable continuation status '${row.status}'`); + } let approvedAt: string | undefined; - if (row.receipt_json) { + if (status === "APPROVED") { try { - const receipt = JSON.parse(row.receipt_json) as PersistedReceipt; + const receipt = JSON.parse(row.receipt_json!) as PersistedReceipt; if (receipt.schema !== RECEIPT_SCHEMA) throw new Error("receipt schema mismatch"); if ( receipt.targetEpoch !== persisted.targetEpoch || From 6f7ccd16cd1e1f9238be0cdb3ff2ff629069142e Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 15:25:03 +0800 Subject: [PATCH 52/55] test(chat-swarm): fence corrupt continuation terminal metadata --- ...warm-continuation-corruption-fence.test.ts | 163 ++++++++++++++---- 1 file changed, 131 insertions(+), 32 deletions(-) diff --git a/src/chat-swarm-continuation-corruption-fence.test.ts b/src/chat-swarm-continuation-corruption-fence.test.ts index f4cf8e10d..13f89ed5a 100644 --- a/src/chat-swarm-continuation-corruption-fence.test.ts +++ b/src/chat-swarm-continuation-corruption-fence.test.ts @@ -10,7 +10,7 @@ import { ChatSwarmStore } from "./chat-swarm-store.js"; import { openDatabase } from "./db/client.js"; import { resolveChatSwarmIdentity } from "./request-meta.js"; -test("corrupt pending continuation identity fences all new continuation work", () => { +function fixture() { const root = mkdtempSync(join(tmpdir(), "swarm-continuation-corruption-fence-")); const swarmStore = new ChatSwarmStore(root); const continuation = new ChatSwarmContinuationStore(root); @@ -19,29 +19,55 @@ test("corrupt pending continuation identity fences all new continuation work", ( const sourceMeta = { "openai/conversation_id": "continuation-source" }; const targetA = resolveChatSwarmIdentity({ "openai/conversation_id": "continuation-target-a" }).fingerprint; const targetB = resolveChatSwarmIdentity({ "openai/conversation_id": "continuation-target-b" }).fingerprint; + const ownerFingerprint = resolveChatSwarmIdentity(ownerMeta).fingerprint; + const swarm = coordinator.createSwarm(ownerMeta, { workerLimit: 2, metadata: { test: true } }); + const worker = coordinator.joinWorker(sourceMeta, swarm.id, { + label: "Worker-01", + runtimeKind: "mcp_peer", + }); + coordinator.checkpoint( + sourceMeta, + worker.id, + 0, + new Date(Date.now() + 60 * 60 * 1000).toISOString(), + { summary: "safe checkpoint" }, + ); + return { + root, + swarmStore, + continuation, + coordinator, + ownerMeta, + sourceMeta, + targetA, + targetB, + ownerFingerprint, + swarm, + worker, + }; +} - try { - const swarm = coordinator.createSwarm(ownerMeta, { workerLimit: 2, metadata: { test: true } }); - const worker = coordinator.joinWorker(sourceMeta, swarm.id, { - label: "Worker-01", - runtimeKind: "mcp_peer", - }); - coordinator.checkpoint( - sourceMeta, - worker.id, - 0, - new Date(Date.now() + 60 * 60 * 1000).toISOString(), - { summary: "safe checkpoint" }, - ); +function cleanup(f: ReturnType): void { + f.continuation.close(); + f.swarmStore.close(); + rmSync(f.root, { recursive: true, force: true }); +} - const first = continuation.createRequest(targetA, { - swarmId: swarm.id, - workerId: worker.id, - attemptKey: "original-attempt", - sourceEpoch: 0, - }); +function createRequest(f: ReturnType, attemptKey: string, target = f.targetA) { + return f.continuation.createRequest(target, { + swarmId: f.swarm.id, + workerId: f.worker.id, + attemptKey, + sourceEpoch: 0, + }); +} + +test("corrupt pending continuation identity fences all new continuation work", () => { + const f = fixture(); + try { + const first = createRequest(f, "original-attempt"); - const database = openDatabase(root); + const database = openDatabase(f.root); try { const row = database.sqlite.prepare( "select request_json from durable_operations where operation_id=?", @@ -56,22 +82,95 @@ test("corrupt pending continuation identity fences all new continuation work", ( } assert.throws( - () => continuation.createRequest(targetB, { - swarmId: swarm.id, - workerId: worker.id, - attemptKey: "new-attempt-must-not-bypass-corruption", - sourceEpoch: 0, - }), + () => createRequest(f, "new-attempt-must-not-bypass-corruption", f.targetB), + (error: unknown) => error instanceof ChatSwarmError && error.code === "INVALID_STATE", + ); + assert.throws( + () => f.continuation.getLatestForTarget(f.swarm.id, f.targetB), + (error: unknown) => error instanceof ChatSwarmError && error.code === "INVALID_STATE", + ); + assert.equal(f.swarmStore.getWorker(f.worker.id)?.continuationEpoch, 0); + } finally { + cleanup(f); + } +}); + +test("succeeded continuation without approval receipt fails closed", () => { + const f = fixture(); + try { + const created = createRequest(f, "approved-with-receipt"); + const approved = f.continuation.approveRequest(f.ownerFingerprint, { + swarmId: f.swarm.id, + requestId: created.request.id, + expectedRequestVersion: 1, + expectedSwarmVersion: 1, + }); + assert.equal(approved.status, "APPROVED"); + + const database = openDatabase(f.root); + try { + database.sqlite.prepare( + "update durable_operations set receipt_json=null where operation_id=?", + ).run(created.request.id); + } finally { + database.close(); + } + + assert.throws( + () => f.continuation.getRequest(created.request.id), + (error: unknown) => error instanceof ChatSwarmError && error.code === "INVALID_STATE", + ); + } finally { + cleanup(f); + } +}); + +test("pending continuation with forged approval receipt fails closed", () => { + const f = fixture(); + try { + const created = createRequest(f, "pending-forged-receipt"); + const database = openDatabase(f.root); + try { + database.sqlite.prepare( + "update durable_operations set receipt_json=? where operation_id=?", + ).run(JSON.stringify({ schema: "forged" }), created.request.id); + } finally { + database.close(); + } + + assert.throws( + () => f.continuation.getRequest(created.request.id), + (error: unknown) => error instanceof ChatSwarmError && error.code === "INVALID_STATE", + ); + assert.throws( + () => createRequest(f, "pending-forged-receipt-new", f.targetB), (error: unknown) => error instanceof ChatSwarmError && error.code === "INVALID_STATE", ); + } finally { + cleanup(f); + } +}); + +test("outcome-unknown continuation with wrong reconciliation code fails closed", () => { + const f = fixture(); + try { + const created = createRequest(f, "unknown-wrong-error-code"); + assert.equal(f.continuation.recoverAfterRestart(), 1); + + const database = openDatabase(f.root); + try { + database.sqlite.prepare( + "update durable_operations set error_code='WRONG_CODE' where operation_id=?", + ).run(created.request.id); + } finally { + database.close(); + } + assert.throws( - () => continuation.getLatestForTarget(swarm.id, targetB), + () => f.continuation.getRequest(created.request.id), (error: unknown) => error instanceof ChatSwarmError && error.code === "INVALID_STATE", ); - assert.equal(swarmStore.getWorker(worker.id)?.continuationEpoch, 0); } finally { - continuation.close(); - swarmStore.close(); - rmSync(root, { recursive: true, force: true }); + cleanup(f); } }); From 9dc6114285b1d3acdfc85deb5a312aee3d1bc861 Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 15:28:00 +0800 Subject: [PATCH 53/55] fix(chat-swarm): bind absolute continuation expiry --- src/chat-swarm-continuation-store.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/chat-swarm-continuation-store.ts b/src/chat-swarm-continuation-store.ts index 6a6244b89..95709cf10 100644 --- a/src/chat-swarm-continuation-store.ts +++ b/src/chat-swarm-continuation-store.ts @@ -145,17 +145,6 @@ export class ChatSwarmContinuationStore { ); this.assertTargetCarrierAvailable(authenticatedTargetCarrierFingerprint, input.workerId); this.assertTargetCarrierNotRetired(authenticatedTargetCarrierFingerprint, input.workerId); - const requestHash = createHash("sha256").update(JSON.stringify({ - attemptKey: input.attemptKey, - checkpointHash: material.checkpointHash, - sourceCarrierFingerprint: material.sourceCarrierFingerprint, - sourceEpoch: material.sourceEpoch, - swarmId: input.swarmId, - targetCarrierFingerprint: material.targetCarrierFingerprint, - targetEpoch: material.targetEpoch, - ttlSeconds, - workerId: input.workerId, - })).digest("hex"); const now = this.nowIso(); this.persistExpiredPendingForWorker(input.workerId, now); @@ -182,6 +171,7 @@ export class ChatSwarmContinuationStore { requestedAt, expiresAt, }; + const requestHash = continuationMaterialHash(request); const operationId = newId("continuation"); this.database.sqlite.prepare(` insert into durable_operations ( @@ -611,6 +601,9 @@ export class ChatSwarmContinuationStore { if (row.scope_root !== this.scopeRoot) { throw new ChatSwarmError("INVALID_STATE", "durable continuation scope root mismatch"); } + if (row.created_at !== persisted.requestedAt) { + throw new ChatSwarmError("INVALID_STATE", "persisted continuation creation timestamp mismatch"); + } if (stableAttemptKey(persisted.swarmId, persisted.workerId, persisted.attemptKey) !== row.attempt_key) { throw new ChatSwarmError("INVALID_STATE", "persisted continuation attempt identity mismatch"); } @@ -700,6 +693,8 @@ function continuationMaterialHash(request: PersistedRequest): string { return createHash("sha256").update(JSON.stringify({ attemptKey: request.attemptKey, checkpointHash: request.checkpointHash, + expiresAt: request.expiresAt, + requestedAt: request.requestedAt, sourceCarrierFingerprint: request.sourceCarrierFingerprint, sourceEpoch: request.sourceEpoch, swarmId: request.swarmId, From 0a35df839518026d8d661970adb16074de4da44f Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 15:28:30 +0800 Subject: [PATCH 54/55] test(chat-swarm): bind absolute continuation expiry --- ...warm-continuation-corruption-fence.test.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/chat-swarm-continuation-corruption-fence.test.ts b/src/chat-swarm-continuation-corruption-fence.test.ts index 13f89ed5a..7af1f1e57 100644 --- a/src/chat-swarm-continuation-corruption-fence.test.ts +++ b/src/chat-swarm-continuation-corruption-fence.test.ts @@ -95,6 +95,36 @@ test("corrupt pending continuation identity fences all new continuation work", ( } }); +test("shifted absolute expiry with unchanged TTL fails closed", () => { + const f = fixture(); + try { + const created = createRequest(f, "shifted-expiry"); + const database = openDatabase(f.root); + try { + const row = database.sqlite.prepare( + "select request_json from durable_operations where operation_id=?", + ).get(created.request.id) as { request_json: string }; + const request = JSON.parse(row.request_json) as Record; + const requestedAt = Date.parse(String(request.requestedAt)); + const expiresAt = Date.parse(String(request.expiresAt)); + request.requestedAt = new Date(requestedAt + 60_000).toISOString(); + request.expiresAt = new Date(expiresAt + 60_000).toISOString(); + database.sqlite.prepare( + "update durable_operations set request_json=? where operation_id=?", + ).run(JSON.stringify(request), created.request.id); + } finally { + database.close(); + } + + assert.throws( + () => f.continuation.getRequest(created.request.id), + (error: unknown) => error instanceof ChatSwarmError && error.code === "INVALID_STATE", + ); + } finally { + cleanup(f); + } +}); + test("succeeded continuation without approval receipt fails closed", () => { const f = fixture(); try { From a168bb39ea55a868f40c15e85f91fb1a59c0d8ce Mon Sep 17 00:00:00 2001 From: James3014 Date: Sun, 13 Sep 2026 15:29:42 +0800 Subject: [PATCH 55/55] ci(chat-swarm): verify pull request diff --- .github/workflows/ci.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f73813b3f..89a33de08 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,21 @@ concurrency: cancel-in-progress: true jobs: + diff-check: + name: Diff check (PR) + if: github.event_name == 'pull_request' + runs-on: macos-latest + timeout-minutes: 5 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Git diff check + run: git diff --check "${{ github.event.pull_request.base.sha }}...HEAD" + smoke: name: Smoke (${{ matrix.os }}) runs-on: ${{ matrix.os }} @@ -122,4 +137,4 @@ jobs: run: npm ci - name: Test local agent sessions - run: npm run test:local-agent-sessions \ No newline at end of file + run: npm run test:local-agent-sessions