AUDIT / src /lib /agentLoop /actionClaimDetector.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 2)
cc11e77 verified
Raw
History Blame Contribute Delete
6.03 kB
/**
* actionClaimDetector.ts — GAP-TRUTH: rileva affermazioni di azioni completate
* nel testo finale e le confronta con proofLedger.
*
* Problema risolto: il finalOutputGate decideva "richiede prova?" guardando
* `steps` (i tool effettivamente chiamati) — ma un LLM può scrivere
* "Ho fatto il commit e il deploy" SENZA aver chiamato git_push/deploy_trigger
* affatto. In quel caso `steps` non contiene alcun CRIT_TOOL → il gate
* (versione precedente) passava SEMPRE, perché `requiresProof` derivava
* solo dai tool call, non dal TESTO.
*
* Questo modulo chiude il cerchio: legge il TESTO (cosa l'LLM dichiara di
* aver fatto) e lo incrocia con proofLedger (cosa è stato VERAMENTE
* verificato), indipendentemente da `steps`.
*
* Design:
* - Solo pattern in PRIMA PERSONA + TEMPO COMPLETATO ("ho fatto", "I've done",
* "completato", "successfully") — evita falsi positivi su spiegazioni
* generiche ("quando fai il commit...", "per pushare devi...").
* - Pure, sync. Unico import: type-only da proofLedger (zero costo runtime).
* - Best-effort: in caso di eccezione interna, ritorna "nessuna claim"
* (mai bloccante — il blocco fail-closed vero è in finalOutputGate
* per garbage/empty, non qui).
*
* @module actionClaimDetector
*/
import type { Proof } from "./proofLedger";
// GAP-TRUTH-3: ClaimCategory, CATEGORY_LABELS, CATEGORY_TO_LEDGER_ACTIONS → single source of truth
import {
type ClaimCategory,
CATEGORY_LABELS,
CATEGORY_TO_LEDGER_ACTIONS,
} from "./proofToolRegistry";
// Re-export per backward compat dei consumer esistenti (emitFinalText.ts, bench-truth-gate.mjs)
export type { ClaimCategory };
export { CATEGORY_LABELS, CATEGORY_TO_LEDGER_ACTIONS };
// ClaimCategory, CATEGORY_LABELS, CATEGORY_TO_LEDGER_ACTIONS → vedi proofToolRegistry.ts (re-exportati sopra)
// ─── Pattern: prima persona + completamento (IT + EN) ─────────────────────────
// Tutti richiedono "ho/I've/I have/è stato/has been/successfully" + verbo al
// participio passato — NON matchano imperativi, futuro, o spiegazioni generiche.
const _CLAIM_PATTERNS: Record<ClaimCategory, RegExp[]> = {
git_push: [
/\b(ho|abbiamo)\s+(già\s+)?(fatto\s+(il\s+)?)?(commit(t?ato)?|push(at[oi])?|spint[oi])\b/i,
/\b(commit|push)\s+(e\s+(commit|push)\s+)?(è\s+stat[oi]\s+|sono\s+stat[ei]\s+)?(effettuat[oi]|complet[ao]t[oi]|fatt[oi]|riuscit[oi])\b/i,
/\bho\s+(già\s+)?(caricat[oi]|invi?at[oi])\s+(le\s+)?modifiche/i,
/\bmodifiche\s+(caricate|pushate|inviate|già\s+su)\s+(su\s+)?(git(hub)?|repo)/i,
/\bI(?:'ve| have)\s+(already\s+)?(committed|pushed|push(ed)?\s+(the\s+)?changes)\b/i,
/\b(commit|push)(ing)?\s+(is\s+)?(done|completed|successful)\b/i,
/\bsuccessfully\s+(pushed|committed)\b/i,
/\bchanges\s+(have\s+been|are)\s+(pushed|committed)\b/i,
],
deploy: [
/\b(ho|abbiamo)\s+(già\s+)?(fatto\s+(il\s+)?)?deploy(at[oi])?\b/i,
/\bdeploy\s+(è\s+stat[oi]\s+|sono\s+stat[ei]\s+)?(effettuat[oi]|complet[ao]t[oi]|riuscit[oi])\b/i,
/\b(il\s+)?sito\s+(è\s+|risulta\s+)?(ora\s+)?(online|pubblicat[oi]|live|aggiornat[oi])\b/i,
/\bI(?:'ve| have)\s+(already\s+)?deployed\b/i,
/\bdeploy(ment)?\s+(is\s+)?(done|completed|successful|live)\b/i,
/\bsuccessfully\s+deployed\b/i,
/\b(the\s+)?site\s+is\s+(now\s+)?(live|online|updated)\b/i,
],
file_write: [
/\b(ho|abbiamo)\s+(già\s+)?(creat[oi]|salvat[oi]|scritt[oi]|aggiornat[oi]|modificat[oi]|sovrascritt[oi])\s+(il\s+)?file/i,
/\bfile\s+(è\s+stat[oi]\s+|sono\s+stat[ei]\s+)?(creat[oi]|salvat[oi]|aggiornat[oi]|scritt[oi]|modificat[oi])\b/i,
/\bI(?:'ve| have)\s+(already\s+)?(created|saved|written|updated|overwritten)\s+(the\s+)?file/i,
/\b(the\s+)?file\s+(has\s+been|is\s+now)\s+(created|saved|updated|written)\b/i,
],
code_exec: [
/\b(ho|abbiamo)\s+(già\s+)?eseguit[oi]\s+(il\s+)?codice/i,
/\bcodice\s+(è\s+stat[oi]\s+)?eseguit[oi]\s+(con\s+successo)?/i,
/\bI(?:'ve| have)\s+(already\s+)?(run|executed)\s+(the\s+)?code/i,
/\bcode\s+(has\s+)?(ran|executed|run)\s+successfully\b/i,
],
};
/**
* Rileva quali categorie di azione il TESTO dichiara come completate.
* Scansiona tutte le categorie; ritorna l'insieme di quelle con almeno
* un pattern match in prima persona + completamento.
*/
export function detectClaimedCategories(text: string): Set<ClaimCategory> {
const out = new Set<ClaimCategory>();
if (!text || text.length < 8) return out;
try {
// Limita la scansione ai primi 4000 char — i claim di completamento
// stanno tipicamente in apertura/chiusura della risposta, non nel mezzo
// di blocchi di codice lunghi.
const sample = text.length > 4000 ? text.slice(0, 2000) + text.slice(-2000) : text;
for (const cat of Object.keys(_CLAIM_PATTERNS) as ClaimCategory[]) {
if (_CLAIM_PATTERNS[cat].some(re => re.test(sample))) out.add(cat);
}
} catch { /* best-effort — mai bloccante */ }
return out;
}
/**
* Per ogni categoria dichiarata, verifica se proofLedger ha almeno una prova
* VERIFICATA per i tool corrispondenti.
*
* @returns lista di etichette leggibili per le categorie PROBLEMATICHE
* (claim presente, ma nessuna prova verificata) — vuota se tutto ok.
*/
export function checkClaimsAgainstLedger(
claimed: Set<ClaimCategory>,
snapshot: readonly Proof[],
): string[] {
const problems: string[] = [];
try {
for (const cat of claimed) {
const actions = CATEGORY_TO_LEDGER_ACTIONS[cat];
const entries = snapshot.filter(p => actions.includes(p.action));
if (entries.length === 0) {
problems.push(`${CATEGORY_LABELS[cat]} (nessuna azione registrata)`);
} else if (!entries.some(e => e.verified)) {
problems.push(`${CATEGORY_LABELS[cat]} (eseguito ma non verificato)`);
}
// entries.some(verified) → almeno una prova verificata per questa categoria → OK
}
} catch { /* best-effort — mai bloccante */ }
return problems;
}