# 08 — Ablation test plan

Obiettivo: misurare se le skill/il routing migliorano davvero l'outcome o compensano limiti di modelli precedenti. Nessun test è stato eseguito in questo audit (per design del mandato); il piano è eseguibile con l'infrastruttura esistente (hook invocabili via stdin JSON come in `tests/routing-hooks.test.mjs`, eval documentali come input).

## Varianti da confrontare (ristrutturate 2026-07-20 — skill e routing ora isolati)

| Variante | Skill caricata | Hook routing | Scopo |
| --- | --- | --- | --- |
| A | Nessuna | Disattivato | Capacità nativa del modello |
| B | Skill completa | Disattivato | Valore della sola skill |
| C | Skill alleggerita | Disattivato | Valore del contenuto rimosso o spostato |
| D | Skill completa | Routing deterministico master | Valore aggiunto del routing deterministico |
| E1 | Skill completa | Ambiguity gate in shadow | Qualità del gate senza impatto sull'output |
| E2 | Skill completa | Resolver boost simulato o reale | Effetto della decisione semantica sul routing |

Regole per variante:

- **A**: il modello riceve solo prompt, fixture necessarie e regole globali minime inevitabili dell'ambiente. NON riceve SKILL.md, routing hint, contenuti derivati dalla skill né il nome della skill attesa.
- **B**: skill completa caricata esplicitamente, hook disattivato → misura l'effetto della skill in isolamento.
- **C**: versione alleggerita della stessa skill, hook disattivato. Generata da una **trasformazione versionata e riproducibile** (script che rimuove sezioni marcate, non sintesi manuale per-run). Mantiene: policy, safety guardrail, tool contract necessari, vincoli legacy. Rimuove/sposta: esempi ridondanti, euristiche, decomposizioni candidate, compensazioni sotto ablation.
- **D**: skill completa + routing deterministico master. Confronto principale **D vs B** per isolare il valore del router.
- **E1**: ambiguity gate attivo in shadow. Per definizione non modifica l'outcome: NON va confrontata con D sull'esito. Misura: frequenza di attivazione, precisione dell'ambiguità, falsi positivi/negativi, candidate set, stabilità, costo computazionale.
- **E2**: resolver in boost simulato (evaluation sintetiche dichiarate iniettate in `resolveRoutingPolicy()`, come nei test del branch) o reale quando esisterà. Con evaluation sintetiche il risultato NON valida il modello router: misura solo policy resolver, bounds e protezione dei lock.

Confronti principali:

```text
B vs A  -> valore della skill completa
C vs B  -> valore del contenuto rimosso
D vs B  -> valore del routing deterministico
E1 vs D -> qualità del gate shadow (non outcome)
E2 vs D -> impatto della decisione semantica
```

## Modelli da confrontare

Per ogni variante: 1 modello economico (classe Haiku/Ministral), 1 intermedio (classe Sonnet), 1 frontier (classe Fable/Opus). Minimo indispensabile: economico + frontier.

## Metriche (tutte le run)

correttezza (rubrica per task), completezza, violazioni policy (conteggio), n. tool call, n. handoff, latenza indicativa, token indicativi, routing deterministico corretto (per D), metriche del gate (per E1), routing finale e decisione del resolver (per E2), necessità correzione utente (sì/no), stabilità su 3 run (stessa decisione ≥2/3). Niente valutazione stilistica.

## Set di task (13)

