mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-26 18:35:05 +00: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:
5 files changed
+82
-8
No files matched your search
@@ -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 };
|
||||
Reference in new issue
Block a user