La catena «sorgente pubblico → binario scaricato» dei sei eseguibili standalone di `sg-attest` (ADR-P40) si reggeva sul solo `SHA256SUMS.txt`, generato dallo stesso workflow che produce il binario: un checksum dimostra che il file non è cambiato dopo la build, non che sia stato costruito da quel sorgente, da quel commit, da quel workflow. Per un servizio la cui tesi pubblica è che la fiducia si dimostra invece di dichiararla, era l'anello più debole rimasto nella distribuzione — un residuo implicitamente accettato da ADR-P40 nello scegliere il checksum come garanzia alternativa alla firma del codice (quest'ultima rimandata, vedi voce A2 dello stesso piano). Nota di coerenza con ADR-P37, che aveva scartato **Sigstore** per attestare le OPERE digitali (categoria sbagliata: è firma keyless di artefatti software, non di opere). Qui il dominio è l'opposto: si firma un artefatto software — il binario CLI — che è esattamente il caso d'uso proprio di quella tecnologia. Non è una riapertura della decisione P37, ne è il complemento naturale.
Decisione: Attivato **GitHub Artifact Attestations** (`actions/attest-build-provenance@v4`) sul job `release` di `attest-mcp/.github/workflows/release-binaries.yml`: firma con identità OIDC effimera i sei binari esatti pubblicati sulla Release (subject-path `dist/*`, collocato PRIMA della generazione di `SHA256SUMS.txt` così il checksum derivato non viene mai attestato), registrata in un transparency log pubblico. Permessi `id-token: write` + `attestations: write` aggiunti al solo job `release`, non a livello di workflow. Costo umano ricorrente: zero, gira ad ogni tag senza intervento. Verificato **due volte**, non solo scritto: (1) primo tag reale attraverso il workflow modificato (v0.4.2, merge PR #1, commit b6d271e); (2) `gh attestation verify` su un binario scaricato dalla **pagina Release pubblica** (non dagli artefatti di CI) con esito positivo, e sullo stesso file alterato di un byte con esito negativo — un test che non prova il fallimento non prova niente. `/developer/cli/` (repo imgauthweb, IT+EN sincronizzati) estende la sezione «Verifica l'integrità» col comando, spiegando la differenza fra le due domande (integrità vs provenienza) senza sostituire il checksum. Dettagli in `EVD-build-provenance-verify`. **Due punti da registrare esplicitamente, emersi dalla verifica reale e non dal solo design doc:** 1. **`gh attestation verify` richiede una sessione GitHub autenticata** (`gh auth login` o `GH_TOKEN`; un account gratuito basta), **anche su un repository pubblico** — confermato empiricamente: senza autenticazione, sia `gh attestation verify` sia `gh attestation download` rispondono con errore (exit 4, "please run gh auth login"). Il dato sottostante è comunque pubblico — l'endpoint REST `GET api.github.com/repos/<owner>/<repo>/attestations/<digest>` risponde 200 senza alcuna autenticazione, verificato con `curl` puro — ma non esiste oggi una procedura a comando singolo, senza `gh` autenticato, che porti dallo stesso binario scaricato a un esito verificato: servirebbe ricostruire a mano il bundle Sigstore dalla risposta JSON. **Conseguenza per il registro**: `CTL-build-provenance` **non soddisfa PRN-08 alla lettera** ("verificabile da un terzo senza credenziali") — il suo `verify_howto` lo dichiara esplicitamente invece di sorvolare, distinguendo "richiede un account GitHub gratuito" (vero, limite del client `gh`) da "richiede un segreto del progetto" (falso, il dato è pubblico). Non è stato scritto un `verify_howto` che finge un requisito diverso da quello reale. 2. **In A2 (firma del codice, non ancora eseguita) l'ordine dei passi è vincolante**: la firma del codice deve avvenire **prima** dello step di attestazione di provenienza, non dopo. `subject-path: 'dist/*'` attesta i byte esatti del file al momento in cui gira lo step — se A2 aggiungesse una firma *dopo* l'attestazione corrente, il binario firmato sarebbe un file diverso da quello attestato e l'attestazione risulterebbe orfana (o, peggio, verificherebbe un file che l'utente non riceve più). L'ordine corretto nel job `release` è: build → firma del codice (A2) → attestazione di provenienza sul binario *firmato* → checksum → pubblicazione Release. Chi esegue A2 deve riposizionare lo step di attestazione esistente dopo la firma, non aggiungerne uno nuovo accanto. **Nota su un collegamento non ovvio, per PRN-06**: il design doc proponeva come candidato `REQ-27037-pres-01` per l'aggancio in Compliance Map. Verificato che è la scelta sbagliata: quel requisito copre la conservazione delle **prove delle opere attestate** (certificato, ancoraggio), un dominio diverso dalla provenienza di un **artefatto software distribuito**. Collegato invece a `REQ-27001-inspiration-01` (sicurezza delle operazioni/CI, la stessa famiglia di `CTL-cicd-pipeline`), coerente con l'uso "a ispirazione" già stabilito per quel requisito. **Seguito, stesso giorno: il gap PRN-08 era del client, non del dato — chiuso.** Il punto 1 sopra lasciava un residuo dichiarato ma non risolto. Scritto e collaudato `attest-mcp/scripts/verify-provenance.mjs` (PR attest-mcp #2): interroga l'endpoint REST pubblico delle attestazioni GitHub (nessun header Authorization, confermato con `curl` a env pulito) e verifica il bundle Sigstore con la libreria npm `sigstore`, che a sua volta verifica contro l'infrastruttura pubblica di Sigstore (Rekor, Fulcio, TUF) — nessun account da nessuna parte nella catena. `sigstore` è stata aggiunta come **devDependency** esplicita (autorizzata dal gestore per questo giro, scelta motivata sotto), non come dipendenza del pacchetto pubblicato: non tocca `src/*`, la CLI/il server MCP restano privi di dipendenze esterne oltre a quelle già presenti, coerente con la preferenza del progetto. Scelta **sigstore (libreria npm) invece di cosign (binario Go)**: il progetto è interamente Node/Bun, e `sigstore` permette uno script auto-contenuto eseguibile con solo `npm install`, senza chiedere a chi verifica di installare un secondo binario per piattaforma — la stessa frizione che si voleva ridurre rispetto a `gh`. `sigstore` è inoltre la libreria con cui GitHub stessa costruisce le attestazioni (`@actions/attest-build-provenance`), non un'implementazione terza. Collaudato con dati reali, non solo scritto: sui sei binari del tag v0.4.2, scaricati dalla Release pubblica — file autentico → verifica riuscita; stessa copia rinominata → verifica riuscita con avviso (il legame è sul digest del subject in-toto, non sul nome file); copia con un byte alterato → fallita (il digest cambia, quindi cambia la chiave stessa con cui si interroga l'endpoint, che risponde 404). Dettagli in `EVD-build-provenance-script-verify`. **Sorpresa emersa scrivendo lo script, non dal solo design**: `sigstore.verify(bundle, data, options)` — la funzione di verifica della libreria — **non lega da sé l'artefatto al subject** per un bundle DSSE (un'attestazione, a differenza di una firma di messaggio): passare bytes completamente estranei come `data` non fa fallire la verifica crittografica (provato con un test dedicato, leggendo poi il sorgente della libreria per capire perché: per le buste DSSE il confronto interno è fra il payload e la propria firma, non fra l'artefatto e il payload). Un'implementazione che si fidasse del parametro `data` per il legame sarebbe silenziosamente insicura: accetterebbe qualunque file locale purché esistesse UNA attestazione valida per quel repository/workflow/tag. Lo script fa quindi il legame esplicitamente, leggendo il predicato in-toto e cercando un subject il cui digest coincide con quello calcolato sul file locale. **Conseguenza per il registro**: `CTL-build-provenance.verify_howto` riscritto con la nuova procedura come percorso primario (soddisfa PRN-08 alla lettera: nessuna credenziale in nessun passo); `gh attestation verify` resta documentato come alternativa per chi ha già `gh` autenticato, con il suo limite dichiarato invariato. Il controllo resta **`draft`**: la promozione ad `active` non è autorità di questa sessione (vedi `consequences`).
Conseguenze: `CTL-build-provenance` nasce **`draft`**: collaudato con dati reali (tag v0.4.2, verifica positiva e negativa), ma la promozione ad `active` resta una decisione del gestore, non mia (invariante vincolante del progetto). Nessun nuovo `RSK` creato in questo giro: un candidato naturale (manomissione della catena di distribuzione dei binari) non ha oggi un record dedicato nel registro — lasciato come domanda aperta al gestore, non deciso qui, perché introdurre un rischio nuovo è una decisione più grande di quanto A1 richieda. Aggiornamento, stesso giorno: il gap PRN-08 dichiarato al punto 1 è **chiuso** (`scripts/verify-provenance.mjs`, vedi sopra) — resta comunque **`draft`**, perché PRN-08 e la promozione `draft`→`active` sono due questioni distinte: la seconda richiede una decisione esplicita del gestore, non la sola disponibilità tecnica di una procedura corretta. Fuori scopo, come da design doc: attestazione del pacchetto npm (pubblicato a mano, non da CI, verificato in FASE 0); SLSA build level 3; qualunque modifica al motore `imgauth`.
ADR-A1