Files
ECC/tests/lib/multi-harness-setup.test.js
T
28e53a0bc1 feat(install): add guided multi-harness installer (#2649)
* feat(install): add guided Claude plugin setup

* fix: support Claude command shims on Windows

* feat: support safe Claude plugin scope migration

* fix(install): preserve interactive setup terminal

* fix(install): auto-migrate setup scope changes

* feat(install): add guided multi-harness installer

* fix(install): sync Yarn binary metadata

* fix(install): handle wizard EOF on Node 18

* ci: allow installer matrix tests to finish

* test(install): allow slower PowerShell delegation

* fix(install): harden guided provider reconciliation

* test(install): harden packaged and local compatibility

* chore: prepare guided installer release 2.2.0

* fix(install): report refreshed Codex marketplace state

* fix(install): verify managed content provenance

* test(install): allow empty Yarn smoke fixture

* test(install): invoke Windows package shims safely

* fix(install): close cross-platform release gaps

* fix(install): require trusted GitHub origins

* fix(install): preserve hook profile precedence

* refactor(install): centralize trusted GitHub origins

* ci: retrigger workflow run after merge of main

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 15:39:49 -04:00

646 lines
23 KiB
JavaScript

'use strict';
const assert = require('assert');
const crypto = require('crypto');
const fs = require('fs');
const os = require('os');
const path = require('path');
const {
applyMultiHarnessPlan,
createMultiHarnessPlan,
normalizeGuidedInstallRequest,
preflightManagedPlan,
} = require('../../scripts/lib/multi-harness-setup');
const { createInstallState } = require('../../scripts/lib/install-state');
let passed = 0;
let failed = 0;
async function test(name, fn) {
try {
await fn();
console.log(` ✓ ${name}`);
passed += 1;
} catch (error) {
console.log(` ✗ ${name}`);
console.log(` Error: ${error.message}`);
failed += 1;
}
}
function tempDir(prefix) {
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
}
function writeFile(filePath, content) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, content, 'utf8');
}
function sha256(content) {
return crypto.createHash('sha256').update(content).digest('hex');
}
function stateOperation(destinationPath, overrides = {}) {
return {
kind: 'copy-file',
moduleId: 'core',
sourceRelativePath: 'rules/security.md',
destinationPath,
strategy: 'preserve-relative-path',
ownership: 'managed',
scaffoldOnly: false,
...overrides,
};
}
function stateOperationFrom(operation) {
return stateOperation(operation.destinationPath, {
kind: operation.kind,
moduleId: operation.moduleId || 'core',
sourceRelativePath: operation.sourceRelativePath || 'rules/security.md',
strategy: operation.strategy || (operation.kind === 'merge-json' ? 'merge-json' : 'preserve-relative-path'),
ownership: operation.ownership || 'managed',
scaffoldOnly: Boolean(operation.scaffoldOnly),
});
}
function managedPlan(root, operations, owned = []) {
const installStatePath = path.join(root, '.kimi-code', 'ecc-install-state.json');
const plan = {
adapter: { id: 'kimi-project', target: 'kimi', kind: 'project' },
installStatePath,
operations,
target: 'kimi',
targetRoot: root,
};
plan.statePreview = createInstallState({
adapter: plan.adapter,
installStatePath,
operations: operations.map(stateOperationFrom),
request: {},
resolution: {},
source: { manifestVersion: 1 },
targetRoot: root,
});
if (owned.length > 0) {
writeManagedState(plan, {
operations: owned.map(destinationPath => stateOperation(destinationPath, {
contentSha256: sha256(fs.readFileSync(destinationPath)),
})),
});
}
return plan;
}
function writeManagedState(plan, overrides = {}) {
const state = createInstallState({
adapter: plan.adapter,
installStatePath: plan.installStatePath,
operations: [],
request: {},
resolution: {},
source: { manifestVersion: 1 },
targetRoot: plan.targetRoot,
});
const nextState = {
...state,
...overrides,
target: { ...state.target, ...(overrides.target || {}) },
operations: overrides.operations || state.operations,
};
writeFile(plan.installStatePath, `${JSON.stringify(nextState, null, 2)}\n`);
return nextState;
}
(async () => {
console.log('\n=== Multi-harness guided setup tests ===\n');
await test('normalizes provider-specific options without inventing shared semantics', () => {
assert.deepStrictEqual(normalizeGuidedInstallRequest({
harnesses: ['kimi', 'claude', 'kimi'],
claudeHooks: 'minimal',
claudeScope: 'local',
profile: 'developer',
}), {
harnesses: ['claude', 'kimi'],
claudeHooks: 'minimal',
claudeScope: 'local',
dryRun: false,
json: false,
profile: 'developer',
yes: false,
});
assert.throws(
() => normalizeGuidedInstallRequest({ harnesses: ['kimi'], claudeScope: 'user' }),
/Claude.*selected/i
);
assert.throws(
() => normalizeGuidedInstallRequest({ harnesses: ['codex'], profile: 'core' }),
/Kimi.*selected/i
);
});
await test('classifies missing, identical, managed, and JSON merge destinations', () => {
const root = tempDir('ecc-guided-preflight-');
try {
const sourceSame = path.join(root, 'sources', 'same.md');
const sourceManaged = path.join(root, 'sources', 'managed.md');
const destinationSame = path.join(root, 'same.md');
const destinationManaged = path.join(root, 'managed.md');
const destinationJson = path.join(root, 'mcp.json');
writeFile(sourceSame, 'same\n');
writeFile(sourceManaged, 'new\n');
writeFile(destinationSame, 'same\n');
writeFile(destinationManaged, 'old\n');
writeFile(destinationJson, '{"other":true}\n');
const plan = managedPlan(root, [
{ kind: 'copy-file', sourcePath: sourceSame, destinationPath: destinationSame },
stateOperation(destinationManaged, { sourcePath: sourceManaged }),
{ kind: 'merge-json', destinationPath: destinationJson, mergePayload: { ecc: true } },
{ kind: 'copy-file', sourcePath: sourceSame, destinationPath: path.join(root, 'new.md') },
], [destinationManaged]);
const result = preflightManagedPlan(plan);
assert.deepStrictEqual(result.operations.map(item => item.classification), [
'identical',
'managed-update',
'json-merge',
'create',
]);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
await test('rejects valid install-state from a different managed target identity', () => {
const root = tempDir('ecc-guided-forged-target-');
try {
const source = path.join(root, 'source.md');
const destination = path.join(root, 'AGENTS.md');
writeFile(source, 'ecc\n');
writeFile(destination, 'user\n');
const plan = managedPlan(root, [
stateOperation(destination, { sourcePath: source }),
]);
writeManagedState(plan, {
target: { id: 'cursor-project', target: 'cursor' },
operations: [stateOperation(destination)],
});
assert.throws(
() => preflightManagedPlan(plan),
/install-state.*target identity|does not belong.*Kimi/i
);
assert.strictEqual(fs.readFileSync(destination, 'utf8'), 'user\n');
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
await test('rejects install-state with mismatched canonical root or state path', () => {
const root = tempDir('ecc-guided-forged-paths-');
const otherRoot = tempDir('ecc-guided-forged-other-');
try {
const source = path.join(root, 'source.md');
const destination = path.join(root, 'AGENTS.md');
writeFile(source, 'ecc\n');
writeFile(destination, 'user\n');
const plan = managedPlan(root, [
stateOperation(destination, { sourcePath: source }),
]);
for (const target of [
{ root: otherRoot },
{ installStatePath: path.join(otherRoot, 'ecc-install-state.json') },
]) {
writeManagedState(plan, {
target,
operations: [stateOperation(destination)],
});
assert.throws(
() => preflightManagedPlan(plan),
/install-state.*(root|path).*does not match/i
);
}
} finally {
fs.rmSync(root, { recursive: true, force: true });
fs.rmSync(otherRoot, { recursive: true, force: true });
}
});
await test('rejects install-state ownership claims outside the canonical target root', () => {
const root = tempDir('ecc-guided-forged-containment-');
const outside = tempDir('ecc-guided-forged-outside-');
try {
const source = path.join(root, 'source.md');
const destination = path.join(root, 'AGENTS.md');
writeFile(source, 'ecc\n');
writeFile(destination, 'user\n');
const plan = managedPlan(root, [
stateOperation(destination, { sourcePath: source }),
]);
writeManagedState(plan, {
operations: [stateOperation(path.join(outside, 'AGENTS.md'))],
});
assert.throws(
() => preflightManagedPlan(plan),
/install-state.*outside|outside the install root/i
);
} finally {
fs.rmSync(root, { recursive: true, force: true });
fs.rmSync(outside, { recursive: true, force: true });
}
});
await test('refuses an unowned differing managed-target file', () => {
const root = tempDir('ecc-guided-collision-');
try {
const source = path.join(root, 'source.md');
const destination = path.join(root, 'AGENTS.md');
writeFile(source, 'ecc\n');
writeFile(destination, 'user\n');
assert.throws(
() => preflightManagedPlan(managedPlan(root, [
{ kind: 'copy-file', sourcePath: source, destinationPath: destination },
])),
/unowned.*AGENTS\.md/i
);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
await test('same-target state without a content digest cannot claim a user file', () => {
const root = tempDir('ecc-guided-forged-same-target-');
try {
const source = path.join(root, 'source.md');
const destination = path.join(root, 'AGENTS.md');
writeFile(source, 'ecc\n');
writeFile(destination, 'user\n');
const operation = stateOperation(destination, { sourcePath: source });
const plan = managedPlan(root, [operation]);
writeManagedState(plan, { operations: [stateOperation(destination)] });
assert.throws(
() => preflightManagedPlan(plan),
/unverified ownership|content digest|unowned existing file/i
);
assert.strictEqual(fs.readFileSync(destination, 'utf8'), 'user\n');
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
await test('managed ownership requires an exact operation identity and content digest', () => {
const root = tempDir('ecc-guided-managed-digest-');
try {
const source = path.join(root, 'source.md');
const destination = path.join(root, 'AGENTS.md');
writeFile(source, 'new ecc\n');
writeFile(destination, 'old ecc\n');
const operation = stateOperation(destination, { sourcePath: source });
const plan = managedPlan(root, [operation]);
writeManagedState(plan, {
operations: [stateOperation(destination, {
contentSha256: sha256('old ecc\n'),
})],
});
assert.strictEqual(
preflightManagedPlan(plan).operations[0].classification,
'managed-update'
);
writeManagedState(plan, {
operations: [stateOperation(destination, {
contentSha256: sha256('old ecc\n'),
sourceRelativePath: 'rules/other.md',
})],
});
assert.throws(
() => preflightManagedPlan(plan),
/operation identity|unverified ownership|unowned existing file/i
);
writeManagedState(plan, {
operations: [stateOperation(destination, {
contentSha256: sha256('different bytes\n'),
})],
});
assert.throws(
() => preflightManagedPlan(plan),
/content digest|unverified ownership|unowned existing file/i
);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
await test('refuses a conflicting key in an unowned JSON merge destination', () => {
const root = tempDir('ecc-guided-json-collision-');
try {
const destination = path.join(root, 'mcp.json');
writeFile(destination, JSON.stringify({
mcpServers: { github: { command: 'user-owned-server' } },
}));
assert.throws(
() => preflightManagedPlan(managedPlan(root, [
{
kind: 'merge-json',
destinationPath: destination,
mergePayload: { mcpServers: { github: { command: 'ecc-server' } } },
},
])),
/unowned JSON.*mcpServers\.github\.command/i
);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
await test('rejects symlinked managed ancestors during batch preflight', () => {
const root = tempDir('ecc-guided-symlink-root-');
const outside = tempDir('ecc-guided-symlink-outside-');
try {
const source = path.join(root, 'source.md');
const linkedDirectory = path.join(root, 'rules');
writeFile(source, 'ecc\n');
fs.symlinkSync(outside, linkedDirectory, process.platform === 'win32' ? 'junction' : 'dir');
assert.throws(
() => preflightManagedPlan(managedPlan(root, [
{
kind: 'copy-file',
sourcePath: source,
destinationPath: path.join(linkedDirectory, 'security.md'),
},
])),
/outside the install root|symlinked path/i
);
assert.strictEqual(fs.existsSync(path.join(outside, 'security.md')), false);
} finally {
fs.rmSync(root, { recursive: true, force: true });
fs.rmSync(outside, { recursive: true, force: true });
}
});
await test('rejects an unwritable Kimi destination during preflight', () => {
const root = tempDir('ecc-guided-unwritable-');
try {
const destination = path.join(root, '.kimi-code', 'rules', 'security.md');
const accessChecks = [];
const accessError = new Error('permission denied');
accessError.code = 'EACCES';
assert.throws(
() => preflightManagedPlan(managedPlan(root, [
{ kind: 'copy-file', destinationPath: destination },
]), {
accessSync(candidatePath, mode) {
accessChecks.push({ candidatePath, mode });
throw accessError;
},
}),
error => (
/Kimi destination is not writable by the current user/i.test(error.message)
&& error.message.includes(root)
)
);
assert.deepStrictEqual(accessChecks, [{
candidatePath: root,
mode: fs.constants.W_OK | fs.constants.X_OK,
}]);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
await test('real filesystem preflight rejects an unwritable project root', () => {
if (process.platform === 'win32' || (typeof process.getuid === 'function' && process.getuid() === 0)) {
return;
}
const root = tempDir('ecc-guided-real-permissions-');
const projectRoot = path.join(root, 'project');
fs.mkdirSync(projectRoot, { mode: 0o755 });
try {
fs.chmodSync(projectRoot, 0o555);
assert.throws(
() => preflightManagedPlan(managedPlan(projectRoot, [
{
kind: 'copy-file',
destinationPath: path.join(projectRoot, '.kimi-code', 'rules', 'security.md'),
},
])),
/Kimi destination is not writable by the current user/i
);
assert.strictEqual(fs.existsSync(path.join(projectRoot, '.kimi-code')), false);
} finally {
fs.chmodSync(projectRoot, 0o755);
fs.rmSync(root, { recursive: true, force: true });
}
});
await test('preflights every selected harness before applying any mutation', async () => {
const events = [];
const request = normalizeGuidedInstallRequest({
harnesses: ['claude', 'codex', 'kimi'],
claudeHooks: 'standard',
claudeScope: 'user',
profile: 'core',
});
await assert.rejects(
() => createMultiHarnessPlan(request, {
previewClaude: async () => events.push('preview:claude'),
previewCodex: async () => events.push('preview:codex'),
createManagedPlan: async () => ({ target: 'kimi' }),
preflightManaged: async () => {
events.push('preview:kimi');
throw new Error('unowned collision');
},
}),
/collision/
);
assert.deepStrictEqual(events, ['preview:claude', 'preview:codex', 'preview:kimi']);
});
await test('refuses a copy-file destination created after preview but before apply', async () => {
const root = tempDir('ecc-guided-late-copy-collision-');
try {
const source = path.join(root, 'source.md');
const destination = path.join(root, '.kimi-code', 'rules', 'security.md');
writeFile(source, 'ecc\n');
const plan = managedPlan(root, [stateOperation(destination, { sourcePath: source })]);
const preview = preflightManagedPlan(plan);
const result = await applyMultiHarnessPlan({
harnesses: [{ id: 'kimi', preview }],
request: { harnesses: ['kimi'] },
}, {
preflightManaged(candidatePlan) {
const latestPreview = preflightManagedPlan(candidatePlan);
writeFile(destination, 'user\n');
return latestPreview;
},
});
assert.strictEqual(result.status, 'failed');
assert.match(result.failure.message, /unowned existing file/i);
assert.deepStrictEqual(result.retryHarnesses, ['kimi']);
assert.strictEqual(fs.readFileSync(destination, 'utf8'), 'user\n');
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
await test('refuses a late unowned copy even when its bytes match the ECC source', async () => {
const root = tempDir('ecc-guided-late-identical-copy-');
try {
const source = path.join(root, 'source.md');
const destination = path.join(root, '.kimi-code', 'rules', 'security.md');
writeFile(source, 'ecc\n');
const plan = managedPlan(root, [stateOperation(destination, { sourcePath: source })]);
const preview = preflightManagedPlan(plan);
const result = await applyMultiHarnessPlan({
harnesses: [{ id: 'kimi', preview }],
request: { harnesses: ['kimi'] },
}, {
preflightManaged(candidatePlan) {
const latestPreview = preflightManagedPlan(candidatePlan);
writeFile(destination, 'ecc\n');
return latestPreview;
},
});
assert.strictEqual(result.status, 'failed');
assert.match(result.failure.message, /destination changed after Kimi preflight/i);
assert.deepStrictEqual(result.retryHarnesses, ['kimi']);
assert.strictEqual(fs.readFileSync(destination, 'utf8'), 'ecc\n');
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
await test('refuses conflicting JSON created after preview but before apply', async () => {
const root = tempDir('ecc-guided-late-json-collision-');
try {
const destination = path.join(root, '.kimi-code', 'mcp.json');
const operation = stateOperation(destination, {
kind: 'merge-json',
mergePayload: { mcpServers: { github: { command: 'ecc-server' } } },
sourceRelativePath: '.mcp.json',
strategy: 'merge-json',
});
const plan = managedPlan(root, [operation]);
const preview = preflightManagedPlan(plan);
const result = await applyMultiHarnessPlan({
harnesses: [{ id: 'kimi', preview }],
request: { harnesses: ['kimi'] },
}, {
preflightManaged(candidatePlan) {
const latestPreview = preflightManagedPlan(candidatePlan);
writeFile(destination, JSON.stringify({
mcpServers: { github: { command: 'user-server' } },
}));
return latestPreview;
},
});
assert.strictEqual(result.status, 'failed');
assert.match(result.failure.message, /unowned JSON.*mcpServers\.github\.command/i);
assert.deepStrictEqual(result.retryHarnesses, ['kimi']);
assert.deepStrictEqual(JSON.parse(fs.readFileSync(destination, 'utf8')), {
mcpServers: { github: { command: 'user-server' } },
});
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
await test('refuses an install-state file created after preview instead of overwriting it', async () => {
const root = tempDir('ecc-guided-late-state-collision-');
try {
const source = path.join(root, 'source.md');
const destination = path.join(root, '.kimi-code', 'rules', 'security.md');
writeFile(source, 'ecc\n');
const plan = managedPlan(root, [stateOperation(destination, { sourcePath: source })]);
const preview = preflightManagedPlan(plan);
const unexpectedState = '{"user":"owned"}\n';
const result = await applyMultiHarnessPlan({
harnesses: [{ id: 'kimi', preview }],
request: { harnesses: ['kimi'] },
}, {
preflightManaged(candidatePlan) {
const latestPreview = preflightManagedPlan(candidatePlan);
writeFile(plan.installStatePath, unexpectedState);
return latestPreview;
},
});
assert.strictEqual(result.status, 'failed');
assert.match(result.failure.message, /unowned or changed install-state/i);
assert.deepStrictEqual(result.retryHarnesses, ['kimi']);
assert.strictEqual(fs.existsSync(destination), false);
assert.strictEqual(fs.readFileSync(plan.installStatePath, 'utf8'), unexpectedState);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
await test('applies in catalog order and reports partial completion with an exact retry set', async () => {
const plan = {
harnesses: [
{ id: 'claude', preview: {} },
{ id: 'codex', preview: {} },
{ id: 'kimi', preview: {} },
],
request: { harnesses: ['claude', 'codex', 'kimi'] },
};
const events = [];
const result = await applyMultiHarnessPlan(plan, {
applyClaude: async () => { events.push('claude'); return { action: 'installed' }; },
applyCodex: async () => { events.push('codex'); throw new Error('verification failed'); },
applyManaged: async () => { events.push('kimi'); return { applied: true }; },
});
assert.deepStrictEqual(events, ['claude', 'codex']);
assert.strictEqual(result.status, 'partial');
assert.deepStrictEqual(result.completed.map(item => item.id), ['claude']);
assert.strictEqual(result.failure.id, 'codex');
assert.deepStrictEqual(result.retryHarnesses, ['codex', 'kimi']);
});
await test('a late Kimi permission failure retries only Kimi', async () => {
const plan = {
harnesses: [
{ id: 'claude', preview: {} },
{ id: 'codex', preview: {} },
{ id: 'kimi', preview: {} },
],
request: { harnesses: ['claude', 'codex', 'kimi'] },
};
const events = [];
const result = await applyMultiHarnessPlan(plan, {
applyClaude: async () => { events.push('claude'); return { action: 'installed' }; },
applyCodex: async () => { events.push('codex'); return { action: 'installed' }; },
applyManaged: async () => {
events.push('kimi');
const error = new Error('permission denied');
error.code = 'EACCES';
throw error;
},
});
assert.deepStrictEqual(events, ['claude', 'codex', 'kimi']);
assert.strictEqual(result.status, 'partial');
assert.deepStrictEqual(result.completed.map(item => item.id), ['claude', 'codex']);
assert.strictEqual(result.failure.id, 'kimi');
assert.deepStrictEqual(result.retryHarnesses, ['kimi']);
});
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
process.exitCode = failed > 0 ? 1 : 0;
})();