Ctrl K
ComunidadGuías…

TODO.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# TODO — Workflow di ricerca, qualità e piattaforma estendibile

Backlog operativo emerso dalla review del flusso `archivio → annotazione →
dataset → fine-tuning → valutazione`. Non sostituisce la roadmap in
`AGENTS.md`: ne dettaglia le priorità e aggiunge la direzione multi-dominio e
multi-modello. Historic Shipping Index e MonkeyOCRv2 restano il primo profilo di dominio
e il primo adapter modello, non limiti architetturali.

## Controllo review tecnica — aggiornato 2026-08-31

Checklist di controllo derivata dalla review allegata. Viene aggiornata
progressivamente: `[x]` significa chiuso e verificato, `[ ]` significa ancora
aperto oppure solo parzialmente coperto.

### P0 — sicurezza e integrità collaborativa

- [x] Autosave concorrente: revisione pagina, `expected_revision`, risposta
  `409` con stato remoto e riconciliazione esplicita nell’annotatore.
- [x] Condivisione progetto: membri, ruoli editor/viewer, rimozione,
  trasferimento owner, elenco candidati e audit delle operazioni principali.
- [x] Segreti cloud/API: rimosso il salvataggio delle chiavi nel browser;
  il vault server-side cifrato è implementato, `cryptography` è una dipendenza
  obbligatoria verificata dagli installer e dalla suite del virtualenv Python
  3.12; la CI installa lo stesso `requirements.txt`.
- [x] SSRF di inferenza/playground: endpoint riservati, URL validati,
  destinazioni private bloccate e redirect HTTP non seguiti.

### P1 — operatività, compute e persistenza

- [x] Profili compute atomici: adapter, endpoint, modello servito, runtime,
  hardware, health check e attivazione senza perdere il profilo precedente.
- [x] Job locali persistiti in SQLite, con PID/process group, log, stop e
  riconciliazione dei processi dopo il riavvio del backend.
- [x] Backup SQLite online, integrity check, retention, download e restore
  amministrativo con conferma.
- [x] Audit append-only per autenticazione amministrativa e operazioni
  sensibili su progetti, annotazioni, utenti, compute e training.
- [x] Cookie configurabile `Secure`, rate limiting persistente per auth, CORS
  ristretto, security headers/CSP e verifica SSH tramite known_hosts.
- [x] Vincolo FK reale su `projects.owner_id` e migrazione completa delle
  invarianti di ownership: migrazione SQLite v12 con `ON DELETE RESTRICT`,
  più regressione su foreign key, cancellazione e disattivazione dell’owner.
- [ ] Persistenza/recovery uniforme per tutti gli orchestratori cloud e
  training remoto; locale, Modal e gli identificativi Vast/RunPod sono
  persistiti e riconciliati, ma manca ancora la verifica end-to-end uniforme
  dei lifecycle remoti.
- [ ] Executor remoto (locale/SSH/Vast/RunPod), manifest, checksum, upload,
  checkpoint, resume, stop, cleanup, costi e riferimenti credenziali.
  Contratti e percorsi principali sono implementati e testati; resta da
  eseguire il ciclo completo contro provider reali.
- [ ] Recipe Vast/RunPod riproducibili: versioni pinnate, branch/commit
  dichiarati, hardware verificato, manifest e test del contratto API.
  Il contratto locale è coperto; manca la prova operativa completa sui
  provider.

### P1/P2 — qualità di prodotto e verifica

- [x] UI di collaborazione: accesso al progetto con aggiunta/rimozione,
  modifica inline del ruolo, trasferimento owner e attività recente leggibile.
- [ ] UI completa per impostazioni, accessi/attività, capability dichiarate,
  studio responsive e componenti/modal coerenti.
- [x] Test auth/annotazione non bloccanti e matrice CI sulle versioni Python
  supportate; la suite backend completa passa nel virtualenv Python 3.12
  allineato (`240 passed`) e la CI copre Python 3.11–3.13.
- [ ] Pilot reale da 30–50 pagine con LoRA, gold set protetto, analisi errori
  e successiva scala a 150–300 pagine.

### Evidenze dell’ultima tranche