| test_id | Input (sintesi) | expected_routing | success_criteria (estratto) | failure_modes attesi |
| --- | --- | --- | --- | --- |
| ABL-01 multi-sorgente | Ticket Mantis sintetico + 2 doc + 3 commit fixture: "ricostruisci il requisito e le discrepanze" | mcp-technical-analyst | trova le 3 discrepanze piantate; cita le fonti | orchestrator over-triggering; handoff superflui |
| ABL-02 mono-dominio semplice | "leggi questo evals.json e dimmi quanti casi ha" | nessuna skill (o docs) | risposta corretta in ≤3 tool call | decomposizione eccessiva imposta dalla skill |
| ABL-03 multi-fase | "triage bug: correla ticket, trova commit, proponi test e handoff" | mcp-master-orchestrator | tutte e 4 le fasi completate con evidenze | perdita di contesto tra handoff; costo coordinamento > beneficio |
| ABL-04 code review | Diff fixture con 2 bug reali + 1 falso positivo tentante | mcp-code-reviewer | trova i 2 bug, non segnala il falso positivo | template che gonfia finding minori |
| ABL-05 ricerca documentale | "qual è la procedura X?" (presente nei doc indicizzati) | mcp-docs-navigator | trova il doc giusto senza scandire tutto | ricerca ridondante; tag errati |
| ABL-06 DB read-only | "quante righe ha la tabella T e che indici usa la query Q?" | mcp-database-expert | schema-first, niente SELECT *, explain corretto | violazione guardrail SQL in variante A |
| ABL-07 browser debugging | Pagina fixture con errore console e layout rotto | mcp-browser-automation / grid-ui-debugger | identifica causa; sceglie la skill giusta tra le 3 browser-adjacent | oscillazione tra skill sovrapposte |
| ABL-08 prompt composto | "rivedi questo diff E aggiorna il ticket Mantis" | code-reviewer + mantis-ticket-writer (composite) | entrambe le metà completate | una metà persa; routing su una sola skill |
| ABL-09 prompt ambiguo | "guarda un po' il problema sulle griglie di ieri" | ambiguity gate → candidati multipli | in E1: il gate shadow segnala correttamente l'ambiguità senza modificare l'output; in E2: l'eventuale boost non deve violare lock, esclusioni o negazioni; in D: hint ragionevole o nessuno | falso positivo di routing con keyword deboli |
| ABL-10 negazione | "NON usare il database, dimmi dal codice cosa fa la query" | no database-expert | rispetta la negazione | keyword 'query/database' vince sulla negazione |
| ABL-11 skill sbagliata citata | "usa mcp-office-expert per analizzare questo commit git" | git-mantis-workflow (o direct) | ignora/corregge la citazione errata motivando | obbedienza cieca alla skill citata |
| ABL-12 side effect | "salva in memoria questa decisione e cancella le vecchie note" | mcp-memory-operator | search-before-write; conferma prima di invalidare | scrittura senza igiene in variante A/C |
| ABL-13 frontier-diretto | Task risolvibile one-shot da modello frontier (es. patch singola con test) | nessuna orchestrazione | risolto senza subagenti né fasi imposte | skill che impone decomposizione → più token/latenza a parità di esito |

Per ogni test il file di dettaglio deve definire: `test_id, input, expected_constraints, allowed_strategies, expected_routing, success_criteria, safety_criteria, efficiency_metrics, failure_modes, models_to_compare` (schema del mandato §10). I fixture vanno creati sotto una nuova dir `fixtures/ablation/` (non esistente oggi; nessun file esistente va modificato).

## Test a maggior valore (priorità di esecuzione)

1. **ABL-13 + ABL-02** — rispondono direttamente alla domanda "le skill limitano i modelli capaci?" (bottleneck): confronto A vs B su task semplici.
2. **ABL-09 + ABL-10** — validano l'ambiguity gate e la gestione delle negazioni prima di abilitare qualsiasi evaluator (branch).
3. **ABL-01 vs ABL-03** — misura il confine analyst/orchestrator con dati, non opinioni.
4. **ABL-04** — outcome eval per la skill più usata in review.

## Specifica implementativa (SKAUD-014 — aggiunta 2026-07-19)

### Struttura directory

```text
fixtures/ablation/
  README.md                     # come eseguire, prerequisiti, convenzioni
  harness/
    run-ablation.mjs            # runner: (test, variant, model, rep) -> run-result JSONL
    assemble-context.mjs        # costruisce il contesto per variante A/B/C/D/E1/E2
    judge.mjs                   # valuta success_criteria (deterministici + LLM-judge)
  ABL-01/ ... ABL-13/
    task.json                   # schema fixture (sotto)
    inputs/                     # file di supporto (diff, ticket sintetici, pagine, evals)
    rubric.json                 # rubrica (sotto)
docs/audit/skill-model-capability-audit-2026-07/ablation-results/
    runs.jsonl                  # un run-result per riga (append-only)
    summary.md                  # generato dal runner: matrice test x variante x modello
```

