mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-18 05:57:40 +02:00
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>
This commit is contained in:
@@ -119,3 +119,5 @@ These are attributed adaptations of concepts; contributor commits have not been
|
||||
The store recovers actual process exits at five durable boundaries: prepared journal, file publication, generation publication, receipt publication and state publication. An interruption before the initial ownership marker is published, or a corrupted partial kernel write, is preserved for inspection. These cases do not receive an automatic recovery claim.
|
||||
|
||||
Small authenticated Claude pilots now provide task and token observations, but they are descriptive and the evaluation gate remains `review-required`. Adequately powered task-quality canaries and whole-context measurements need additional evidence. The opt-in interactive bootstrap has local source, discovery and terminal-start evidence, but authenticated task behavior and native skill invocation remain unobserved. Isolated Codex registration, switching, refresh and rollback have local native evidence; changing a live user installation still requires its own ownership and recovery contract. Fresh-install default changes, existing-user migration, other-provider activation, hook plans, ECC Tools compatibility, hosted rollout and package publication remain outside this local preview.
|
||||
|
||||
The optional routing index binds its advisory metadata, including its trigger snapshot, to the current managed receipt. Trigger content is not part of the carrier generation itself, so rebuilding can use newly curated triggers without changing that generation. No skill body or permission is admitted by the index; the resolver re-verifies canonical sources. Suggestions also require an exact name or the fallback's absolute evidence floor, plus the routing policy's name/trigger anchor, so weak ranking matches remain silent.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
const path = require('node:path');
|
||||
const io = require('./context-profile-store-fs');
|
||||
const { buildRetrievalIndex, searchRetrieval, sparseDense } = require('./context-retrieval');
|
||||
const { hasSuggestionEvidence } = require('./context-selection');
|
||||
const { DEFAULT_REPO_ROOT, digestObject, stableStringify } = require('./context-profile-support');
|
||||
|
||||
const SCHEMA = 'ecc.context-routing-index.v1';
|
||||
@@ -39,11 +40,16 @@ function readBinding(stateRoot) {
|
||||
throw new Error('Directory is not an owned ECC managed store');
|
||||
}
|
||||
const state = io.readJson(path.join(root, 'state.json'));
|
||||
if (state.schemaVersion !== 'ecc.context-store-state.v1' || !DIGEST.test(state.receiptDigest) || !DIGEST.test(state.generationDigest)) {
|
||||
if (state.schemaVersion !== 'ecc.context-store-state.v1' || !DIGEST.test(state.receiptDigest) || !DIGEST.test(state.generationDigest)
|
||||
|| !Number.isSafeInteger(state.revision) || state.revision < 1) {
|
||||
throw new Error('Managed state integrity mismatch');
|
||||
}
|
||||
const receipt = io.readJson(path.join(root, 'receipts', `${state.receiptDigest}.json`));
|
||||
if (digestObject(receipt) !== state.receiptDigest || receipt.generationDigest !== state.generationDigest) {
|
||||
if (receipt.schemaVersion !== 'ecc.context-store-receipt.v1' || digestObject(receipt) !== state.receiptDigest
|
||||
|| receipt.generationDigest !== state.generationDigest || receipt.destinationDigest !== marker.destinationDigest
|
||||
|| !Number.isSafeInteger(receipt.revision) || receipt.revision < 1 || receipt.revision !== state.revision
|
||||
|| !['auto', 'manual', 'suggest'].includes(receipt.selection?.selectionMode)
|
||||
|| stableStringify(receipt.selection) !== stableStringify(state.selection)) {
|
||||
throw new Error('Managed receipt and state integrity mismatch');
|
||||
}
|
||||
return { root, generationDigest: state.generationDigest, receiptDigest: state.receiptDigest,
|
||||
@@ -132,7 +138,9 @@ function readRoutingIndex(stateRoot, binding = readBinding(stateRoot)) {
|
||||
if (!io.inspect(path.dirname(file), true).stat || !io.inspect(file, true).stat) return null;
|
||||
const invalid = () => new Error('Routing index integrity mismatch; rebuild it with ecc profile routing-index');
|
||||
if (io.inspect(file).stat.size > MAX_POINTER_BYTES) throw invalid();
|
||||
const { pointerDigest, ...pointer } = io.readJson(file);
|
||||
const pointerBytes = io.read(file);
|
||||
if (pointerBytes.length > MAX_POINTER_BYTES) throw invalid();
|
||||
const { pointerDigest, ...pointer } = JSON.parse(pointerBytes.toString('utf8'));
|
||||
if (pointer.schemaVersion !== POINTER_SCHEMA || digestObject(pointer) !== pointerDigest || !DIGEST.test(pointer.indexDigest || '')
|
||||
|| !DIGEST.test(pointer.receiptDigest || '') || pointer.generationDigest !== binding.generationDigest
|
||||
|| !Number.isSafeInteger(pointer.bytes) || pointer.bytes <= 0 || pointer.bytes > MAX_INDEX_BYTES) throw invalid();
|
||||
@@ -166,7 +174,8 @@ const singleLine = text => String(text).replace(CONTROL_CHARACTERS, ' ').replace
|
||||
function suggestContext(index, prompt, { limit = MAX_SUGGESTIONS } = {}) {
|
||||
// Filter before trimming so unanchored matches cannot crowd out anchored ones.
|
||||
return searchRetrieval(buildRetrievalIndex(index.entries), prompt, { limit: index.entries.length })
|
||||
.filter(candidate => SKILL_ID.test(candidate.id) && (candidate.exact || candidate.anchorTerms.length > 0))
|
||||
.filter(candidate => SKILL_ID.test(candidate.id) && hasSuggestionEvidence(candidate)
|
||||
&& (candidate.exact || candidate.anchorTerms.length > 0))
|
||||
.slice(0, Math.min(limit, MAX_SUGGESTIONS))
|
||||
.map(candidate => {
|
||||
const description = singleLine(candidate.description);
|
||||
|
||||
@@ -26,6 +26,18 @@ const AUTO_ADMIT_MARGIN = 1.5;
|
||||
const FALLBACK_MIN_BM25 = 12;
|
||||
const FALLBACK_MIN_TERMS = 2;
|
||||
const FALLBACK_MARGIN = 1.1;
|
||||
|
||||
// Adapted from contributor PR #3480: rankings become suggestions only when
|
||||
// an exact name or the fallback's absolute evidence floor supports them.
|
||||
function meetsFallbackBar(candidate) {
|
||||
return Boolean(candidate && candidate.bm25 >= FALLBACK_MIN_BM25
|
||||
&& Array.isArray(candidate.matchedTerms) && candidate.matchedTerms.length >= FALLBACK_MIN_TERMS);
|
||||
}
|
||||
|
||||
function hasSuggestionEvidence(candidate) {
|
||||
return Boolean(candidate && (candidate.exact || meetsFallbackBar(candidate)));
|
||||
}
|
||||
|
||||
// Both tiers also require at least one matched term in the skill's name or
|
||||
// curated triggers. Description-only overlap on common words (two, both, same)
|
||||
// can clear the score bars without naming the skill's subject.
|
||||
@@ -259,7 +271,7 @@ function resolveTaskContext({ repoRoot = DEFAULT_REPO_ROOT, task, profileId = 'l
|
||||
&& !explicitIds.length && !proposedIds.length && candidates.length && !exactAnchors.length) {
|
||||
const top = candidates[0];
|
||||
const second = candidates[1];
|
||||
if (top.bm25 >= FALLBACK_MIN_BM25 && top.matchedTerms.length >= FALLBACK_MIN_TERMS
|
||||
if (meetsFallbackBar(top)
|
||||
&& top.anchorTerms.length >= MIN_ANCHOR_TERMS
|
||||
&& (!second || top.bm25 >= FALLBACK_MARGIN * (second.bm25 || 0))) {
|
||||
fallback = { id: top.id, bm25: top.bm25, matchedTerms: top.matchedTerms.length };
|
||||
@@ -310,4 +322,4 @@ function resolveDeclinedFallback(options, selection) {
|
||||
receipt: { ...receiptValue, receiptDigest: digestObject(receiptValue) } };
|
||||
}
|
||||
|
||||
module.exports = { resolveTaskContext, resolveDeclinedFallback, routingEntries };
|
||||
module.exports = { hasSuggestionEvidence, resolveTaskContext, resolveDeclinedFallback, routingEntries };
|
||||
|
||||
@@ -180,11 +180,14 @@ test('an index larger than the read bound fails before it is read', () => fixtur
|
||||
assert.throws(() => routing.readRoutingIndex(stateRoot), /integrity|bound/);
|
||||
}));
|
||||
|
||||
test('an anchored suggestion is not hidden by unanchored candidates ranked above it', () => {
|
||||
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: 'Generic helper', ownerModuleId: 'm', packId: 'p' }) };
|
||||
.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']);
|
||||
});
|
||||
|
||||
@@ -234,3 +237,41 @@ test('a skill edited during the build never publishes an index under the stored
|
||||
} 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'), []);
|
||||
});
|
||||
|
||||
@@ -334,3 +334,13 @@ test('admission denials carry stable codes', () => withFixture(root => {
|
||||
assert.throws(() => resolve(root, { query: 'shared', proposedIds: ['skill:shared'] }, { load: true }),
|
||||
error => error.code === 'ECC_CONTEXT_MANUAL_ONLY');
|
||||
}));
|
||||
|
||||
test('suggestion evidence requires an exact name or the absolute fallback floor', () => {
|
||||
const { hasSuggestionEvidence } = require('../../scripts/lib/context-selection');
|
||||
const candidate = values => ({ exact: false, bm25: 12, matchedTerms: ['feature', 'work'], ...values });
|
||||
assert.equal(hasSuggestionEvidence(candidate()), true);
|
||||
assert.equal(hasSuggestionEvidence(candidate({ bm25: 11.99 })), false);
|
||||
assert.equal(hasSuggestionEvidence(candidate({ matchedTerms: ['feature'] })), false);
|
||||
assert.equal(hasSuggestionEvidence(candidate({ exact: true, bm25: 1, matchedTerms: ['feature'] })), true);
|
||||
assert.equal(hasSuggestionEvidence(null), false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user