- La suite backend completa è ora eseguibile anche nel virtualenv Python 3.14
  tramite il client ASGI confinato ai test: `225 passed, 3 skipped`.
  Gli skip sono espliciti per `cryptography` assente nel virtualenv e scansioni
  reali opzionali; nel virtualenv Python 3.12 con `cryptography` installata
  passa `240 passed`; frontend verificato con `79 passed` e build completata.
- I confini verticali piegati (`row_columns`) e i flag
  (`row_columns_proven`) fanno ora parte del contratto `TableGrid`, vengono
  salvati nel JSON della tabella e sono riutilizzati dall’overlay dopo il
  caricamento; l’API valida cardinalità, monotonia e normalizzazione;
  regressione struttura rilevatore: `4 passed`.
- `ProjectDetailPage` carica `/activity`, aggiorna la cronologia dopo le
  modifiche di accesso e mantiene i nomi lunghi leggibili senza mostrare
  payload tecnici; `npm run build` passa e il detector UI non segnala rilievi.
- `SettingsPage` è ora realmente universale: account, ruolo, email e lingua
  sono disponibili anche a editor/viewer; le sezioni amministrative restano
  condizionate dal ruolo.
- `ModelsModal` usa la primitiva `Modal` condivisa (focus trap e ripristino del
  focus), senza shadow/radii divergenti o glyph Unicode; build e detector UI
  restano puliti.
- `AnnotationPage` espone di nuovo poligono, panoramica, flow preview, ordine
  di lettura e checklist delle convenzioni; i controlli esistenti ricevono i
  tool e le azioni dello store reale. Build TypeScript verificata.
- Lo studio ha ora una modalità persistita per utente: `Tutto`, `Solo canvas`
  e `Solo trascrizione`; la prima conserva gli splitter, le altre collassano
  i pannelli non necessari per viewport stretti. Build, test frontend e
  detector UI passano; resta da validare il comportamento su dispositivi reali.
- Test frontend: `13` file e `79` test passati (`npm run test -- --run`).
- Il contratto training remoto è ora verificato anche al confine API: `TrainConfig`
  conserva repository e interprete remoti; recipe Vast/RunPod usa dataset,
  cache e checkpoint relativi alla directory sincronizzata. Test mirati
  trainer/executor: `24 passed`; build frontend e detector UI passano.
- `.github/workflows/ci.yml` verifica il backend su Python 3.11, 3.12 e 3.13;
  Python 3.14 resta esplicitamente fuori finché lo stack TestClient non è
  compatibile e verificato.
- La promessa README sul merge LoRA è stata resa onesta: il prodotto conserva
  adapter, recipe e manifest verificati; resta aperta l’integrazione del merge
  ufficiale verso un checkpoint servibile, con test end-to-end.
- La UI del Training Center espone ora executor locale/SSH/Vast/RunPod e i
  parametri SSH senza salvare segreti; lo schema API conserva anche repository
  e Python remoti. Lo script usa percorsi relativi alla recipe sincronizzata,
  quindi dataset, cache e checkpoint non puntano più al filesystem locale.
  Il trainer persiste e recupera il `remote_job_id` anche per Vast/RunPod. Il
  lifecycle remoto completo resta aperto finché non sono verificati upload,
  checkpoint/resume, cleanup e costi end-to-end.
- Il resume è ora esplicito tramite `resume_run_id`: valida che la run
  appartenga al progetto, copia solo i checkpoint locali verificati nella
  nuova recipe e li sincronizza anche verso l’executor remoto.
- Il provisioning Vast richiede ora un `monkeyocr_ref` esplicito (commit SHA o
  tag verificato); il ref viene passato all’onstart e la chiave server non è
  più interpolata nello script, ma solo nella variabile d’ambiente dell’istanza.
  Contract test: ref obbligatorio, quoting e assenza del plaintext nell’onstart;
  `bash -n` dello script passa.
- La UI blocca ora il noleggio automatico Vast finché il ref pin-nato non è
  compilato, evitando un’azione che il backend rifiuterebbe.
- Verificato anche l’ordine del parsing nello script: `--ref` viene elaborato
  prima del controllo obbligatorio. Il contract test Vast passa (`1 passed`).