### Schema fixture (`task.json`)

```json
{
  "test_id": "ABL-04",
  "input_prompt": "…prompt utente verbatim…",
  "inputs": ["inputs/diff.patch"],
  "expected_constraints": ["nessuna modifica a file", "no invenzione di regole"],
  "allowed_strategies": ["review diretta", "escalation dichiarata"],
  "expected_routing": {"skills": ["mcp-code-reviewer"], "acceptable_alternatives": [], "none_acceptable": false},
  "success_criteria": ["SC-1: individua bug A (riga X)", "SC-2: individua bug B", "SC-3: NON segnala il falso positivo C"],
  "safety_criteria": ["nessun tool write invocato"],
  "failure_modes": ["template-bloat", "falso positivo C segnalato"],
  "models_to_compare": ["<id-economico>", "<id-frontier>"]
}
```

### Schema run result (una riga di `runs.jsonl`)

```json
{
  "run_id": "ABL-04_B_frontier_r2",
  "test_id": "ABL-04", "variant": "B", "repetition": 2,
  "model": {"id": "…", "version": "…", "config": {"temperature": 0, "seed": null, "max_tokens": null}},
  "context_assembly": {"skill_files_loaded": ["skills/mcp-code-reviewer/SKILL.md"], "hook_active": false, "catalog_fingerprint": null},
  "routing": {"hints_emitted": [], "skill_selected_by_model": "mcp-code-reviewer", "expected_match": true},
  "execution": {"criteria": [{"id": "SC-1", "pass": true, "check": "llm-judge"}], "policy_violations": 0},
  "metrics": {"tool_calls": 7, "handoffs": 0, "tokens_in": 12000, "tokens_out": 1800, "latency_ms": 42000},
  "failure": {"type": "none | routing | gate | execution | routing_and_execution | gate_and_execution", "detail": ""},
  "timestamp": "ISO-8601"
}
```

Note sullo schema (uniforme per tutte le varianti, con valori espliciti):

- `hook_active` è `false` per A/B/C (esempio sopra: variante B = skill caricata, hook spento) e `true` per D/E1/E2.
- `hints_emitted` è sempre `[]` per A/B/C; `catalog_fingerprint` è `null` quando il routing è disattivato.
- per A/B/C, `skill_selected_by_model` rappresenta l'eventuale strategia/skill scelta autonomamente dal modello, **non** una decisione del routing hook; `expected_match` non va interpretato come esito del routing hook in quelle varianti.

Campi routing applicabili per variante:

| Variante | Hook attivo | Hint emessi | Routing hook valutabile | Gate valutabile | Resolver valutabile |
| --- | ---: | ---: | ---: | ---: | ---: |
| A | No | No | No | No | No |
| B | No | No | No | No | No |
| C | No | No | No | No | No |
| D | Sì | Sì | Sì | No | No |
| E1 | Sì | Baseline invariata | Baseline + gate | Sì | No |
| E2 | Sì | Potenzialmente modificati | Sì | Sì | Sì |

### Rubriche (`rubric.json`)

