Files
ECC/tests/lib/opencode-hook-consent-safety.test.js
T
affaan-m d0dc1fcb08 fix(install): preserve OpenCode refusal for relative source roots
Keep historical fingerprints independent of source discovery and normalize explicit repository roots at doctor and repair entry points. Preserve unknown user plugins and absolute-only low-level source probing.

Source-PR: https://github.com/affaan-m/ECC/pull/3008
Reviewed-Manifest-SHA256: fe1444b12dfb730524970a51b6bb805271cdfa71d4cc9b247772e6d9b1d1c9d8
2026-09-27 23:40:43 -04:00

568 lines
32 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 { applyInstallPlan } = require('../../scripts/lib/install/apply');
const { withHookConsent } = require('../../scripts/lib/install/hook-consent');
const { createInstallState, readInstallState, writeInstallState } = require('../../scripts/lib/install-state');
const { buildDoctorReport, repairInstalledStates } = require('../../scripts/lib/install-lifecycle');
const REPO_ROOT = path.resolve(__dirname, '../..');
const sha256 = value => crypto.createHash('sha256').update(value).digest('hex');
// Authentic public ECC source fixtures, kept as inert bytes (never imported).
// Copying these bytes to an alias does not claim they were published build output.
const legacyPluginFixtures = [
{
name: "2.2.1 barrel",
// https://github.com/affaan-m/ECC/blob/ca185ef5f7667078a1e70a763bd3a9c71c48acf0/.opencode/plugins/index.ts
sha256: '965c5fac76ce0c3ceb3836814f5eb9ede8c9db50373a508c734f949cb321a21a',
content: `/**
* ECC Plugins for OpenCode
*
* This module exports all ECC plugins for OpenCode integration.
* Plugins provide hook-based automation that mirrors Claude Code's hook system
* while taking advantage of OpenCode's more sophisticated 20+ event types.
*/
export { ECCHooksPlugin, default } from "./ecc-hooks.js"
// Re-export for named imports
export * from "./ecc-hooks.js"
`,
},
{
name: "early barrel",
// https://github.com/affaan-m/ECC/blob/6d440c036df2c1b2fec957627d1202c3708e0627/.opencode/plugins/index.ts
sha256: 'e42c733adb177f84cea813663aa34c7868dbaa98c96950d0ef91cd211b8aa169',
content: `/**
* Everything Claude Code (ECC) Plugins for OpenCode
*
* This module exports all ECC plugins for OpenCode integration.
* Plugins provide hook-based automation that mirrors Claude Code's hook system
* while taking advantage of OpenCode's more sophisticated 20+ event types.
*/
export { ECCHooksPlugin, default } from "./ecc-hooks"
// Re-export for named imports
export * from "./ecc-hooks"
`,
},
];
function fixture(callback, enabled = false) {
const root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-opencode-write-')));
const homeDir = path.join(root, 'home');
const sourceRoot = path.join(root, 'source');
const targetRoot = path.join(homeDir, '.config', 'opencode');
const adapter = { id: 'opencode-home', target: 'opencode', kind: 'home' };
try {
fs.mkdirSync(path.join(sourceRoot, 'manifests'), { recursive: true });
for (const name of ['install-modules.json', 'install-components.json', 'install-profiles.json']) {
fs.copyFileSync(path.join(REPO_ROOT, 'manifests', name), path.join(sourceRoot, 'manifests', name));
}
fs.copyFileSync(path.join(REPO_ROOT, 'package.json'), path.join(sourceRoot, 'package.json'));
const operations = ['opencode.json', 'plugins/ecc-hooks.ts'].map(relativePath => {
const sourceRelativePath = `.opencode/${relativePath}`;
const sourcePath = path.join(sourceRoot, sourceRelativePath);
const destinationPath = path.join(targetRoot, relativePath);
const content = relativePath === 'opencode.json'
? '{"plugin":["./plugins"],"userSetting":true}\n'
: 'export default async () => ({ "session.created": () => {} });\n';
fs.mkdirSync(path.dirname(sourcePath), { recursive: true });
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
fs.writeFileSync(sourcePath, content);
fs.writeFileSync(destinationPath, content);
return { kind: 'copy-file', moduleId: 'platform-configs', sourceRelativePath, sourcePath,
destinationPath, ownership: 'managed', scaffoldOnly: false,
strategy: 'preserve-relative-path', contentSha256: sha256(content) };
});
const dist = path.join(sourceRoot, '.opencode', 'dist');
fs.mkdirSync(path.join(dist, 'plugins'), { recursive: true });
fs.mkdirSync(path.join(dist, 'tools'), { recursive: true });
fs.writeFileSync(path.join(dist, 'index.js'), 'module.exports = {};\n');
const installStatePath = path.join(targetRoot, 'ecc-install-state.json');
const state = createInstallState({
adapter, targetRoot, installStatePath,
request: { modules: ['platform-configs'], legacyMode: true,
hookConsent: enabled ? 'enabled' : null },
resolution: { selectedModules: enabled ? ['platform-configs', 'hooks-runtime'] : ['platform-configs'],
skippedModules: [] },
operations, source: { repoVersion: '2.2.2', manifestVersion: 1 },
});
writeInstallState(installStatePath, state);
const basePlan = { target: 'opencode', adapter, homeDir, sourceRoot, targetRoot,
installRoot: targetRoot, installStatePath, operations, warnings: [],
selectedModuleIds: state.resolution.selectedModules, statePreview: state };
callback({ root, homeDir, sourceRoot, targetRoot, installStatePath, state, basePlan,
declinePlan: withHookConsent(basePlan, 'declined') });
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
}
function repair(value, extra = {}) {
return repairInstalledStates({ repoRoot: value.sourceRoot, homeDir: value.homeDir,
projectRoot: value.homeDir, targets: ['opencode'],
buildOpencodePayload() { throw new Error('Unexpected compiler execution'); }, ...extra });
}
function withWritableOpenMutation(filePath, mutate, callback) {
const originalOpen = fs.openSync;
const originalClose = fs.closeSync;
let injected = false;
const descriptors = new Set();
fs.openSync = function (candidate, flags, ...rest) {
const writable = typeof flags === 'number'
? Boolean(flags & (fs.constants.O_WRONLY | fs.constants.O_RDWR))
: /[wa+]/.test(flags);
const matches = typeof candidate === 'string' && path.resolve(candidate) === path.resolve(filePath);
if (matches && writable && !injected) {
injected = true;
mutate();
}
const fd = originalOpen.call(fs, candidate, flags, ...rest);
if (matches && writable) descriptors.add(fd);
return fd;
};
fs.closeSync = function (fd) {
descriptors.delete(fd);
return originalClose.call(fs, fd);
};
try {
callback();
assert.ok(injected, 'The destination writable-open boundary must be exercised');
assert.strictEqual(descriptors.size, 0, 'Every owned writable descriptor must close');
} finally {
fs.openSync = originalOpen;
fs.closeSync = originalClose;
}
}
function assertPriorOwnership(value, filePath) {
const after = readInstallState(value.installStatePath);
const prior = value.state.operations.find(operation => operation.destinationPath === filePath);
const current = after.operations.find(operation => operation.destinationPath === filePath);
assert.strictEqual(current.contentSha256, prior.contentSha256, 'Do not adopt raced bytes');
assert.strictEqual(after.request.hookConsent, value.state.request.hookConsent);
}
function runTests() {
let passed = 0;
let failed = 0;
const test = (name, callback) => {
try { callback(); passed++; console.log(` PASS ${name}`); }
catch (error) { failed++; console.error(` FAIL ${name}: ${error.stack}`); }
};
for (const relativePath of ['plugins/ecc-hooks.ts', 'opencode.json']) {
for (const mode of ['apply', 'repair']) {
test(`${mode} preserves a same-inode ${relativePath} edit at writable open`, () => fixture(value => {
const destination = path.join(value.targetRoot, relativePath);
const content = relativePath === 'opencode.json'
? '{"plugin":["./plugins"],"userEdit":"keep"}\n' : '// user edit: preserve this\n';
withWritableOpenMutation(destination, () => fs.writeFileSync(destination, content), () => {
if (mode === 'apply') {
assert.throws(() => applyInstallPlan(value.declinePlan), /changed after preflight/i);
} else {
const result = repair(value).results[0];
assert.strictEqual(result.status, 'error');
assert.match(result.error, /changed after preflight/i);
assert.notStrictEqual(result.stateRefreshed, true);
}
assert.strictEqual(fs.readFileSync(destination, 'utf8'), content);
assertPriorOwnership(value, destination);
});
}, mode === 'apply'));
}
}
for (const mode of ['apply', 'repair']) {
test(`${mode} preserves a file created after expected absence`, () => fixture(value => {
const destination = path.join(value.targetRoot, 'plugins/ecc-hooks.ts');
fs.unlinkSync(destination);
const content = '// newly created user file\n';
withWritableOpenMutation(destination, () => fs.writeFileSync(destination, content), () => {
if (mode === 'apply') assert.throws(() => applyInstallPlan(value.declinePlan), /EEXIST|changed after preflight/i);
else assert.strictEqual(repair(value).results[0].status, 'error');
assert.strictEqual(fs.readFileSync(destination, 'utf8'), content);
assertPriorOwnership(value, destination);
});
}, mode === 'apply'));
test(`${mode} does not recreate an expected existing file removed at open`, () => fixture(value => {
const destination = path.join(value.targetRoot, 'opencode.json');
withWritableOpenMutation(destination, () => fs.unlinkSync(destination), () => {
if (mode === 'apply') assert.throws(() => applyInstallPlan(value.declinePlan), /ENOENT|changed after preflight/i);
else assert.strictEqual(repair(value).results[0].status, 'error');
assert.strictEqual(fs.existsSync(destination), false);
assertPriorOwnership(value, destination);
});
}, mode === 'apply'));
}
for (const mode of ['apply', 'doctor', 'repair']) {
test(`${mode} reports malformed activation config with source context`, () => fixture(value => {
const destination = path.join(value.targetRoot, 'opencode.json');
fs.writeFileSync(destination, '{ malformed');
const before = fs.readFileSync(value.installStatePath);
if (mode === 'apply') assert.throws(() => applyInstallPlan(value.declinePlan), /Failed to parse .*opencode\.json/);
else if (mode === 'repair') assert.match(repair(value).results[0].error, /Failed to parse .*opencode\.json/);
else {
const result = buildDoctorReport({ repoRoot: value.sourceRoot, homeDir: value.homeDir,
projectRoot: value.homeDir, targets: ['opencode'] }).results[0];
assert.match(JSON.stringify(result.issues), /Failed to parse .*opencode\.json/);
}
assert.strictEqual(fs.readFileSync(destination, 'utf8'), '{ malformed');
assert.deepStrictEqual(fs.readFileSync(value.installStatePath), before);
}));
}
test('apply rejects another enabled writer and repair while holding its target lease', () => fixture(value => {
let checked = false;
applyInstallPlan(value.declinePlan, {
beforeOperationWrite() {
if (checked) return;
checked = true;
assert.throws(() => applyInstallPlan(withHookConsent(value.basePlan, 'enabled')), /Another ECC process|OpenCode.*lock/i);
assert.strictEqual(repair(value).results[0].status, 'error');
},
});
assert.ok(checked);
assert.strictEqual(fs.existsSync(`${value.installStatePath}.ecc.lock`), false);
assert.strictEqual(applyInstallPlan(value.declinePlan).applied, true);
}));
test('repair preserves ownership for a destination-classified activation with a recorded transform', () => fixture(value => {
const operation = value.state.operations.find(entry => entry.sourceRelativePath.endsWith('ecc-hooks.ts'));
const oldDestination = operation.destinationPath;
operation.sourceRelativePath = '.opencode/tools/fixture.js';
operation.contentTransform = 'opencode-disable-plugin-entrypoint';
operation.destinationPath = path.join(value.targetRoot, 'plugins', 'custom.js');
const source = path.join(value.sourceRoot, operation.sourceRelativePath);
fs.mkdirSync(path.dirname(source), { recursive: true });
fs.copyFileSync(oldDestination, source);
fs.unlinkSync(oldDestination);
writeInstallState(value.installStatePath, value.state);
const content = '// preserve unowned new custom plugin\n';
withWritableOpenMutation(operation.destinationPath, () => fs.writeFileSync(operation.destinationPath, content), () => {
const result = repair(value).results[0];
assert.strictEqual(result.status, 'error');
assert.match(result.error, /changed after preflight/i);
assert.strictEqual(fs.readFileSync(operation.destinationPath, 'utf8'), content);
assertPriorOwnership(value, operation.destinationPath);
});
}));
test('repair completes historical deactivation with exact inert bytes and releases its lock', () => fixture(value => {
const result = repair(value).results[0];
assert.strictEqual(result.status, 'repaired', result.error);
assert.strictEqual(result.stateRefreshed, true);
assert.strictEqual(fs.readFileSync(path.join(value.targetRoot, 'plugins/ecc-hooks.ts'), 'utf8'),
'export default async () => ({});\n');
assert.deepStrictEqual(JSON.parse(fs.readFileSync(path.join(value.targetRoot, 'opencode.json'))),
{ plugin: [], userSetting: true });
assert.strictEqual(fs.existsSync(`${value.installStatePath}.ecc.lock`), false);
}));
for (const consent of [null, 'declined']) {
test(`fresh ${consent || 'default'} apply preserves unrelated unrecorded plugin aliases`, () => fixture(value => {
fs.unlinkSync(value.installStatePath);
for (const operation of value.basePlan.operations) fs.unlinkSync(operation.destinationPath);
const aliases = ['index.ts', 'index.js', 'index.mjs', 'index.cjs', 'ecc-hooks.js'];
const content = 'export default async () => ({ "user.plugin": () => {} });\n';
for (const alias of aliases) fs.writeFileSync(path.join(value.targetRoot, 'plugins', alias), content);
const result = applyInstallPlan(withHookConsent(value.basePlan, consent));
assert.strictEqual(result.applied, true);
const state = readInstallState(value.installStatePath);
for (const alias of aliases) {
const destination = path.join(value.targetRoot, 'plugins', alias);
assert.strictEqual(fs.readFileSync(destination, 'utf8'), content);
assert.ok(!state.operations.some(operation => operation.destinationPath === destination), 'Do not adopt a user plugin');
}
}));
}
for (const mode of ['doctor', 'repair']) {
test(`${mode} preserves an unrelated plugin without reporting ECC activation`, () => fixture(value => {
applyInstallPlan(value.declinePlan);
const destination = path.join(value.targetRoot, 'plugins', 'index.js');
const content = 'export default async () => ({ "user.plugin": () => {} });\n';
fs.writeFileSync(destination, content);
const before = fs.readFileSync(value.installStatePath);
if (mode === 'doctor') {
const result = buildDoctorReport({ repoRoot: value.sourceRoot, homeDir: value.homeDir,
projectRoot: value.homeDir, targets: ['opencode'] }).results[0];
assert.ok(!result.issues.some(issue => issue.code === 'opencode-hook-consent-violation'), JSON.stringify(result.issues));
} else {
const result = repair(value).results[0];
assert.notStrictEqual(result.status, 'error', result.error);
}
assert.strictEqual(fs.readFileSync(destination, 'utf8'), content);
const { lastValidatedAt: _beforeValidation, ...priorState } = JSON.parse(before);
const { lastValidatedAt: _afterValidation, ...afterState } = readInstallState(value.installStatePath);
assert.deepStrictEqual(afterState, priorState, 'Only the legitimate validation timestamp may change');
assert.ok(!afterState.operations.some(operation => operation.destinationPath === destination), 'Do not adopt a user plugin');
}));
}
for (const legacy of legacyPluginFixtures) {
for (const consent of [null, 'declined']) {
test(`fresh ${consent || 'default'} apply refuses the unrecorded historical ${legacy.name}`, () => fixture(value => {
assert.strictEqual(sha256(legacy.content), legacy.sha256, 'Preserve exact public-source fixture bytes');
assert.notStrictEqual(sha256(fs.readFileSync(path.join(value.sourceRoot, '.opencode/plugins/ecc-hooks.ts'))), legacy.sha256);
assert.notStrictEqual(sha256(fs.readFileSync(path.join(REPO_ROOT, '.opencode/plugins/index.ts'))), legacy.sha256);
fs.unlinkSync(value.installStatePath);
for (const operation of value.basePlan.operations) fs.unlinkSync(operation.destinationPath);
const alias = path.join(value.targetRoot, 'plugins', 'index.js');
assert.ok(!value.basePlan.operations.some(operation => operation.destinationPath === alias));
fs.writeFileSync(alias, legacy.content);
assert.throws(() => applyInstallPlan(withHookConsent(value.basePlan, consent)), /Refusing OpenCode hook deactivation/);
assert.strictEqual(fs.readFileSync(alias, 'utf8'), legacy.content);
assert.strictEqual(fs.existsSync(value.installStatePath), false, 'Do not adopt an unrecorded historical alias');
for (const operation of value.basePlan.operations) assert.strictEqual(fs.existsSync(operation.destinationPath), false);
assert.strictEqual(fs.existsSync(`${value.installStatePath}.ecc.lock`), false);
}));
}
for (const mode of ['doctor', 'repair']) {
test(`${mode} refuses the unrecorded historical ${legacy.name} without changing ownership`, () => fixture(value => {
applyInstallPlan(value.declinePlan);
const alias = path.join(value.targetRoot, 'plugins', 'index.js');
fs.writeFileSync(alias, legacy.content);
const before = fs.readFileSync(value.installStatePath);
const operationsBefore = value.basePlan.operations.map(operation => fs.readFileSync(operation.destinationPath));
assert.ok(!readInstallState(value.installStatePath).operations.some(operation => operation.destinationPath === alias));
if (mode === 'doctor') {
const result = buildDoctorReport({ repoRoot: value.sourceRoot, homeDir: value.homeDir,
projectRoot: value.homeDir, targets: ['opencode'] }).results[0];
const issue = result.issues.find(entry => entry.code === 'opencode-hook-consent-violation');
assert.ok(issue, JSON.stringify(result.issues));
assert.match(issue.message, /OpenCode hook activation remains active/);
} else {
const result = repair(value).results[0];
assert.strictEqual(result.status, 'error');
assert.match(result.error, /Refusing OpenCode hook deactivation/);
assert.notStrictEqual(result.stateRefreshed, true);
}
assert.strictEqual(fs.readFileSync(alias, 'utf8'), legacy.content);
assert.deepStrictEqual(fs.readFileSync(value.installStatePath), before);
assert.deepStrictEqual(value.basePlan.operations.map(operation => fs.readFileSync(operation.destinationPath)), operationsBefore);
assert.strictEqual(fs.existsSync(`${value.installStatePath}.ecc.lock`), false);
}));
}
}
for (const mode of ['apply', 'repair']) {
test(`${mode} refuses a historical alias inserted after preflight before state refresh`, () => fixture(value => {
const destination = path.join(value.targetRoot, 'plugins', 'ecc-hooks.ts');
const alias = path.join(value.targetRoot, 'plugins', 'index.js');
const content = legacyPluginFixtures[0].content;
withWritableOpenMutation(destination, () => fs.writeFileSync(alias, content), () => {
if (mode === 'apply') assert.throws(() => applyInstallPlan(value.declinePlan), /OpenCode hook activation remains active/);
else {
const result = repair(value).results[0];
assert.strictEqual(result.status, 'error');
assert.match(result.error, /OpenCode hook activation remains active/);
assert.notStrictEqual(result.stateRefreshed, true);
}
assert.strictEqual(fs.readFileSync(alias, 'utf8'), content);
const state = readInstallState(value.installStatePath);
assert.ok(!state.operations.some(operation => operation.destinationPath === alias));
assert.strictEqual(state.request.hookConsent, value.state.request.hookConsent);
assert.strictEqual(fs.existsSync(`${value.installStatePath}.ecc.lock`), false);
});
}));
}
for (const rootForm of ['relative', 'missing']) {
for (const consent of [null, 'declined']) {
test(`${rootForm} source root keeps historical refusal for fresh ${consent || 'default'} apply`, () => fixture(value => {
const legacy = legacyPluginFixtures[0];
assert.strictEqual(sha256(legacy.content), legacy.sha256);
fs.unlinkSync(value.installStatePath);
for (const operation of value.basePlan.operations) fs.unlinkSync(operation.destinationPath);
const alias = path.join(value.targetRoot, 'plugins', 'index.js');
fs.writeFileSync(alias, legacy.content);
const sourceRoot = rootForm === 'relative'
? path.relative(process.cwd(), value.sourceRoot) : undefined;
if (sourceRoot) assert.strictEqual(path.isAbsolute(sourceRoot), false);
const plan = withHookConsent({ ...value.basePlan, sourceRoot }, consent);
assert.throws(() => applyInstallPlan(plan), /Refusing OpenCode hook deactivation/);
assert.strictEqual(fs.readFileSync(alias, 'utf8'), legacy.content);
assert.strictEqual(fs.existsSync(value.installStatePath), false);
for (const operation of value.basePlan.operations) assert.strictEqual(fs.existsSync(operation.destinationPath), false);
assert.strictEqual(fs.existsSync(`${value.installStatePath}.ecc.lock`), false);
}));
}
}
for (const mode of ['doctor', 'repair']) {
for (const historical of [true, false]) {
test(`${mode} with relative repoRoot ${historical ? 'refuses historical ECC' : 'preserves unrelated user'} aliases`, () => fixture(value => {
applyInstallPlan(value.declinePlan);
const alias = path.join(value.targetRoot, 'plugins', 'index.js');
const content = historical ? legacyPluginFixtures[0].content
: 'export default async () => ({ "user.plugin": () => {} });\n';
fs.writeFileSync(alias, content);
const before = fs.readFileSync(value.installStatePath);
const operationsBefore = value.basePlan.operations.map(operation => fs.readFileSync(operation.destinationPath));
const repoRoot = path.relative(process.cwd(), value.sourceRoot);
assert.strictEqual(path.isAbsolute(repoRoot), false);
if (mode === 'doctor') {
const result = buildDoctorReport({ repoRoot, homeDir: value.homeDir,
projectRoot: value.homeDir, targets: ['opencode'] }).results[0];
const issue = result.issues.find(entry => entry.code === 'opencode-hook-consent-violation');
assert.strictEqual(Boolean(issue), historical, JSON.stringify(result.issues));
if (historical) assert.match(issue.message, /OpenCode hook activation remains active/);
} else {
const result = repair(value, { repoRoot }).results[0];
if (historical) {
assert.strictEqual(result.status, 'error');
assert.match(result.error, /Refusing OpenCode hook deactivation/);
assert.notStrictEqual(result.stateRefreshed, true);
} else assert.strictEqual(result.status, 'ok', result.error);
}
assert.strictEqual(fs.readFileSync(alias, 'utf8'), content);
if (historical) assert.deepStrictEqual(fs.readFileSync(value.installStatePath), before);
else {
const { lastValidatedAt: _beforeValidation, ...priorState } = JSON.parse(before);
const { lastValidatedAt: _afterValidation, ...afterState } = readInstallState(value.installStatePath);
assert.deepStrictEqual(afterState, priorState, 'Only the validation timestamp may change');
}
assert.ok(!readInstallState(value.installStatePath).operations.some(operation => operation.destinationPath === alias));
assert.deepStrictEqual(value.basePlan.operations.map(operation => fs.readFileSync(operation.destinationPath)), operationsBefore);
assert.strictEqual(fs.existsSync(`${value.installStatePath}.ecc.lock`), false);
}));
}
}
for (const mode of ['apply', 'repair']) {
test(`${mode} with relative root refuses a historical alias inserted after preflight`, () => fixture(value => {
const alias = path.join(value.targetRoot, 'plugins', 'index.js');
const content = legacyPluginFixtures[0].content;
const repoRoot = path.relative(process.cwd(), value.sourceRoot);
withWritableOpenMutation(path.join(value.targetRoot, 'plugins', 'ecc-hooks.ts'),
() => fs.writeFileSync(alias, content), () => {
if (mode === 'apply') {
assert.throws(() => applyInstallPlan({ ...value.declinePlan, sourceRoot: repoRoot }),
/OpenCode hook activation remains active/);
} else {
const result = repair(value, { repoRoot }).results[0];
assert.strictEqual(result.status, 'error');
assert.match(result.error, /OpenCode hook activation remains active/);
assert.notStrictEqual(result.stateRefreshed, true);
}
assert.strictEqual(fs.readFileSync(alias, 'utf8'), content);
const state = readInstallState(value.installStatePath);
assert.ok(!state.operations.some(operation => operation.destinationPath === alias));
assert.strictEqual(state.request.hookConsent, value.state.request.hookConsent);
assert.strictEqual(fs.existsSync(`${value.installStatePath}.ecc.lock`), false);
});
}));
}
for (const artifact of ['source', 'build']) {
test(`relative repoRoot attributes only trusted current ${artifact} bytes in doctor and repair`, () => fixture(value => {
applyInstallPlan(value.declinePlan);
const source = artifact === 'source'
? path.join(value.sourceRoot, '.opencode', 'plugins', 'ecc-hooks.ts')
: path.join(value.sourceRoot, '.opencode', 'dist', 'plugins', 'index.js');
if (artifact === 'build') fs.writeFileSync(source, 'module.exports = { eccHook: true };\n');
const content = fs.readFileSync(source);
const alias = path.join(value.targetRoot, 'plugins', 'index.js');
fs.writeFileSync(alias, content);
const before = fs.readFileSync(value.installStatePath);
const operationsBefore = value.basePlan.operations.map(operation => fs.readFileSync(operation.destinationPath));
const repoRoot = path.relative(process.cwd(), value.sourceRoot);
assert.strictEqual(path.isAbsolute(repoRoot), false);
const doctor = buildDoctorReport({ repoRoot, homeDir: value.homeDir,
projectRoot: value.homeDir, targets: ['opencode'] }).results[0];
assert.ok(doctor.issues.some(issue => issue.code === 'opencode-hook-consent-violation'), JSON.stringify(doctor.issues));
const result = repair(value, { repoRoot }).results[0];
assert.strictEqual(result.status, 'error');
assert.match(result.error, /Refusing OpenCode hook deactivation/);
assert.notStrictEqual(result.stateRefreshed, true);
assert.deepStrictEqual(fs.readFileSync(alias), content);
assert.deepStrictEqual(fs.readFileSync(value.installStatePath), before);
assert.deepStrictEqual(value.basePlan.operations.map(operation => fs.readFileSync(operation.destinationPath)), operationsBefore);
assert.strictEqual(fs.existsSync(`${value.installStatePath}.ecc.lock`), false);
}));
}
for (const mode of ['doctor', 'repair']) {
test(`${mode} still rejects unsupported explicit repoRoot types`, () => fixture(value => {
for (const repoRoot of [{}, true, 1]) {
const invoke = mode === 'doctor'
? () => buildDoctorReport({ repoRoot, homeDir: value.homeDir,
projectRoot: value.homeDir, targets: ['opencode'] })
: () => repair(value, { repoRoot });
assert.throws(invoke, error => error instanceof TypeError && error.code === 'ERR_INVALID_ARG_TYPE');
}
}));
}
for (const artifact of ['source', 'build']) {
test(`unrecorded ${artifact}-identical ECC alias still fails closed`, () => fixture(value => {
applyInstallPlan(value.declinePlan);
const source = artifact === 'source'
? path.join(value.sourceRoot, '.opencode', 'plugins', 'ecc-hooks.ts')
: path.join(value.sourceRoot, '.opencode', 'dist', 'plugins', 'index.js');
if (artifact === 'build') fs.writeFileSync(source, 'module.exports = { eccHook: true };\n');
const content = fs.readFileSync(source);
const alias = path.join(value.targetRoot, 'plugins', 'index.js');
fs.writeFileSync(alias, content);
const before = fs.readFileSync(value.installStatePath);
assert.throws(() => applyInstallPlan(value.declinePlan), /OpenCode hook deactivation/);
const doctor = buildDoctorReport({ repoRoot: value.sourceRoot, homeDir: value.homeDir,
projectRoot: value.homeDir, targets: ['opencode'] }).results[0];
assert.ok(doctor.issues.some(issue => issue.code === 'opencode-hook-consent-violation'));
assert.strictEqual(repair(value).results[0].status, 'error');
assert.deepStrictEqual(fs.readFileSync(alias), content);
assert.deepStrictEqual(fs.readFileSync(value.installStatePath), before);
}));
}
test('an unrecorded collision at a planned ECC plugin destination still fails closed', () => fixture(value => {
fs.unlinkSync(value.installStatePath);
fs.unlinkSync(path.join(value.targetRoot, 'opencode.json'));
const destination = path.join(value.targetRoot, 'plugins', 'ecc-hooks.ts');
const content = '// user-owned file at an ECC destination\n';
fs.writeFileSync(destination, content);
assert.throws(() => applyInstallPlan(value.declinePlan), /user-owned|unverifiable/i);
assert.strictEqual(fs.readFileSync(destination, 'utf8'), content);
assert.strictEqual(fs.existsSync(value.installStatePath), false);
}));
for (const planned of [false, true]) {
test(`${planned ? 'planned ECC' : 'unrecorded user'} plugin read failures respect the attribution boundary`, () => fixture(value => {
const destination = path.join(value.targetRoot, 'plugins', planned ? 'ecc-hooks.ts' : 'index.js');
const content = planned ? fs.readFileSync(destination) : Buffer.from('// unreadable user plugin\n');
if (!planned) fs.writeFileSync(destination, content);
const originalOpen = fs.openSync;
let refusedReads = 0;
fs.openSync = function (candidate, ...args) {
if (typeof candidate === 'string' && path.resolve(candidate) === destination) {
refusedReads++;
throw Object.assign(new Error('Synthetic plugin read permission denied'), { code: 'EACCES' });
}
return originalOpen.call(fs, candidate, ...args);
};
try {
if (planned) assert.throws(() => applyInstallPlan(value.declinePlan), /permission denied/);
else assert.strictEqual(applyInstallPlan(value.declinePlan).applied, true);
assert.ok(refusedReads > 0, 'The permission boundary must be exercised');
} finally {
fs.openSync = originalOpen;
}
assert.deepStrictEqual(fs.readFileSync(destination), content);
if (!planned) assert.ok(!readInstallState(value.installStatePath).operations.some(operation => operation.destinationPath === destination));
}));
}
test('repair preserves the primary failure and replacement lock when release also fails', () => fixture(value => {
const destination = path.join(value.targetRoot, 'opencode.json');
const lock = `${value.installStatePath}.ecc.lock`;
withWritableOpenMutation(destination, () => {
fs.writeFileSync(destination, '{"userEdit":true}');
fs.renameSync(lock, `${lock}.owned`);
fs.writeFileSync(lock, 'replacement lock');
}, () => {
const result = repair(value).results[0];
assert.strictEqual(result.status, 'error');
assert.match(result.error, /changed after preflight/i);
assert.match(result.releaseError, /changed OpenCode install lock/);
assert.strictEqual(fs.readFileSync(lock, 'utf8'), 'replacement lock');
assertPriorOwnership(value, destination);
});
}));
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
return { passed, failed };
}
if (require.main === module) process.exitCode = runTests().failed ? 1 : 0;
module.exports = { runTests };