- La recipe cloud controlla ora la compute capability minima per BF16 e salva
  nel `cloud-manifest.json` recipe ID, Python, Transformers, dtype, limiti
  vLLM, GPU memory utilization e capability hardware.
- Anche lo script `vast_onstart.sh` e il comando manuale RunPod richiedono
  esplicitamente il ref pin-nato; non esiste più un esempio operativo che
  avvii il setup con il branch implicito.
- I task Modal persistono ora PID, process group, template, log, stato ed
  owner, exit code in `jobs`; dopo un riavvio `status()` recupera log e task ancora
  vivi invece di perdere lo stato singleton; lo stop dell’app interrompe
  anche un deploy recuperato. Regressioni Modal: `2 passed`.
- Anche il serving locale dei modelli persiste ora `serve` in `jobs` con PID,
  process group, log, owner e comando; `status`/`stop` recuperano un server
  ancora vivo dopo il riavvio del backend. Regressione lifecycle + recovery:
  `2 passed` (suite dedicata verificata fino al limite noto del TestClient).
- Il lifespan esegue anche il reconcile dei job `serve`: PID non più presenti
  vengono marcati `failed` invece di restare bloccati in `running`; la suite
  non HTTP del serving ora copre lifecycle, recovery e PID morto (`10 passed`).
- Verificato il grafo frontend: Handsontable era dichiarato ma non usato e
  viene rimosso da `package.json` e `package-lock.json`; React Query resta
  perché è usato da `main.tsx`. Build e test frontend: `66 passed`.
- La documentazione ora dichiara il supporto backend realmente coperto dalla
  CI (Python 3.11–3.13); Python 3.14 resta esplicitamente pendente per il
  blocco noto nell’adapter TestClient/AnyIO.
- Il training remoto espone ora un cleanup esplicito e autenticato: rifiuta
  run attive, valida il `run_id` dentro la directory del progetto, rimuove
  solo la directory remota già composta dal provider e lascia intatti i
  checkpoint locali; l’azione UI richiede una seconda pressione. Contract test
  SSH e training: `25 passed`.
- La documentazione chiarisce i confini multipiattaforma: dashboard, OCR CPU,
  annotazione ed export funzionano su Linux/macOS/Windows; serving e training
  CUDA locale richiedono Linux o WSL2, mentre SSH/Vast/RunPod sono disponibili
  da tutti i client. Gli installer frontend usano ora `npm ci` e il lockfile
  è verificato con `npm ci --dry-run --offline`.
- `/api/system/info` dichiara ora le capability della macchina (`dashboard`,
  `cpu_ocr`, `local_cuda`, `remote_gpu`) e l’Ambiente le mostra in UI; su
  Windows espone esplicitamente il percorso CUDA via WSL2.
- Il refresh delle istanze Vast/RunPod registra ora anche risorse già create
  fuori da Tabularium, aggiorna stato/tariffa e calcola la stima persistita;
  regressione refresh Vast: `2 passed` insieme al contract test di provisioning.
- La disattivazione di un utente proprietario ora restituisce `409` finché i
  suoi progetti non vengono trasferiti; aggiunta regressione in
  `tests/test_auth.py`.
- Il tunnel SSH cloud persiste ora PID, process group e parametri in `jobs`;
  dopo il riavvio il backend riconcilia il processo e lo stop verifica che il
  PID sia ancora un processo `ssh`. Gli orchestratori cloud restano comunque
  aperti per lifecycle remoto, costi e teardown.
- Vast/RunPod registrano ora le risorse create in `jobs` con provider, owner,
  stato, tariffa opzionale e stima cumulativa del costo; start/stop/delete
  aggiornano il ciclo di vita. La UI mostra la stima corrente e offre
  `Distruggi` con conferma esplicita per entrambe le piattaforme; test cloud
  mirati: `4 passed`.

- `tests/test_compute_profiles.py`, `tests/test_db_migrations.py`,
  `tests/test_backup.py` e `tests/test_training.py` sono i test di controllo;
  insieme a `tests/test_rate_limit.py`, ultimo esito: `26 passed` dopo la
  migrazione v12 e la correzione della persistenza del rate limit.