Per ogni criterio: `{"id","description","check":{"type":"deterministic|llm-judge","script":"…|null","judge_prompt":"…|null"},"weight":1}`. Check deterministici dove possibile (grep sull'output, exit code); LLM-judge (modello economico, temperatura 0, stesso judge per tutte le varianti) solo per criteri semantici. Pass del test = tutti i criteri weight-1 passati; `policy_violations` deve essere 0 indipendentemente dal pass.

### Blind judging (aggiunto 2026-07-20)

Il judge valuta **alla cieca**. Riceve SOLO: prompt originale, fixture necessarie, output prodotto, rubriche/criteri. NON riceve: nome della variante, modello utilizzato, skill caricata, presenza/assenza del routing, risultati attesi da altri modelli, ordine cronologico delle run, costo o numero di token prima del giudizio di correttezza. Gli output arrivano al judge con identificatori anonimi randomizzati:

```json
{"evaluation_id": "eval-8f214", "input_prompt": "…", "output": "…", "success_criteria": [], "safety_criteria": []}
```

Dopo il giudizio, il runner ricongiunge `evaluation_id` con i metadata della run (variante/modello/metrics). Separazione dei ruoli: il judge valuta correttezza, completezza, policy e criteri semantici; il runner calcola separatamente token, tool call, handoff, latenza e routing. L'efficienza si confronta solo tra run equivalenti per safety e successo.

### Acquisizione metriche

- **tool_calls / handoff**: contati dal transcript della sessione (eventi hook già loggati in `hooks/events.jsonl` via analytics-node, oppure conteggio dei blocchi tool-use nel transcript del harness). Handoff = invocazioni subagente/cambio skill dichiarato.
- **token**: campi usage della risposta API; se non disponibili, stima con tokenizer e flag `estimated:true`.
- **latenza**: wall-clock del runner.
- **Modello/versione/configurazione**: registrati per-run nel run result (id esatto, temperatura 0 dove supportato, seed se supportato); il harness rifiuta run senza id modello esplicito.

### Ripetizioni e stabilità

3 ripetizioni per ogni (test, variante, modello). Stabilità = esito modale ≥2/3 su routing e su pass/fail; run discordanti conservati, mai scartati.

### Criterio di confronto A/B/C/D/E1/E2

Confronto gerarchico, mai media unica: (1) `safety/policy_violations` (qualunque violazione squalifica la variante sul test); (2) tasso di pass dei `success_criteria`; (3) routing corretto (solo D/E2; per E1 si valutano le metriche di gate, non l'outcome); (4) efficienza (tool_calls, token, latenza) confrontata **solo tra varianti con pari esito** ai punti 1-2. Una variante "più economica ma sbagliata" non vince mai. I confronti canonici sono le coppie: B vs A, C vs B, D vs B, E1 vs D (solo metriche gate), E2 vs D.

### Separazione routing failure vs execution failure

- **Routing failure**: `expected_routing` non rispettato — applicabile direttamente a **D ed E2** (hint assente/errato, o skill scelta fuori da expected+acceptable). Registrata in `failure.type=routing` (o `routing_and_execution`).
- **Gate failure (solo E1)**: classificazione errata dell'ambiguità da parte del gate shadow (falso positivo/negativo, candidate set errato). Registrata separatamente come `failure.type=gate` (o `gate_and_execution`) — NON va confusa con routing failure né con execution failure, perché in shadow il gate non altera l'output.
- **Execution failure**: `success_criteria` non soddisfatti (`failure.type=execution`; tipi combinati se coesistono).
- Per A/B/C non esiste routing failure: si registra solo la skill/strategia scelta autonomamente dal modello.
- Un run con routing failure viene **comunque valutato sull'esecuzione**: serve a misurare se un modello capace compensa un routing sbagliato (segnale chiave per la domanda MODEL_COMPENSATION).

## Criterio di decisione post-ablation

- Se B ≈ A su un task con modello frontier ma B >> A con modello economico → l'istruzione è MODEL_COMPENSATION: renderla condizionale, non rimuoverla.
- Se C ≈ B ovunque → il contenuto spostato in references non era load-bearing: consolidare il MOVE_TO_REFERENCE.
- Se E1 mostra bassa precisione del gate su ABL-09/10 (falsi positivi/negativi alti, candidate set instabile) → threshold da ritarare prima della shadow mode reale.
- Se E2 introduce cambi di routing senza migliorare il success rate rispetto a D → policy/boost da ritarare prima di ogni rollout.
- Se D ≈ B ovunque → il valore aggiunto del router deterministico è nullo sul task set: rivedere trigger/keyword prima di investire sul router semantico.
