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:
haelyraandmontjeffrey committed 2026-10-10 17:48:20 -04:00
1 parent 810e5c0176
commit cc48be3c52
5 files changed
+82 -8

No files matched your search

+13 -4
View File
@@ -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);
+14 -2
View File
@@ -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 };