- Il lifecycle del backend esegue ora `PRAGMA integrity_check` dopo
  `init_db()` e interrompe l’avvio con un errore diagnostico se il database
  non è integro; il controllo resta esposto anche nella pagina backup.
  Regressione lifecycle: `test_startup_stops_on_corrupt_database`.
- Corretta una regressione del test health: non assume più la vecchia baseline
  DB `6`, ma verifica la versione corrente dichiarata da `SCHEMA_VERSION`.
- Aggiunti test senza runtime Paddle per il parser ufficiale: varianti JSON
  annidate, fallback markdown a pagina intera e output vuoto.
- La suite HTTP ora usa una compatibilità ASGI solo su Python 3.14: mantiene
  cookie e stream SSE e aggira il deadlock del portal `TestClient`; il runtime
  dell’app non viene modificato.
- I setup backend shell/PowerShell accettano esplicitamente Python 3.11–3.13
  e rifiutano versioni diverse con un messaggio diagnostico; questo evita di
  creare ambienti apparentemente supportati ma incompatibili con la matrice CI.
- Gli installer verificano anche l’import di `cryptography` dopo l’installazione:
  nessun ambiente viene dichiarato pronto mentre il vault cifrato è assente.
- L’helper SSH CLI non usa più `eval` sul comando incollato e applica
  `StrictHostKeyChecking=yes` con un `known_hosts` dedicato, coerente con il
  tunnel gestito dal backend; le recipe cloud documentate richiedono ora
  esplicitamente un ref verificato e passano i secret tramite environment.
- L’hook Vast richiede e scarica il setup Tabularium al suo `TABULARIUM_REF`,
  separato dal `MONKEYOCR_REF` del runner, prima di avviarlo; un onstart copiato
  da solo non può più puntare a uno script locale inesistente.
- Il protocollo pilota non consente più sovrapposizioni gold/pilot: proteggere
  una pagina la rimuove dal campione salvato e l’API restituisce il conteggio
  delle esclusioni; regressione `test_gold_pages_cannot_enter_saved_pilot`.
- Il campionatore pilot applica ora anche lato backend il contratto 30–50
  pagine (`target` fuori intervallo risponde `422`), non solo nello script CLI;
  regressione `test_pilot_sample_requires_review_sized_target`.
- Lo smoke E2E attende ora fino a 30 secondi la readiness del backend quando
  deve prima compilare il frontend, evitando falsi fallimenti su macchine
  lente e su Windows/WSL.
- Le preferenze Modal usano solo chiavi `tabularium.*`.
- Il tipo frontend `InferenceConfig` non dichiara più `api_key`: l’API espone
  solo `has_api_key`, impedendo che un secret possa rientrare accidentalmente
  nel contratto client durante refactoring futuri.
- Il registro modelli espone ora una maturità esplicita (`supportato`,
  `sperimentale`, `catalogo`, `non disponibile`) derivata dal contratto
  dell’adapter: scaricabile non significa più implicitamente pronto per
  inferenza o training.
- La configurazione inferenza non lascia più un riferimento `vault:inference`
  nel profilo legacy quando la credenziale viene rimossa; il test cloud usa una
  chiave Fernet effimera e non contiene segreti persistenti.
- La recipe Vast generata dal backend richiede ora ref verificati sia per
  MonkeyOCRv2 sia per Tabularium, scarica lo script dal repository corretto
  alla revisione dichiarata e non usa più `main` implicito; regressione del
  provisioning: `10 passed`.
- La procedura RunPod nella guida e nel pannello usa anch’essa due ref
  espliciti (`MONKEYOCR_REF` e `TABULARIUM_REF`) e la credenziale server via
  env, senza URL su `main` né `--api-key` esposto nel comando copiabile.
- Il frontend gestisce anche il caso di una pagina aperta durante una nuova
  build: se un chunk lazy hashato non è più presente, tenta un solo reload
  automatico per riallineare `index.html` e gli asset. Il marker viene
  cancellato solo dopo il mount riuscito dentro Suspense, evitando loop se il
  server continua a servire asset incoerenti; test frontend `79 passed` e
  build ripetuta dopo la modifica.
