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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/core-mutation-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,14 +524,14 @@ export function registerCoreMutationSessionTools(
},
);

if (options.recoveryOwnerClientId && inspectWriterDomain) {
if (inspectWriterDomain) {
registerAppTool(
server,
"core_mutation_session_recover_orphaned_process",
{
title: "Recover orphaned Core PROCESS writer",
description:
"Owner-only administrative recovery for one exact ACTIVE Core session whose original caller identity is unavailable and whose PROCESS writer is OUTCOME_UNKNOWN. Revalidates exact physical Git evidence, refuses live writers, clears only the PROCESS writer pin, preserves the original actor/session/worktree, and grants no retry, Candidate, mutation, completion, integration, merge, or release authority.",
"Owner-only administrative recovery for one exact ACTIVE Core session whose original caller identity is unavailable and whose PROCESS writer is OUTCOME_UNKNOWN. The tool remains projected even when owner recovery is disabled so the MCP catalog/schema stays stable; invocation fails closed unless the exact owner client is configured and authenticated. Revalidates exact physical Git evidence, refuses live writers, clears only the PROCESS writer pin, preserves the original actor/session/worktree, and grants no retry, Candidate, mutation, completion, integration, merge, or release authority.",
inputSchema: {
workspaceId: z.string(),
sessionId: z.string(),
Expand Down
12 changes: 10 additions & 2 deletions src/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1974,15 +1974,23 @@ test("direct_candidate_execution_evidence tool produces valid evidence through h
assert.doesNotThrow(() => validateDirectCandidateExecutionEvidence(content));
});

test("Core orphan PROCESS recovery tool is opt-in and exact-owner-client fenced", async (t) => {
test("Core orphan PROCESS recovery tool keeps a stable catalog and exact-owner-client fence", async (t) => {
const disabled = await fixture(t, { coreMutation: true });
const disabledTools = await disabled.client.listTools();
assert.equal(disabledTools.tools.some((tool) => tool.name === "core_mutation_session_recover_orphaned_process"), false);
assert.equal(disabledTools.tools.some((tool) => tool.name === "core_mutation_session_recover_orphaned_process"), true);
assert.throws(
() => assertCoreMutationRecoveryOwnerClient({}, undefined),
/CORE_MUTATION_RECOVERY_DISABLED/,
);

const ownerClientId = "devspace-core-recovery-owner";
const enabled = await fixture(t, { coreMutation: true, coreMutationRecoveryOwnerClientId: ownerClientId });
const enabledTools = await enabled.client.listTools();
assert.equal(enabledTools.tools.some((tool) => tool.name === "core_mutation_session_recover_orphaned_process"), true);
assert.deepEqual(
disabledTools.tools.map((tool) => tool.name).sort(),
enabledTools.tools.map((tool) => tool.name).sort(),
);

assert.throws(
() => assertCoreMutationRecoveryOwnerClient({}, ownerClientId),
Expand Down
Loading