Skip to content
This repository was archived by the owner on Oct 23, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"test:node-pty-permissions": "node --import tsx --test src/node-pty-postinstall.test.ts",
"test:physical-host-registry": "tsx src/physical-host-registry.test.ts",
"start": "node dist/cli.js serve",
"test": "node scripts/check-test-chain.mjs && tsx src/local-agent-path.test.ts && npm run test:node-pty-permissions && tsx src/development-boundaries.test.ts && npm run test:physical-host-registry && 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/capability-generation-convergence.test.ts && tsx src/control-plane-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/chat-swarm-runtime-tools.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/tool-projection-recovery.test.ts && tsx src/host-storage-retention.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/local-agent-process-tree.test.ts && tsx src/mcp-sessions.test.ts && tsx src/cutover-state.test.ts && tsx src/cutover-activation.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/herdr-socket-path.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-activation.test.ts && tsx src/host-operation-policy.test.ts && tsx src/host-operations.test.ts && tsx src/host-operations-http.test.ts && tsx src/host-operation-external.test.ts && node --import tsx --test src/issue-413-wave1.test.ts && node --import tsx --test src/work-resume-integration.test.ts && tsx src/batch-autonomy-grant.test.ts && node --import tsx --test src/db/migrations.test.ts && node --import tsx --test src/host-capability-snapshot.test.ts && node --import tsx --test src/pi-tools.test.ts && node --import tsx --test src/work-resume.test.ts",
"test": "node scripts/check-test-chain.mjs && tsx src/local-agent-path.test.ts && npm run test:node-pty-permissions && tsx src/development-boundaries.test.ts && npm run test:physical-host-registry && 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/capability-generation-convergence.test.ts && tsx src/control-plane-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/chat-swarm-runtime-tools.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/tool-projection-recovery.test.ts && tsx src/host-storage-retention.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/local-agent-process-tree.test.ts && tsx src/mcp-sessions.test.ts && tsx src/cutover-state.test.ts && tsx src/cutover-activation.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 && node --import tsx --test src/cutover-progression.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/herdr-socket-path.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-activation.test.ts && tsx src/host-operation-policy.test.ts && tsx src/host-operations.test.ts && tsx src/host-operations-http.test.ts && tsx src/host-operation-external.test.ts && node --import tsx --test src/issue-413-wave1.test.ts && node --import tsx --test src/work-resume-integration.test.ts && tsx src/batch-autonomy-grant.test.ts && node --import tsx --test src/db/migrations.test.ts && node --import tsx --test src/host-capability-snapshot.test.ts && node --import tsx --test src/pi-tools.test.ts && node --import tsx --test src/work-resume.test.ts && node --test scripts/ops/m5-cutover/m5-cutover.test.mjs",
"verify:nexus-core": "node scripts/nexus-core-verify.mjs",
"typecheck": "tsc -p tsconfig.json --noEmit",
"test:carrier-binding": "tsx src/carrier-binding.test.ts",
Expand Down Expand Up @@ -87,5 +87,5 @@
"optionalDependencies": {
"node-pty": "1.1.0"
},
"//devspace-ds-1": "TODO(DS-1): test files excluded from npm test (src/cli.test.ts, src/server.test.ts via cli-ci/server-ci; src/cutover-progression.test.ts, src/issue344-minimal-direct-coding.test.ts, src/local-agent-herdr.test.ts, src/local-agent-p2-telemetry.test.ts) are listed with reasons in scripts/check-test-chain.mjs. Failure summaries: /home/claude/issues/devspace/DS-1-failures.md. Tracking GitHub issue IDs: TBD (not created from this session)."
"//devspace-ds-1": "TODO(DS-1): test files excluded from npm test (src/cli.test.ts, src/server.test.ts via cli-ci/server-ci; src/issue344-minimal-direct-coding.test.ts, src/local-agent-herdr.test.ts, src/local-agent-p2-telemetry.test.ts) are listed with reasons in scripts/check-test-chain.mjs. Failure summaries: /home/claude/issues/devspace/DS-1-failures.md. Tracking GitHub issue IDs: TBD (not created from this session)."
}
1 change: 0 additions & 1 deletion scripts/check-test-chain.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ const pkg = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8"));
// Details: /home/claude/issues/devspace/DS-1-failures.md
const EXCLUDED = new Map([
["src/cli.test.ts", "TODO(DS-1-F1): fails in npm test; see DS-1-failures.md"],
["src/cutover-progression.test.ts", "TODO(DS-1-F2): fails in npm test; see DS-1-failures.md"],
["src/issue344-minimal-direct-coding.test.ts", "TODO(DS-1-F3): needs DEVSPACE_OAUTH_OWNER_TOKEN; see DS-1-failures.md"],
["src/local-agent-herdr.test.ts", "TODO(DS-1-F4): live canary with hard-coded /Users/james paths; see DS-1-failures.md"],
["src/local-agent-p2-telemetry.test.ts", "TODO(DS-1-F5): requires agy CLI; see DS-1-failures.md"],
Expand Down
7 changes: 5 additions & 2 deletions scripts/ops/m5-cutover/m5-cutover.test.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
Expand All @@ -25,10 +26,12 @@ function record(overrides = {}) {
const select = (file, liveIsOld, liveIsTarget) => selectMode({ file, contract, liveIsOld, liveIsTarget });

test("sidecar without env exits 1 with a usage error and no side effects", () => {
const env = { PATH: process.env.PATH };
// HOME is set explicitly: CI containers may run as a uid with no passwd entry, where os.homedir() throws.
const env = { PATH: process.env.PATH, HOME: tmpdir() };
const result = spawnSync(process.execPath, [join(here, "m5-cutover-sidecar.mjs")], { env, encoding: "utf8" });
assert.equal(result.status, 1);
const failure = JSON.parse(result.stderr);
let failure;
try { failure = JSON.parse(result.stderr); } catch { assert.fail(`sidecar stderr is not the JSON usage failure:\n${result.stderr}`); }
assert.equal(failure.ok, false);
assert.match(failure.error, /PENDING_ID, CONTRACT_PATH, RECEIPT_PATH and ACTIVE_RELEASE_ROOT are all required/);
});
Expand Down
47 changes: 43 additions & 4 deletions src/cutover-progression.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import {
CUTOVER_ACTIVATION_BINDING_SCHEMA,
CutoverStateStore,
type CutoverServerIdentity,
type ExpectedCutoverIdentity,
Expand Down Expand Up @@ -74,6 +75,28 @@ function createMockActuator(fail = false): SelfRestartActuator & { scheduledCoun
return actuator;
}

// Orchestrated restart is fenced on an exact activation binding (#377). The
// fixture records one after the restart decision so the original gates under
// test (build-ready, actuator) are reached instead of the earlier fence.
const BOUND_RELEASE_SHA256 = "e".repeat(64);
const boundReleasePath = (stateDir: string): string => join(stateDir, "releases", "release-target");

function bindActivation(stateDir: string, controller: McpCutoverController): void {
const record = controller.record();
assert.ok(record);
const cutoverId = record.cutoverId;
new CutoverStateStore(stateDir).recordActivationBinding(cutoverId, {
schema: CUTOVER_ACTIVATION_BINDING_SCHEMA,
cutoverId,
sourceCommit: record.expectedNewIdentity.sourceCommit,
buildId: record.expectedNewIdentity.buildId,
releaseSha256: BOUND_RELEASE_SHA256,
releasePath: boundReleasePath(stateDir),
pointerPath: join(stateDir, "current-release.json"),
boundAt: new Date().toISOString(),
});
}

function createOrchestratorFixture(stateDir: string, options?: {
actuatorFail?: boolean;
probe?: (expected: ExpectedCutoverIdentity) => BuildReadyProbeResult;
Expand Down Expand Up @@ -101,6 +124,17 @@ function createOrchestratorFixture(stateDir: string, options?: {
oldOrch: makeOrch(oldController),
replacementOrch: makeOrch(replacementController),
mismatchedOrch: makeOrch(mismatchedController),
// Successor that reports the activation it was started under (health
// build.activation_cutover_id), as the real replacement process does.
activatedReplacementFor: (cutoverId: string) => {
const controller = new McpCutoverController(store, {
...replacementIdentity,
releaseSha256: BOUND_RELEASE_SHA256,
releasePath: boundReleasePath(stateDir),
activationCutoverId: cutoverId,
});
return { controller, orch: makeOrch(controller) };
},
};
}

Expand Down Expand Up @@ -155,6 +189,7 @@ test("devspace#263 G4: end-to-end self-cutover progression canary (advance-drive
verifiedBy: "build-pipeline",
verifiedAt: new Date().toISOString(),
});
bindActivation(stateDir, f.oldController);

// Step 6: cutover_advance marks restart and invokes actuator -> ADVANCED / AWAITING_RECONNECT
const step6 = await f.oldOrch.advance();
Expand All @@ -170,17 +205,18 @@ test("devspace#263 G4: end-to-end self-cutover progression canary (advance-drive
assert.equal(f.actuator.scheduledCount, 1, "must never schedule duplicate restart");

// Step 8: Client reconnects to replacement instance (reconcile-only mode)
assert.equal(f.replacementController.mode(), "reconcile-only");
const step8 = await f.replacementOrch.advance({
const successor = f.activatedReplacementFor(started.cutoverId);
assert.equal(successor.controller.mode(), "reconcile-only");
const step8 = await successor.orch.advance({
callerContinuity: { caller: "reconnected-agent" },
});
assert.equal(step8.status, "TERMINAL");
assert.equal(step8.cutover_phase, "closed");
assert.equal(step8.reconciliation_required, false);
assert.equal(f.replacementController.mode(), "normal");
assert.equal(successor.controller.mode(), "normal");

// Step 9: Post-close advance returns TERMINAL
const final = await f.replacementOrch.advance();
const final = await successor.orch.advance();
assert.equal(final.status, "TERMINAL");
} finally {
rmSync(stateDir, { recursive: true, force: true });
Expand Down Expand Up @@ -278,6 +314,7 @@ test("devspace#263 G3: build-ready missing or probe failure blocks with BUILD_NO
verifiedBy: "attester",
verifiedAt: new Date().toISOString(),
});
bindActivation(stateDir, f.oldController);
const probeFailed = await f.oldOrch.advance();
assert.equal(probeFailed.status, "BLOCKED");
assert.equal(probeFailed.blocker, "BUILD_NOT_READY");
Expand All @@ -298,6 +335,7 @@ test("devspace#263 G3: replacement identity mismatch fails closed with RECONCILI
verifiedBy: "attester",
verifiedAt: new Date().toISOString(),
});
bindActivation(stateDir, f.oldController);
await f.oldOrch.advance();

// Replacement boots with unexpected source/build
Expand All @@ -320,6 +358,7 @@ test("devspace#263 G3: actuator failure is durable and never retried blindly", a
verifiedBy: "attester",
verifiedAt: new Date().toISOString(),
});
bindActivation(stateDir, f.oldController);

const failed = await f.oldOrch.advance();
assert.equal(failed.status, "BLOCKED");
Expand Down
Loading