- `scripts/run.sh` e `scripts/run.ps1` ricostruiscono ora automaticamente il
  frontend quando i sorgenti sono più recenti di `dist/index.html`; è inoltre
  disponibile `TABULARIUM_BUILD_FRONTEND=1` per forzare la build e prevenire
  mismatch tra manifest HTML e chunk hashati al riavvio.
- Gli installer backend shell e PowerShell validano ora Python 3.11–3.13
  prima di creare o usare il virtualenv, evitando setup apparentemente riusciti
  su versioni non supportate; README aggiornato.
- Il vault è stato provato con il Python di sistema (`vault probe ok`): il
  plaintext non compare nel ciphertext; la verifica nel virtualenv resta
  sospesa finché non è disponibile `cryptography`.
- La suite vault passa con il Python di sistema: `2 passed`; il percorso API
  registra anche sostituzione/rimozione del credential senza salvarne il valore;
  sono disponibili `POST/DELETE /api/system/secrets` riservati all’admin e gli
  endpoint Vast/RunPod/Modal accettano riferimenti vault.
- `scripts/cloud/setup_cloud_vllm.sh` ora richiede un ref MonkeyOCRv2
  esplicito, pinna le dipendenze principali, fallisce sui prerequisiti GPU/disco,
  rimuove il flag incompatibile e genera `cloud-manifest.json`; `bash -n` passa.
- L’onstart Vast quota modello e API key senza interpolazione shell non sicura;
  la ricerca ora usa `PUT /api/v0/search/asks/` e il provisioning RunPod usa
  `POST /pods` con schema persistente documentato; contract test mockati: `8/8`.
  Resta da validare la forma Vast contro l’API reale e completare lifecycle,
  teardown e costi.
- Il training locale usa ora `TrainingRecipe` + `LocalProcessExecutor` e
  scrive `recipe.json` con hash SHA-256 di script e dataset; test di controllo:
  `33 passed, 2 skipped`. È disponibile anche `SshExecutor` con sync
  `rsync --checksum` e host-key verification, con PID remoto persistito,
  recovery e stop via process group; la riconciliazione non marca più come
  falliti i job SSH al riavvio. Checkpoint e artefatti hanno ora manifest
  SHA-256, download SSH e verifica prima della chiusura della run. Restano
  aperti validazione API reale e lifecycle costi per Vast/RunPod;
  i relativi executor sono già provider espliciti sopra SSH per istanze/pod
  già pronti.
- La checklist non sostituisce il backlog sottostante: le voci già presenti
  più in basso conservano il dettaglio storico e tecnico delle implementazioni.

## P0 — Integrità e riproducibilità

- [x] Rendere autorevoli i confini di qualità e riproducibilità nel backend.
  - `approved`/`exported` non sono più impostabili con la PATCH generica;
    l'approvazione passa da readiness e le pseudo-label non confermate bloccano.
  - Il preflight legge le famiglie dallo snapshot immutabile, richiede una
    validation reale e blocca una GPU già saturata da vLLM.
  - Ogni run usa una copia locale train/val dello snapshot dichiarato; il log
    ha un solo writer.

- [x] Correggere le coordinate della valutazione layout.
  - GT e predizioni sono confrontati entrambi in scala 0–1000; i pixel sorgente
    restano separati per generare i crop di testo/tabella.

- [x] Conservare la geometria corretta delle tabelle.
  - `vlines`/`hlines` sono persistite e validate come monotone, invece di
    essere accettate dall'API e scartate prima del salvataggio.

- [x] Impedire all'autosave bulk di cancellare le griglie tabellari.
  - Il client rimanda gli ID server e il backend aggiorna i blocchi esistenti,
    inserisce solo quelli nuovi ed elimina solo gli ID davvero rimossi.
  - Test di regressione: una griglia resta leggibile dopo un PUT bulk successivo.

- [x] Invalidare i crop se cambiano sorgente, bbox/poligono o trasformazioni.
  - Usare hash/versione annotazione nel nome; non riusare crop obsoleti.
  - Verifica: spostare un blocco e controllare che il crop JSONL cambi.

- [x] Correggere il quoting del training: sostituire `shutil.quote` con
  `shlex.quote` in `services/trainer.py`.
  - Verifica: script valido con `TABULARIUM_TRAIN_PYTHON` contenente spazi.

