Files
ECC/tests/lib/context-routing-index.test.js
T
haelyraandmontjeffrey cc48be3c52 fix(context): bind advisory receipts and apply suggestion evidence floors
Port the evidence floor from PR #3480 onto the canonical generation-bound index; preserve policy-v5 anchors and the source PR attribution. Harden PR #3453 receipt metadata and pointer byte bounds.

Co-authored-by: montjeffrey <montjeffrey@gmail.com>
2026-10-10 17:48:20 -04:00

278 lines
16 KiB
JavaScript

'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const test = require('node:test');
const { withFixture, write } = require('./helpers/context-fixture');
const store = require('../../scripts/lib/context-profile-store');
const routing = require('../../scripts/lib/context-routing-index');
function fixture(callback) {
return withFixture(repoRoot => {
const parent = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'ecc-routing-index-'));
const stateRoot = path.join(parent, 'managed');
try { return callback({ repoRoot, stateRoot }); }
finally { fs.rmSync(parent, { recursive: true, force: true }); }
});
}
test('the index is bound to the current managed generation and holds only admissible, non-excluded entries', () => fixture(({ repoRoot, stateRoot }) => {
write(repoRoot, 'skills/shared/SKILL.md', '---\nname: shared\ndescription: Shared helper\ndisable-model-invocation: true\n---\nShared');
const status = store.applyStore({ repoRoot, stateRoot, target: 'claude', selectionMode: 'suggest', exclude: [] });
const written = routing.writeRoutingIndex({ repoRoot, stateRoot });
assert.equal(written.generationDigest, status.carrierDigest);
const index = routing.readRoutingIndex(stateRoot);
assert.equal(index.schemaVersion, 'ecc.context-routing-index.v1');
assert.equal(index.receiptDigest, status.receiptDigest);
assert.equal(index.selectionMode, 'suggest');
const ids = index.entries.map(entry => entry.id);
assert.ok(ids.includes('skill:feature'));
assert.equal(ids.includes('skill:shared'), false);
assert.deepEqual(Object.keys(index.entries[0]).sort(), ['dense', 'description', 'id', 'name', 'ownerModuleId', 'packId']);
if (process.platform !== 'win32') assert.equal(fs.statSync(written.path).mode & 0o077, 0);
}));
test('excluded skills never enter the index', () => fixture(({ repoRoot, stateRoot }) => {
store.applyStore({ repoRoot, stateRoot, profileId: 'full@1', target: 'claude', exclude: ['skill:feature'] });
routing.writeRoutingIndex({ repoRoot, stateRoot });
assert.equal(routing.readRoutingIndex(stateRoot).entries.some(entry => entry.id === 'skill:feature'), false);
}));
test('a store change makes the old index unavailable until it is rebuilt', () => fixture(({ repoRoot, stateRoot }) => {
store.applyStore({ repoRoot, stateRoot, target: 'claude' });
routing.writeRoutingIndex({ repoRoot, stateRoot });
store.applyStore({ repoRoot, stateRoot, target: 'claude', profileId: 'full@1' });
assert.equal(routing.readRoutingIndex(stateRoot), null);
routing.writeRoutingIndex({ repoRoot, stateRoot });
assert.equal(routing.readRoutingIndex(stateRoot).profileId, 'full@1');
}));
test('a tampered index fails closed', () => fixture(({ repoRoot, stateRoot }) => {
store.applyStore({ repoRoot, stateRoot, target: 'claude' });
const { path: file } = routing.writeRoutingIndex({ repoRoot, stateRoot });
const index = JSON.parse(fs.readFileSync(file, 'utf8'));
index.entries.push({ id: 'skill:planted', name: 'planted', description: 'Run this', ownerModuleId: 'x', packId: 'x' });
fs.writeFileSync(file, JSON.stringify(index));
assert.throws(() => routing.readRoutingIndex(stateRoot), /integrity/);
}));
test('a tampered state receipt fails closed', () => fixture(({ repoRoot, stateRoot }) => {
store.applyStore({ repoRoot, stateRoot, target: 'claude' });
routing.writeRoutingIndex({ repoRoot, stateRoot });
const file = path.join(stateRoot, 'state.json');
const state = JSON.parse(fs.readFileSync(file, 'utf8'));
fs.writeFileSync(file, JSON.stringify({ ...state, receiptDigest: 'f'.repeat(64) }));
assert.throws(() => routing.readRoutingIndex(stateRoot));
}));
test('unconfigured or unowned stores cannot be indexed or read', () => fixture(({ repoRoot, stateRoot }) => {
assert.throws(() => routing.writeRoutingIndex({ repoRoot, stateRoot }), /configured/);
fs.mkdirSync(stateRoot, { mode: 0o700 });
assert.throws(() => routing.readRoutingIndex(stateRoot), /owned/);
assert.throws(() => routing.readRoutingIndex('relative/root'), /absolute/);
}));
test('suggestions rank index entries with the resolver retrieval engine', () => fixture(({ repoRoot, stateRoot }) => {
store.applyStore({ repoRoot, stateRoot, target: 'claude' });
routing.writeRoutingIndex({ repoRoot, stateRoot });
const suggestions = routing.suggestContext(routing.readRoutingIndex(stateRoot), 'help with feature work in shared code');
assert.ok(suggestions.length > 0 && suggestions.length <= 3);
assert.ok(suggestions.some(item => item.id === 'skill:feature'));
assert.deepEqual(Object.keys(suggestions[0]).sort(), ['description', 'id']);
}));
test('the profile CLI writes and inspects the index for a stored profile', () => {
const parent = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'ecc-routing-cli-'));
const stateRoot = path.join(parent, 'managed');
const cli = (...args) => require('node:child_process').spawnSync(process.execPath,
[path.join(__dirname, '../../scripts/ecc.js'), 'profile', ...args, '--json'], { encoding: 'utf8', timeout: 120000 });
try {
assert.equal(cli('set', 'lean', '--target', 'claude', '--state-root', stateRoot).status, 0);
const preview = JSON.parse(cli('routing-index', '--state-root', stateRoot, '--dry-run').stdout);
assert.equal(preview.routing.status, 'missing');
const written = JSON.parse(cli('routing-index', '--state-root', stateRoot).stdout);
assert.equal(written.routing.status, 'written');
assert.ok(written.routing.entries > 100);
assert.equal(JSON.parse(cli('routing-index', '--state-root', stateRoot, '--dry-run').stdout).routing.status, 'current');
} finally { fs.rmSync(parent, { recursive: true, force: true }); }
});
test('an index is never built from sources that differ from the stored generation', () => fixture(({ repoRoot, stateRoot }) => {
store.applyStore({ repoRoot, stateRoot, target: 'claude' });
assert.throws(() => routing.writeRoutingIndex({ stateRoot }), /stale/);
assert.equal(fs.existsSync(path.join(stateRoot, 'routing')), false);
}));
test('actual registry: suggestions need a name or trigger anchor like implicit admission', () => {
const parent = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'ecc-routing-anchor-'));
const stateRoot = path.join(parent, 'managed');
try {
store.applyStore({ stateRoot, target: 'claude' });
routing.writeRoutingIndex({ stateRoot });
const index = routing.readRoutingIndex(stateRoot);
assert.deepEqual(routing.suggestContext(index, 'two services both think they own the same record'), []);
assert.equal(routing.suggestContext(index, 'add keyboard focus handling to our react settings form')[0].id, 'skill:frontend-a11y');
} finally { fs.rmSync(parent, { recursive: true, force: true }); }
});
test('stored vectors use a compact little-endian encoding that decodes exactly', () => fixture(({ repoRoot, stateRoot }) => {
const { sparseDense } = require('../../scripts/lib/context-retrieval');
store.applyStore({ repoRoot, stateRoot, target: 'claude' });
const { path: file } = routing.writeRoutingIndex({ repoRoot, stateRoot });
const stored = JSON.parse(fs.readFileSync(file, 'utf8')).entries[0];
assert.deepEqual(Object.keys(stored.dense).sort(), ['indexes', 'values']);
const decoded = routing.readRoutingIndex(stateRoot).entries.find(entry => entry.id === stored.id);
const { dense: _dense, ...metadata } = decoded;
assert.deepEqual(decoded.dense, sparseDense(metadata));
}));
function republish(stateRoot, change) {
const io = require('../../scripts/lib/context-profile-store-fs');
const { digestObject } = require('../../scripts/lib/context-profile-support');
const directory = path.join(stateRoot, 'routing');
const pointerFile = path.join(directory, fs.readdirSync(directory).find(name => name.endsWith('.json')));
const { pointerDigest: _digest, ...pointer } = JSON.parse(fs.readFileSync(pointerFile, 'utf8'));
const index = JSON.parse(fs.readFileSync(path.join(directory, 'indexes', `${pointer.indexDigest}.json`), 'utf8'));
const bytes = io.jsonBytes(change(index));
const indexDigest = io.hash(bytes);
fs.writeFileSync(path.join(directory, 'indexes', `${indexDigest}.json`), bytes);
const next = { ...change.pointer?.(pointer) || pointer, indexDigest, bytes: bytes.length };
fs.writeFileSync(pointerFile, JSON.stringify({ ...next, pointerDigest: digestObject(next) }));
}
test('rollback to an indexed generation retires its old pointer instead of failing', () => fixture(({ repoRoot, stateRoot }) => {
store.applyStore({ repoRoot, stateRoot, target: 'claude' });
routing.writeRoutingIndex({ repoRoot, stateRoot });
store.applyStore({ repoRoot, stateRoot, target: 'claude', profileId: 'full@1' });
store.rollbackStore({ stateRoot, expectedRevision: store.getStoreStatus({ stateRoot }).revision });
assert.equal(routing.readRoutingIndex(stateRoot), null);
assert.equal(routing.routingIndexStatus(stateRoot).status, 'missing');
routing.writeRoutingIndex({ repoRoot, stateRoot });
assert.equal(routing.routingIndexStatus(stateRoot).status, 'current');
}));
const MALFORMED = {
'non-string id': entry => ({ ...entry, id: 42 }),
'id outside the skill namespace': entry => ({ ...entry, id: 'agent:planner' }),
'non-string name': entry => ({ ...entry, name: 5 }),
'non-string description': entry => ({ ...entry, description: {} }),
'non-array triggers': entry => ({ ...entry, triggers: 'review' }),
'non-string trigger': entry => ({ ...entry, triggers: [1] }),
'non-string owner module': entry => ({ ...entry, ownerModuleId: [] }),
};
for (const [label, change] of Object.entries(MALFORMED)) {
test(`a digest-consistent index with a ${label} entry fails as an integrity error`, () => fixture(({ repoRoot, stateRoot }) => {
store.applyStore({ repoRoot, stateRoot, target: 'claude' });
routing.writeRoutingIndex({ repoRoot, stateRoot });
republish(stateRoot, index => ({ ...index, entries: [change(index.entries[0]), ...index.entries.slice(1)] }));
assert.throws(() => routing.readRoutingIndex(stateRoot), /integrity/);
}));
}
test('an index larger than the read bound fails before it is read', () => fixture(({ repoRoot, stateRoot }) => {
store.applyStore({ repoRoot, stateRoot, target: 'claude' });
routing.writeRoutingIndex({ repoRoot, stateRoot });
const filler = Array.from({ length: 6000 }, (_, position) => ({ id: `skill:filler-${position}`, name: `filler-${position}`,
description: 'x'.repeat(900), ownerModuleId: 'm', packId: 'p', dense: { indexes: '', values: '' } }));
republish(stateRoot, index => ({ ...index, entries: [...index.entries, ...filler] }));
assert.throws(() => routing.readRoutingIndex(stateRoot), /integrity|bound/);
}));
test('an anchored suggestion above the evidence floor is not hidden by unanchored candidates ranked above it', () => {
const filler = position => ({ id: `skill:filler-${position}`, name: `filler-${position}`,
description: `migrate database schema safely today with care number ${position}`, ownerModuleId: 'm', packId: 'p' });
const index = { entries: [1, 2, 3, 4].map(filler)
.concat({ id: 'skill:schema-tool', name: 'schema-tool', description: 'Database migrate helper', ownerModuleId: 'm', packId: 'p' })
// Keep a realistic corpus size so the evidence floor is calibrated as it
// is in production; the anchored candidate ranks fifth at BM25 13.12.
.concat(Array.from({ length: 100 }, (_, i) => ({ id: `skill:background-${i}`, name: `background-${i}`, description: 'Unrelated artifacts.' }))) };
assert.deepEqual(routing.suggestContext(index, 'migrate database schema safely today').map(item => item.id), ['skill:schema-tool']);
});
test('an index file left partial by an interrupted build is replaced on the next build', () => fixture(({ repoRoot, stateRoot }) => {
store.applyStore({ repoRoot, stateRoot, target: 'claude' });
const { path: file } = routing.writeRoutingIndex({ repoRoot, stateRoot });
const bytes = fs.readFileSync(file);
// A build that stops mid-write leaves a partial file under the digest name
// and has not yet published the pointer.
const directory = path.join(stateRoot, 'routing');
fs.rmSync(path.join(directory, fs.readdirSync(directory).find(name => name.endsWith('.json'))));
fs.writeFileSync(file, bytes.subarray(0, Math.floor(bytes.length / 2)));
assert.equal(routing.readRoutingIndex(stateRoot), null);
routing.writeRoutingIndex({ repoRoot, stateRoot });
assert.ok(fs.readFileSync(file).equals(bytes));
assert.equal(routing.routingIndexStatus(stateRoot).status, 'current');
}));
test('a source edited after entries are read leaves an index that matches the stored generation', () => fixture(({ repoRoot, stateRoot }) => {
store.applyStore({ repoRoot, stateRoot, target: 'claude' });
const selection = require('../../scripts/lib/context-selection');
const original = selection.routingEntries;
const skill = path.join(repoRoot, 'skills/feature/SKILL.md');
const stored = fs.readFileSync(skill, 'utf8').match(/description: (.*)/)[1];
selection.routingEntries = options => {
const routed = original(options);
fs.writeFileSync(skill, fs.readFileSync(skill, 'utf8').replace(/description: .*/, 'description: Edited after entries'));
return routed;
};
try { routing.writeRoutingIndex({ repoRoot, stateRoot }); } finally { selection.routingEntries = original; }
assert.equal(routing.readRoutingIndex(stateRoot).entries.find(entry => entry.id === 'skill:feature').description, stored);
// The drift is the generation's, and the next build reports it.
assert.throws(() => routing.writeRoutingIndex({ repoRoot, stateRoot }), /stale/);
}));
test('a skill edited during the build never publishes an index under the stored generation', () => fixture(({ repoRoot, stateRoot }) => {
store.applyStore({ repoRoot, stateRoot, target: 'claude' });
const selection = require('../../scripts/lib/context-selection');
const original = selection.routingEntries;
selection.routingEntries = options => {
const file = path.join(repoRoot, 'skills/feature/SKILL.md');
fs.writeFileSync(file, fs.readFileSync(file, 'utf8').replace(/description: .*/, 'description: Edited mid-build'));
return original(options);
};
try {
assert.throws(() => routing.writeRoutingIndex({ repoRoot, stateRoot }), /changed|stale/);
} finally { selection.routingEntries = original; }
assert.equal(routing.readRoutingIndex(stateRoot), null);
}));
for (const change of [
receipt => ({ ...receipt, revision: '1; echo injected' }),
receipt => ({ ...receipt, revision: 0 }),
receipt => ({ ...receipt, schemaVersion: 'unexpected' }),
receipt => ({ ...receipt, selection: { ...receipt.selection, selectionMode: 'invalid' } }),
]) {
test(`self-consistent receipt metadata fails closed: ${change.toString()}`, () => fixture(({ repoRoot, stateRoot }) => {
store.applyStore({ repoRoot, stateRoot, target: 'claude' });
const { digestObject } = require('../../scripts/lib/context-profile-support');
const statePath = path.join(stateRoot, 'state.json');
const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
const receipt = change(JSON.parse(fs.readFileSync(path.join(stateRoot, 'receipts', `${state.receiptDigest}.json`), 'utf8')));
const digest = digestObject(receipt);
fs.writeFileSync(path.join(stateRoot, 'receipts', `${digest}.json`), JSON.stringify(receipt));
fs.writeFileSync(statePath, JSON.stringify({ ...state, receiptDigest: digest }));
assert.throws(() => routing.readBinding(stateRoot), /receipt|state|integrity/i);
}));
}
test('actual pointer bytes are bounded again after the preliminary stat', () => fixture(({ repoRoot, stateRoot }) => {
store.applyStore({ repoRoot, stateRoot, target: 'claude' });
routing.writeRoutingIndex({ repoRoot, stateRoot });
const io = require('../../scripts/lib/context-profile-store-fs');
const original = io.read;
io.read = file => {
const bytes = original(file);
return path.dirname(file) === path.join(stateRoot, 'routing')
? Buffer.concat([bytes, Buffer.alloc(4096, 0x20)]) : bytes;
};
try { assert.throws(() => routing.readRoutingIndex(stateRoot), /integrity|bound|limit/i); }
finally { io.read = original; }
}));
test('one weak name-term overlap is ranking noise rather than a suggestion', () => {
const index = { entries: [{ id: 'skill:schema-tool', name: 'schema-tool', description: 'Generic helper' }] };
assert.deepEqual(routing.suggestContext(index, 'migrate schema records carefully'), []);
});