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
This commit is contained in:
affaan-m
2026-09-27 23:40:43 -04:00
parent e9911d6668
commit d0dc1fcb08
3 changed files with 126 additions and 3 deletions
+2 -2
View File
@@ -1735,7 +1735,7 @@ function analyzeRecord(record, context) {
}
function buildDoctorReport(options = {}) {
const repoRoot = options.repoRoot || DEFAULT_REPO_ROOT;
const repoRoot = options.repoRoot ? path.resolve(options.repoRoot) : DEFAULT_REPO_ROOT;
const manifests = loadInstallManifests({ repoRoot });
const records = discoverInstalledStates({
homeDir: options.homeDir,
@@ -1973,7 +1973,7 @@ function preflightOpenCodeHookDeactivation(record, context, options = {}) {
}
function repairInstalledStates(options = {}) {
const repoRoot = options.repoRoot || DEFAULT_REPO_ROOT;
const repoRoot = options.repoRoot ? path.resolve(options.repoRoot) : DEFAULT_REPO_ROOT;
const manifests = loadInstallManifests({ repoRoot });
const context = {
repoRoot,
+2 -1
View File
@@ -398,8 +398,9 @@ const LEGACY_OPENCODE_PLUGIN_DIGESTS = Object.freeze([
]);
function knownOpenCodePluginDigests(plan) {
if (typeof plan.sourceRoot !== 'string' || !path.isAbsolute(plan.sourceRoot)) return new Set();
// Historical refusal fingerprints do not depend on a current source checkout.
const digests = new Set(LEGACY_OPENCODE_PLUGIN_DIGESTS);
if (typeof plan.sourceRoot !== 'string' || !path.isAbsolute(plan.sourceRoot)) return digests;
const sourcePlan = { ...plan, targetRoot: plan.sourceRoot };
for (const directory of ['.opencode/plugins', '.opencode/dist/plugins']) {
for (const name of ['ecc-hooks', 'index']) {
@@ -366,6 +366,128 @@ function runTests() {
});
}));
}
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);