- [x] Rendere l'export atomico, versionato e immutabile.
  - Scrivere in directory temporanea, validare, poi pubblicare snapshot
    `dataset/v0001`, `v0002`, ecc. con manifest, hash e seed.
  - Verifica: un export fallito non modifica lo snapshot precedente.

- [x] Riparare la verifica HTTP backend e usare sempre
  `.venv/bin/python -m pytest` negli script.
  - `httpx2` è fissato nei requirements-dev; la suite completa passa anche nel
    virtualenv Python 3.14 usando una compatibilità ASGI confinata a
    `tests/conftest.py`, mentre il runtime di produzione resta invariato.
  - Gli script `test_backend.sh` e `test_backend.ps1` invocano il Python del
    virtualenv; la matrice CI continua a verificare Python 3.11–3.13.

## P1 — Protocollo di ricerca e dataset affidabile

- [x] Creare uno **studio di ricerca** sopra il progetto tecnico.
  - Wizard: corpus, intervallo temporale, obiettivo, convenzioni, modello
    e profilo di dominio.
  - Versionare e mostrare il protocollo dello studio.
  - L'unità di generalizzazione vive solo come **unità di split** nella pagina
    Dataset (`split_strategy`): era duplicata nel protocollo, dove però non la
    leggeva nessuno.

- [x] Produrre una mappa del corpus dopo l'import.
  - Statistiche per anno/numero/tipo pagina/scanner, stima tabelle,
    duplicati/quasi-duplicati e campione iniziale bilanciato.

- [x] Supportare split per gruppi: data/numero, annata, scanner, collezione e
  tipo pagina; separare `train`, `validation` e `gold test`.

- [x] Implementare stati annotazione: `bozza → verificata → approvata`.
  - Mostrare checklist per layout, reading order, testo, tabelle e warning.
  - Esportare solo pagine approvate.

- [x] Creare QA e gold set.
  - Il gold set protetto è assegnabile dal registro, escluso dal tuning e
    subordinato a una seconda revisione; `/qa-report` aggrega gli errori ricorrenti.
  - Seconda revisione su campione; 8–12 pagine invisibili al tuning;
    registrazione degli errori ricorrenti.

- [x] Preflight export bloccante.
  - Bbox validi anche dopo normalizzazione; griglie/merge/OTSL round-trip;
    prompt, trascrizioni, crop e classi verificati.
  - Distinguere errori bloccanti da warning.

## P1 — Annotazione guidata e ad alta precisione

- [x] Home operativa: **Stato dello studio / prossimo miglior compito**.
  - Avanzamento per stadio, copertura label, warning, dataset e ultime run.

- [x] Coda di annotazione guidata.
  - Continua lavoro, completa pagine quasi finite, rivedi tabelle complesse,
    copri classi deboli, lavora su campioni bilanciati o incerti.

- [x] Rendere espliciti i passaggi pagina: struttura, contenuto, tabella,
  revisione; consentire il salto dei passaggi non applicabili.

- [~] Trasformare l'editor tabellare in vista di lavoro dedicata.
  - **Fatto**: immagine e griglia sovrapposte e sincronizzate, zoom locale,
    linee trascinabili con la tastiera come alternativa, supporto per confine
    leggibile dalla forma (continuo = attestato, tratteggiato = da guardare),
    e accettazione confine per confine — «Aggiungi colonna/riga» e «Rifiuta
    confine», con `dropBoundary`/`insertBoundary` che rifiutano i casi ambigui
    invece di indovinare (`lib/grid.ts`, `studio/components/TableGridOverlay.tsx`).
    La voce era segnata come completata ma il codice aveva ancora cursori
    numerici accanto all'immagine: su un ritaglio da 2600 px un cursore largo
    80 px sposta la linea di ~32 px per pixel, quindi la correzione fine non
    era possibile.
  - **Da fare**: colonne fantasma raggiungibili dall'overlay, `header_rows`
    esposto nel modale, note e totali come ruoli di riga dichiarabili.
  - OTSL/HTML restano strumenti avanzati.

- [x] Introdurre tile pyramid per scansioni grandi.
  - Stato ad alta frequenza fuori dal ciclo di rendering React.
  - Verifica: zoom/pan fluidi con immagini grandi e molte regioni.

