Spaces:
Sleeping
Sleeping
| /** | |
| * proofLedgerChain.test.ts — Regressione GAP-TRUTH-2 | |
| * | |
| * Verifica la correttezza della catena proof-ledger dopo la rimozione della | |
| * doppia scrittura introdotta erroneamente in toolExecutor.ts (GAP-TRUTH-2). | |
| * | |
| * BUG documentato (pre-fix): | |
| * toolExecutor.ts registrava proofLedger.record({ verified: true }) nel | |
| * momento del [✓ VERIFIED] stamp testuale, PRIMA che verifyToolResult() | |
| * in executeToolGatedFactory.ts potesse valutare la qualità del risultato. | |
| * Risultato: un git_push senza SHA nel result riceveva comunque una entry | |
| * { verified: true } nel ledger → checkClaimsAgainstLedger non emetteva | |
| * warning → falso silenzio su claim non provate. | |
| * | |
| * Copertura (7 test): | |
| * 1. BUG: doppia scrittura {true, false} → problems=[] (silenzio sbagliato) | |
| * 2. FIXED: singola scrittura {false} → problems.length===1 (warning corretto) | |
| * 3. Happy path: singola scrittura {true} → problems=[] (silenzio corretto) | |
| * 4. Ledger vuoto su claim → problems "nessuna azione registrata" | |
| * 5. Doppia categoria: file_write non provata, git_push provata → solo file_write in problems | |
| * 6. reset() garantisce isolamento tra run agente | |
| * 7. Tutte le categorie: ogni CATEGORY_TO_LEDGER_ACTIONS copre i tool corretti | |
| * | |
| * SESSIONE: GAP-TRUTH-2 | |
| */ | |
| import { describe, it, expect, beforeEach } from "vitest"; | |
| import { proofLedger } from "@/lib/agentLoop/proofLedger"; | |
| import { checkClaimsAgainstLedger } from "@/lib/agentLoop/actionClaimDetector"; | |
| import { CATEGORY_TO_LEDGER_ACTIONS, PROOF_VERIFIED_TOOLS, TOOL_TO_CATEGORY_LABEL } from "@/lib/agentLoop/proofToolRegistry"; | |
| import type { ClaimCategory } from "@/lib/agentLoop/proofToolRegistry"; | |
| // Reset prima di ogni test — fondamentale per isolamento | |
| beforeEach(() => { | |
| proofLedger.reset(); | |
| }); | |
| // ─── T1: BUG scenario — documenta la regressione ───────────────────────────── | |
| describe("T1 — BUG scenario (doppia scrittura, pre-fix)", () => { | |
| it( | |
| "verified:true poi verified:false → entries.some(verified)=true → problems=[] (silenzio sbagliato)", | |
| () => { | |
| // Simula il vecchio toolExecutor: record verified:true al momento dello stamp | |
| proofLedger.record({ | |
| action: "git_push", | |
| verified: true, | |
| evidence: "✅ Push completato", // nessun SHA — risultato non verificabile | |
| ts: Date.now(), | |
| }); | |
| // Poi executeToolGatedFactory: verifyToolResult → SHA assente → verified:false | |
| proofLedger.record({ | |
| action: "git_push", | |
| verified: false, | |
| evidence: "Push ok ma SHA non trovato nel result", | |
| ts: Date.now(), | |
| }); | |
| const snapshot = proofLedger.snapshot(); | |
| expect(snapshot).toHaveLength(2); | |
| const claimed = new Set<ClaimCategory>(["git_push"]); | |
| const problems = checkClaimsAgainstLedger(claimed, snapshot); | |
| // BUG: entries.some(e => e.verified) trova il primo record (true) | |
| // → warning non emesso → l'utente NON viene avvertito del push non provato | |
| expect(problems).toHaveLength(0); // documenta il comportamento SCORRETTO | |
| }, | |
| ); | |
| }); | |
| // ─── T2: FIXED scenario — comportamento corretto post-fix ──────────────────── | |
| describe("T2 — FIXED scenario (singola scrittura da verifier)", () => { | |
| it( | |
| "solo verified:false → entries.some(verified)=false → warning emesso", | |
| () => { | |
| // Post-fix: solo executeToolGatedFactory registra, con verità verificata | |
| proofLedger.record({ | |
| action: "git_push", | |
| verified: false, | |
| evidence: "✅ Push completato — SHA non trovato nel result (proof assente)", | |
| ts: Date.now(), | |
| }); | |
| const snapshot = proofLedger.snapshot(); | |
| expect(snapshot).toHaveLength(1); | |
| const claimed = new Set<ClaimCategory>(["git_push"]); | |
| const problems = checkClaimsAgainstLedger(claimed, snapshot); | |
| // CORRETTO: solo entry con verified:false → warning emesso | |
| expect(problems).toHaveLength(1); | |
| expect(problems[0]).toMatch(/commit\/push su Git/); | |
| expect(problems[0]).toMatch(/eseguito ma non verificato/); | |
| }, | |
| ); | |
| it( | |
| "verified:true (SHA reale nel result) → problems=[] (silenzio corretto)", | |
| () => { | |
| proofLedger.record({ | |
| action: "git_push", | |
| verified: true, | |
| evidence: "SHA=a1b2c3d4e5f6 pushed to origin/main", | |
| ts: Date.now(), | |
| }); | |
| const snapshot = proofLedger.snapshot(); | |
| const claimed = new Set<ClaimCategory>(["git_push"]); | |
| const problems = checkClaimsAgainstLedger(claimed, snapshot); | |
| // Prova presente e verificata → silenzio corretto | |
| expect(problems).toHaveLength(0); | |
| }, | |
| ); | |
| it( | |
| "ledger vuoto per categoria dichiarata → 'nessuna azione registrata'", | |
| () => { | |
| // Ledger vuoto (reset() già chiamato in beforeEach) | |
| const snapshot = proofLedger.snapshot(); | |
| expect(snapshot).toHaveLength(0); | |
| const claimed = new Set<ClaimCategory>(["deploy"]); | |
| const problems = checkClaimsAgainstLedger(claimed, snapshot); | |
| expect(problems).toHaveLength(1); | |
| expect(problems[0]).toMatch(/deploy/); | |
| expect(problems[0]).toMatch(/nessuna azione registrata/); | |
| }, | |
| ); | |
| }); | |
| // ─── T3: Scenari multi-categoria ────────────────────────────────────────────── | |
| describe("T3 — Multi-categoria", () => { | |
| it( | |
| "file_write non provata, git_push provata → solo file_write in problems", | |
| () => { | |
| // git_push verificato | |
| proofLedger.record({ action: "git_push", verified: true, evidence: "sha:xyz789", ts: Date.now() }); | |
| // write_file non verificato (es. errore ENOENT) | |
| proofLedger.record({ action: "write_file", verified: false, evidence: "❌ ENOENT", ts: Date.now() }); | |
| const snapshot = proofLedger.snapshot(); | |
| const claimed = new Set<ClaimCategory>(["git_push", "file_write"]); | |
| const problems = checkClaimsAgainstLedger(claimed, snapshot); | |
| expect(problems).toHaveLength(1); | |
| expect(problems[0]).toMatch(/scrittura\/modifica file/); | |
| expect(problems[0]).not.toMatch(/commit\/push su Git/); | |
| }, | |
| ); | |
| it( | |
| "apply_patch verified:true copre la categoria file_write → problems=[]", | |
| () => { | |
| proofLedger.record({ action: "apply_patch", verified: true, evidence: "patch applied", ts: Date.now() }); | |
| const snapshot = proofLedger.snapshot(); | |
| const claimed = new Set<ClaimCategory>(["file_write"]); | |
| const problems = checkClaimsAgainstLedger(claimed, snapshot); | |
| expect(problems).toHaveLength(0); | |
| }, | |
| ); | |
| }); | |
| // ─── T4: Isolamento sessione ─────────────────────────────────────────────────── | |
| describe("T4 — Isolamento sessione via reset()", () => { | |
| it( | |
| "reset() garantisce che prove della sessione precedente non contaminino quella corrente", | |
| () => { | |
| // Sessione 1: registra prove | |
| proofLedger.record({ action: "git_push", verified: true, evidence: "sha:aaa", ts: Date.now() }); | |
| expect(proofLedger.count()).toBe(1); | |
| // Reset (chiamato da agentLoop prima di ogni run) | |
| proofLedger.reset(); | |
| expect(proofLedger.count()).toBe(0); | |
| // Sessione 2: ledger pulito — claim git_push non ha prove | |
| const snapshot = proofLedger.snapshot(); | |
| const claimed = new Set<ClaimCategory>(["git_push"]); | |
| const problems = checkClaimsAgainstLedger(claimed, snapshot); | |
| expect(problems).toHaveLength(1); | |
| expect(problems[0]).toMatch(/nessuna azione registrata/); | |
| }, | |
| ); | |
| }); | |
| // ─── T5: Completezza CATEGORY_TO_LEDGER_ACTIONS ─────────────────────────────── | |
| describe("T5 — Completezza registry (ogni tool in PROOF_VERIFIED_TOOLS ha una categoria)", () => { | |
| it( | |
| "ogni tool in CATEGORY_TO_LEDGER_ACTIONS è coperto da una categoria (no orfani)", | |
| () => { | |
| const allMapped = Object.values(CATEGORY_TO_LEDGER_ACTIONS).flat(); | |
| const cats = Object.keys(CATEGORY_TO_LEDGER_ACTIONS) as ClaimCategory[]; | |
| for (const tool of allMapped) { | |
| const belongsToCategory = cats.some(cat => | |
| CATEGORY_TO_LEDGER_ACTIONS[cat].includes(tool), | |
| ); | |
| expect(belongsToCategory, `${tool} non appartiene a nessuna categoria`).toBe(true); | |
| } | |
| }, | |
| ); | |
| it( | |
| "git_push_multiple è nella categoria git_push (verifica alias variante)", | |
| () => { | |
| proofLedger.record({ action: "git_push_multiple", verified: false, evidence: "auth error", ts: Date.now() }); | |
| const snapshot = proofLedger.snapshot(); | |
| const claimed = new Set<ClaimCategory>(["git_push"]); | |
| const problems = checkClaimsAgainstLedger(claimed, snapshot); | |
| // git_push_multiple è nelle azioni di git_push → catturato correttamente | |
| expect(problems).toHaveLength(1); | |
| expect(problems[0]).toMatch(/eseguito ma non verificato/); | |
| }, | |
| ); | |
| }); | |
| // ─── T6: Regressione create_file in PROOF_VERIFIED_TOOLS ────────────────────── | |
| // Riproduce il bug dove emitFinalText._CRIT_TOOLS locale mancava create_file. | |
| // La 5ª copia locale aveva: write_file, apply_patch, create_project, run_code, | |
| // execute_shell, run_python, git_push, git_push_multiple, deploy_trigger (9 tool). | |
| // PROOF_VERIFIED_TOOLS ha 10 tool — create_file era il buco. | |
| describe("T6 — Regressione: create_file presente in PROOF_VERIFIED_TOOLS", () => { | |
| it( | |
| "create_file è in PROOF_VERIFIED_TOOLS (non deve mai sparire)", | |
| () => { | |
| expect(PROOF_VERIFIED_TOOLS.has("create_file")).toBe(true); | |
| }, | |
| ); | |
| it( | |
| "create_file non verificato → warning in checkClaimsAgainstLedger", | |
| () => { | |
| proofLedger.record({ action: "create_file", verified: false, evidence: "write failed", ts: Date.now() }); | |
| const snapshot = proofLedger.snapshot(); | |
| const claimed = new Set<ClaimCategory>(["file_write"]); | |
| const problems = checkClaimsAgainstLedger(claimed, snapshot); | |
| expect(problems).toHaveLength(1); | |
| expect(problems[0]).toMatch(/eseguito ma non verificato/); | |
| }, | |
| ); | |
| it( | |
| "create_file verificato → nessun warning", | |
| () => { | |
| proofLedger.record({ action: "create_file", verified: true, evidence: "ok:sha123", ts: Date.now() }); | |
| const snapshot = proofLedger.snapshot(); | |
| const claimed = new Set<ClaimCategory>(["file_write"]); | |
| const problems = checkClaimsAgainstLedger(claimed, snapshot); | |
| expect(problems).toHaveLength(0); | |
| }, | |
| ); | |
| }); | |
| // ─── T7: Validazione TOOL_TO_CATEGORY_LABEL derived map ────────────────────── | |
| // Verifica che TOOL_TO_CATEGORY_LABEL (la mappa derivata che ha sostituito _critLabel()) | |
| // copra tutti i tool in PROOF_VERIFIED_TOOLS e ritorni label leggibili. | |
| describe("T7 — TOOL_TO_CATEGORY_LABEL copertura completa", () => { | |
| it( | |
| "ogni tool in PROOF_VERIFIED_TOOLS ha un entry in TOOL_TO_CATEGORY_LABEL", | |
| () => { | |
| for (const tool of PROOF_VERIFIED_TOOLS) { | |
| const label = TOOL_TO_CATEGORY_LABEL.get(tool); | |
| expect(label, `${tool} non ha label in TOOL_TO_CATEGORY_LABEL`).toBeDefined(); | |
| expect(label!.length, `${tool} ha label vuota`).toBeGreaterThan(0); | |
| } | |
| }, | |
| ); | |
| it( | |
| "git_push → 'commit/push su Git' · write_file → 'scrittura/modifica file' · deploy_trigger → 'deploy'", | |
| () => { | |
| expect(TOOL_TO_CATEGORY_LABEL.get("git_push")).toBe("commit/push su Git"); | |
| expect(TOOL_TO_CATEGORY_LABEL.get("write_file")).toBe("scrittura/modifica file"); | |
| expect(TOOL_TO_CATEGORY_LABEL.get("deploy_trigger")).toBe("deploy"); | |
| expect(TOOL_TO_CATEGORY_LABEL.get("create_file")).toBe("scrittura/modifica file"); | |
| }, | |
| ); | |
| it( | |
| "TOOL_TO_CATEGORY_LABEL dimensione = PROOF_VERIFIED_TOOLS dimensione (bijection check)", | |
| () => { | |
| // Ogni tool in PROOF_VERIFIED_TOOLS deve avere esattamente 1 label (no duplicati, no orfani). | |
| expect(TOOL_TO_CATEGORY_LABEL.size).toBe(PROOF_VERIFIED_TOOLS.size); | |
| }, | |
| ); | |
| }); | |