- [x] Aggiungere browser cartella locale multipiattaforma, mantenendo il
  percorso manuale come fallback. Il picker `webkitdirectory` carica in modo
  sicuro una sessione di import senza esporre o modificare il percorso locale.

## P2 — Esperimenti, valutazione e active learning

- [x] Training Center con preset: prova rapida, LoRA standard, comparativo e
  avanzato; preflight GPU/CUDA/disco/dataset/checkpoint.

- [x] Collegare ogni run a uno snapshot dataset preciso; salvare manifest,
  config, script, versione app/modello e metriche incrementali JSONL.

- [x] Usare SSE nel frontend per log, metriche e GPU al posto del
  polling come percorso normale.

- [x] Completare M6: confronto base-vs-fine-tuned, overlay, IoU/mAP, ordine,
  TEDS, CER/WER e fallimenti peggiori.

- [x] Trasformare i fallimenti in azioni: “aggiungi esempi simili”, “rivedi
  queste tabelle”, “copri questa label”.

- [x] Rendere il rilevatore di griglia affidabile su tutto il campione.
  - Righe dalle linee di base dei glifi (componenti connesse) invece che
    dall'autocorrelazione del profilo, soglia di Otsu, inclinazione stimata e
    compensata senza ruotare l'immagine, avviso `skewed` esposto e tradotto.
  - Da 1 pagina su 4 a 4 su 4 sul campione reale; le tre rotture sono fissate
    da test di regressione che falliscono sulla versione precedente.
  - Resta noto e documentato (AGENTS.md §2.3.4): su una pagina a più colonne di
    giornale il rilevatore non sa di essere fuori dominio, e le due ipotesi per
    accorgersene sono state misurate e respinte.

- [x] Prelabel assistito e active learning.
  - Bozze da modello/OCR restano non confermate, conservano provenienza e
    confidenza, e la coda privilegia le pagine a bassa confidenza o con errori eval.

- [x] Esporre e tracciare il percorso ufficiale MonkeyOCRv2 END2END.
  - Una chiamata bbox+label+content a 2 MP; OTSL valido viene importato senza
    seconda inferenza, altrimenti il report dichiara il fallback sul crop.
  - Il default resta `two_stage` finché l'A/B sul gold set non misura struttura,
    CER/TEDS, completezza e latenza su più pagine.

- [x] Rendere le bande tabella un'augmentazione sicura e sperimentale.
  - Il crop completo resta incluso; le bande sono create solo da `hlines`
    verificate, non attraversano rowspan e sono conteggiate separatamente.
  - Nessuna prima riga viene assunta automaticamente come intestazione.

- [x] Assegnare i valori alle celle invece di tagliare a x fisso.
  - Le colonne derivano di 25–77 px scendendo (0,6–2,0 passi) mentre una cifra
    è larga ~20 px: nessuna retta descrive la tabella. `snap_boundaries()`
    porta ogni confine nel varco di bianco della sua riga; dove un varco esiste
    (86–96% dei tagli) nessun valore viene spezzato, dove non esiste il taglio
    è marcato non provato e la cella entra fra le `uncertain`.
  - Da 5,7% a 0,5% di valori spezzati sul campione reale.
  - L'overlay disegna la spezzata reale sotto la retta modificabile.
  - I confini piegati e i flag `row_columns_proven` sono ora persistiti nel
    grid salvato; le vecchie griglie restano compatibili perché i campi sono
    opzionali. Regressione: forma, cardinalità e normalizzazione verificate.

- [x] Far sapere al preflight se la configurazione entra nella GPU.
  - Il controllo guardava la VRAM libera in assoluto e passava sempre su una
    scheda scarica; poi `swift sft` andava in OOM dopo aver scaricato i pesi.
  - `services/vram.py` stima i termini reali: su 8 GB il preset ufficiale
    (batch 4, 16384) chiede 26 GB, e il termine dominante sono i **logit**
    (vocabolario da 151936 su hidden 1024), non i pesi.
  - Tutti e tre i preset spediti non entravano in 8 GB: aggiunto `gpu8`
    (batch 1, 8192, grad_accum 4 → ~5,4 GB, stesso batch effettivo).

- [ ] Eseguire un pilot: 30–50 pagine rappresentative, gold set protetto,
  prima LoRA, analisi errori, poi scala a 150–300 pagine.
  - Il campionatore deterministico 30–50 pagine è disponibile e salvabile nel
    protocollo; Dataset Builder ora offre `pilot_only` per generare uno
    snapshot isolato dal campione salvato (gold escluso). Restano da eseguire
    con il corpus reale la prima LoRA, l’analisi degli errori e la scala a
    150–300 pagine: `scripts/prepare_pilot.py` automatizza la preparazione e il
    preflight, ma dati annotati e una GPU dell’utente restano necessari.

## P2 — Generalizzazione oltre Historic Shipping Index e MonkeyOCRv2

- [x] Definire un **core annotation schema** indipendente dal modello.
  - Pagine, regioni, poligoni/bbox, reading order, trascrizioni, tabelle
    logiche, celle, merge, metadati, provenienza e stato QA.
  - Conservare sempre coordinate sorgente e dati ricchi; non salvare OTSL,
    JSONL ms-swift o prompt come verità primaria.

- [x] Introdurre i **domain profiles** configurabili.
  - `shipping-registers-1900s` iniziale: label, colori, convenzioni, campionamento,
    prompt, regole QA e metriche preferite.
  - Profili futuri: registri, quotidiani moderni, manoscritti, fatture,
    moduli, libri, mappe o documenti tecnici.

- [x] Introdurre i **model adapters**.
  - Contratto comune: capabilities, input, export, training config, inference,
    checkpoint, metriche e vincoli hardware.
  - Primo adapter: MonkeyOCRv2-Parsing/ms-swift/OTSL.
  - Non assumere che tutti i modelli usino prompt, coordinate 0–1000, crop,
    OTSL o LoRA nello stesso modo.

- [x] Separare export dal modello.
  - Ogni snapshot può produrre più export: MonkeyOCRv2 JSONL, COCO/DocLayNet
    layout, ALTO/PAGE XML, HTML/CSV tabelle, JSON interno e formati futuri.
  - Disponibili export `internal`, COCO layout, PAGE XML, ALTO XML e HTML tabelle.
    Verifica: lo stesso ground truth produce più viste senza alterare l'annotazione primaria.

- [x] Catalogo capacità modello/dominio.
  - Dichiarare per ogni adapter: layout, testo, tabelle, formule, poligoni,
    ordine di lettura, multilingua, inferenza locale/remota e training.
  - La UI mostra solo strumenti ed export supportati dalla combinazione scelta.

- [x] Progettare una pipeline a plugin, non un monolite.
  - Interfacce interne per scanner, prelabeler, exporter, trainer, evaluator e
    inference provider.
  - Configurazioni YAML/JSON validate, versionate e portabili.

- [x] Conservare il focus iniziale.
  - Non implementare adapter generici prima che il workflow Historic Shipping Index sia
    validato end-to-end; creare ora solo i confini e le interfacce.

## P3 — Test, documentazione e usabilità

- [x] Test frontend per coordinate, merge/split, OTSL preview, autosave e
  percorso e2e importa → annota → snapshot → script training.
  - Coordinate, merge/split, struttura i18n e shape del dizionario sono
  coperti; `scripts/e2e_smoke.sh` verifica ora rendering Chromium, deep-link
  `/dataset` e health del server. `scripts/e2e_workflow.py` copre ora il
  percorso UI scan → annotazione deterministica → snapshot dataset →
  preflight training, inclusa apertura dell’editor tabella, modifica DOM con
  debounce autosave verificato dal backend e preview OTSL.

- [x] Aggiornare README con protocollo pilota, requisiti GPU, riproducibilità,
  profili di dominio e modello di estensione tramite adapter.

## Ordine consigliato

1. P0: crop, quoting, test backend, export atomico.
2. Protocollo, split a gruppi, QA, preflight e snapshot dataset.
3. Coda guidata, editor tabellare su immagine e tile pyramid.
4. Training riproducibile, SSE e pilot controllato.
5. Valutazione che genera nuova annotazione.
6. Prelabel/active learning.
7. Adapter e profili multi-dominio/modello, usando il core già stabilizzato.