Genesis Trust Framework

Il registro della fiducia di Spazio Genesi ETS

Perché attestazione.spaziogenesi.org merita fiducia — con evidenze verificabili, non dichiarazioni.

Trasparenza97
Integrità *83
Tracciabilità100
Documentazione100
Automazione61
Audit *100
Conservazione *88
Riproducibilità97
Privacy *100
Governance *80
Saldo91/100

10 di 10 indicatori disponibili — i restanti non sono stimati: restano n/d finché non esisteranno i dati per calcolarli davvero. * = valore parziale, passa il mouse per i dettagli. Formula di ciascuno.

Missione e principi

Spazio Genesi ETS costruisce un servizio di attestazione digitale (attestazione.spaziogenesi.org) capace di dimostrare pubblicamente, con evidenze verificabili da chiunque, perché merita fiducia — senza mai chiedere a nessuno di crederci sulla parola. La fiducia non si dichiara, si dimostra.

PRN-01

Transparency by Design

Ogni elemento del sistema è visibile per default; ciò che non lo è deve giustificare esplicitamente perché.

Regola verificabile: Ogni record del registro ha `visibility: public|internal|secret`; default `public`; `internal`/`secret` richiedono il campo `why_not_public` compilato.

PRN-02

Evidence by Design

Nessuna affermazione senza un'evidenza che la sostenga; un'evidenza scaduta degrada silenziosamente il controllo che dipende da lei, mai lo status quo.

Regola verificabile: Ogni controllo (CTL) referenzia ≥1 evidenza (EVD) con `freshness` dichiarata; evidenza scaduta → controllo passa a `stale`, lo score scende.

PRN-03

Documentation by Design

Nessun documento pubblico si scrive a mano nel Trust Center: si genera dal registro, sempre, così non può mai divergere da ciò che è vero.

Regola verificabile: Nessun documento scritto a mano nel Trust Center: tutto generato dal registro; la CI fallisce se un file pubblicato non ha un record sorgente.

PRN-04

Security by Design

Ogni controllo di sicurezza è ancorato a un'implementazione reale e a una minaccia dichiarata: nessuna misura "di facciata".

Regola verificabile: Ogni CTL di sicurezza referenzia l'implementazione (IMP → file/riga/config) e il rischio (RSK) che mitiga.

PRN-05

Privacy by Design

Ogni flusso di dati personali è mappato, con base giuridica e retention dichiarate, prima ancora di essere costruito — non dopo.

Regola verificabile: Ogni flusso di dati ha un record DAT (dato, base giuridica, retention); un flusso senza DAT fa fallire la build.

PRN-06

Governance by Design

Ogni modifica al registro passa da revisione; le decisioni non ovvie lasciano una traccia esplicita, non solo un commit.

Regola verificabile: Ogni modifica al registro passa da PR; le decisioni non ovvie richiedono un'ADR obbligatoria (l'etichetta `needs-adr` blocca il merge finché non c'è).

PRN-07

Automation by Design

Ciò che si può raccogliere automaticamente si raccoglie automaticamente; la raccolta manuale è l'eccezione da giustificare, non la norma.

Regola verificabile: Campo `collection: auto|manual` su ogni evidenza (EVD); la percentuale `auto` è un indicatore dello score; nuove EVD `manual` richiedono giustificazione.

PRN-08

Open Verification

Ogni affermazione pubblica porta con sé il modo per un terzo di verificarla da solo, senza credenziali e senza doverci credere.

Regola verificabile: Per ogni affermazione pubblica esiste uno script o una procedura eseguibile da terzi senza credenziali (`verify_howto` obbligatorio sui controlli pubblici).

PRN-09

Continuous Improvement

Un incidente senza un'azione correttiva è un incidente destinato a ripetersi; il sistema non permette che accada senza segnalarlo.

Regola verificabile: Ogni incidente (INC) produce ≥1 azione (ACT) con scadenza; le ACT scadute e ancora aperte mostrano un banner nel Trust Center.

PRN-10

Least Complexity

Il framework deve essere sostenibile da un'organizzazione di volontari: ogni processo ricorrente dichiara il proprio costo umano, e il sistema ha un tetto.

Regola verificabile: Ogni nuovo processo dichiara `human_cost_minutes_month`; il totale di sistema ha un tetto (budget 8 h/mese) visibile nel Trust Center.

Cosa NON è questo servizio

Il servizio produce attestazioni di esistenza non qualificate: firma elettronica avanzata con certificato self-signed, più marca temporale RFC 3161 (TSA in Adobe AATL) e ancoraggio Bitcoin (OpenTimestamps). Non è un servizio fiduciario qualificato eIDAS. Alle prove elettroniche non qualificate non possono essere negati effetti giuridici per il solo motivo della forma elettronica o della non-qualificazione; il valore probatorio è rimesso al giudice. L'upgrade a sigillo qualificato è nella roadmap pubblica, condizionato alla sostenibilità economica dell'organizzazione.

CTL-eidas-honest-positioning attivo

Compliance Map

inspiration

ISO/IEC 27001

controlli Annex A pertinenti alla scala dell'organizzazione

I controlli Annex A applicabili a un'organizzazione di volontari senza infrastruttura propria (controllo accessi, crittografia, sicurezza delle operazioni, gestione degli incidenti) sono adottati per ispirazione, non per certificazione formale: la certificazione è insostenibile per un ETS di questa scala, ma i controlli pertinenti sono comunque implementati e verificabili uno per uno.

  • CTL-hmac-signing attivo Token HMAC che vincola hash, timestamp server-side e metadati dichiarati
    come verificare

    Prova a chiamare /api/cert-pdf con un hash o timestamp diverso da quello restituito da /api/hash: la richiesta viene rifiutata (400/403). Lo stato del componente "worker" su /api/status riflette anche l'esito della sonda HMAC interna.

  • CTL-rate-limiting attivo Rate limiting per-IP su emissione certificati e hash/verify
    come verificare

    Invia più di 10 richieste in 60s a /api/cert-pdf (o più di 60 a /api/hash|/api/verify) dallo stesso IP: le richieste oltre soglia ricevono 429.

  • CTL-turnstile-antibot attivo Challenge anti-bot prima di qualunque emissione di attestazione
    come verificare

    Chiama /api/hash senza turnstile_token: risposta 400. Con un token fasullo: 403. Il widget è visibile sopra "Genera attestazione" su attestazione.spaziogenesi.org.

  • CTL-cors-restricted attivo CORS ristretto al solo dominio dell'interfaccia utente
    come verificare

    Chiama un endpoint di imgauth da un'origine diversa da attestazione.spaziogenesi.org in un browser: la risposta CORS nega l'origine. Nota: è una difesa solo lato browser, non contro client diretti.

  • CTL-r2-eu-archive attivo Archivio certificati in giurisdizione EU, recuperabile solo da chi conosce l'hash
    come verificare

    GET /api/cert?hash=<sha256> restituisce il PDF archiviato per quell'hash; un hash malformato o assente risponde 400/404, mai il contenuto di un altro certificato.

  • CTL-secrets-escrow attivo Recupero possibile dei segreti critici senza dipendere dalla sola memoria del maintainer
  • CTL-availability-monitoring attivo Stato semaforico pubblico dei servizi, con storico proporzionale e drill-down giornaliero
    come verificare

    Consulta https://attestazione.spaziogenesi.org/status/: stato live, barre 90 giorni con uptime % pesato sul tempo (P36, 2026-07-21: un disservizio breve mostra una tacca proporzionale alla sua durata reale, non più l'intera giornata colorata), drill-down sugli eventi fini di ogni giornata (anche quelle "verdi") con un riquadro d'impatto onesto.

  • CTL-agent-access attivo Accesso agenti: bearer token D1-backed (API key + device flow), bypass del solo Turnstile
    come verificare

    Chiama POST /api/hash con un header Authorization: Bearer sg_(k|s)_<id>_<secret> valido e senza turnstile_token: la richiesta va a buon fine (nessuna challenge richiesta). Con credenziale sconosciuta/malformata/revocata → 403; con quota esaurita → 429; senza header Authorization il percorso Turnstile resta identico a prima (invariato). Per il device flow: POST /api/agent/authorize restituisce un codice, GET /agent/authorize?code= serve la pagina di autorizzazione sul dominio pubblico, GET /api/agent/token?code= consegna il session token una sola volta dopo l'approvazione.

  • CTL-agent-admin-panel attivo Pannello admin credenziali agente: elenco/emissione/revoca/quota con accesso riservato
    come verificare

    L'accesso al pannello e alle sue funzioni di gestione è riservato al gestore e protetto in profondità (Cloudflare Access a monte più protezione applicativa — vedi CTL-cloudflare-access-admin e ADR-P21-admin-cfaccess). L'effetto delle operazioni di gestione è osservabile in modo indiretto: una credenziale emessa autentica su /api/hash, una revocata riceve 403. Il perimetro non tocca in alcun caso l'emissione dei certificati.

  • CTL-dev-selfservice attivo Self-service API key con verifica email OAuth one-shot (Google/Microsoft/LinkedIn), post-moderazione
    come verificare

    Apri attestazione.spaziogenesi.org/developer/keys/ (P29: pagina statica su authweb, tre bottoni "Continua con Google/Microsoft/LinkedIn" ora fissi nell'HTML — trade-off accettato in FASE 3, non riflettono più la configurazione runtime del provider su imgauth): mostra l'informativa privacy sintetica prima del redirect (LinkedIn dal 2026-07-12, imgauth 1.20.0, ADR-P25-linkedin). Dopo il login, la callback OAuth reindirizza con la chiave sg_k_… SOLO nel fragment dell'URL (`#sgk=…`, mai in una risposta HTML del server, P29), mostrata una sola volta dal JS lato client e usabile su /api/hash come qualunque altra API key. Una seconda richiesta con la stessa email non emette una seconda chiave (fragment `#sgstate=gia-attiva`). Nel pannello /admin la chiave compare con colonna Titolare (email + provider); revocandola compare "Dimentica titolare" per l'anonimizzazione immediata (altrimenti automatica dopo 180 giorni dalla revoca, via cron).

  • CTL-cicd-pipeline attivo Catena di rilascio: staging replicato + CI + gate di produzione con approvazione umana (P24)
    come verificare

    Stato ad oggi (2026-07-11, FASE 0-4 e 7 di 8 completate — vedi https://trust.spaziogenesi.org/devops.html per la tabella completa): ogni pull request sul repo pubblico imgauth fa girare automaticamente check di sintassi/contratto/build (tab Actions, workflow "CI"); il merge in main aggiorna da solo l'ambiente di staging (`imgauth-staging`, workers.dev, mai dati o segreti di produzione) e lo verifica con uno smoke test end-to-end (ping, stato, giro completo attestazione → certificato → recupero). Il **gate di produzione con approvazione umana è attivo**: un job dedicato (`deploy-production`) resta IN ATTESA finché un required reviewer (GitHub Environment "production") non approva dall'interfaccia GitHub — nessuno step del deploy parte prima. Dopo l'approvazione: `wrangler versions upload` (nuova versione a 0% traffico) seguito da `wrangler versions deploy` (promozione al 100%), poi uno smoke di sola lettura su `/ping`. Verificabile aprendo un run pubblico che ha attraversato l'intero percorso, es. https://github.com/SPAZIO-GENESI/imgauth/actions/runs/29153958055 (primo rilascio gated della storia del progetto, 2026-07-11): il job `deploy-production` mostra lo stato "waiting" fino all'approvazione, poi i passi di upload/promozione/smoke, tutti verdi. Il rollback (`wrangler rollback <version-id> --yes`, secondi, senza rebuild) è stato collaudato dal vivo su staging, mai in produzione per un test. L'**interfaccia di staging** (repo pubblico `attestazione-staging`, GitHub Pages su `*.github.io`, nessun dominio custom) è generata dal branch `staging` di imgauthweb e collaudata con un test browser reale (Playwright, non solo curl): footer con versione del motore letta da `imgauth-staging` (conferma CORS), attestazione completa con Turnstile di test, PDF non firmato scaricato e verificato, link permanente presente. Chiunque può ripetere il collaudo aprendo https://spazio-genesi.github.io/attestazione-staging/ e attestando un file di prova. RADART ha il proprio staging/CI pianificato ma non ancora eseguito (FASE 5-6) — questo controllo copre oggi imgauth e authweb, non ancora RADART.

  • CTL-edge-security-headers attivo Header di sicurezza HTTP (HSTS, CSP, Permissions-Policy) all'edge Cloudflare
    come verificare

    Interroga gli header di risposta di spaziogenesi.org o attestazione.spaziogenesi.org (es. curl -I o securityheaders.com): devono comparire Strict-Transport-Security (max-age un anno, includeSubDomains), Content-Security-Policy (script-src 'self' https:, SENZA 'unsafe-inline' dal 2026-07-14 — vedi ADR-edge-security-headers per come si è arrivati a rimuoverlo) e Permissions-Policy; l' Access-Control-Allow-Origin: * di GitHub Pages deve essere assente sui soli host Pages (spaziogenesi.org, www., attestazione.), MAI su imgauth.spaziogenesi.org dove il CORS resta necessario.

  • CTL-pro-subscription attivo Fascia Professionale: abbonamento Stripe (Checkout/Portal hosted, nessun dato di carta sul Worker), pagina profilo, listino/sconti gestiti in D1
    come verificare

    Apri attestazione.spaziogenesi.org/profilo/ (P29: guscio statico su authweb, fetch verso imgauth), accedi con un login OAuth one-shot (Google/Microsoft/LinkedIn — stesso meccanismo di CTL-dev-selfservice e CTL-site-voucher-auth, nessuna password, nessun account). Senza abbonamento: prezzo del listino attivo mostrato dinamicamente (mai hardcodato) e bottone Checkout, che apre una pagina Stripe hosted — nessun campo carta compare mai su imgauth.spaziogenesi.org. Con un abbonamento attivo: stato, scadenza, log ricariche, consumo del mese, archivio dei certificati con canale di produzione (web/api/mcp/telegram), bottone "Gestisci o cessa l'abbonamento" che apre lo Stripe Customer Portal hosted (fatture, metodo di pagamento, cessazione — mai su questo Worker). Una cessazione dal Portal (comportamento di default per abbonamenti annuali: resta attivo fino alla scadenza già pagata) mostra un banner "cessazione programmata" senza perdere la fascia. La catena di precedenza convenzione→professionale→sviluppatore→base non blocca mai: quota o abbonamento esauriti degradano a Base con motivo esplicito (`fascia_motivo`), stessa logica già verificata per CTL-convention-accounting. Il listino e i codici sconto si gestiscono dal pannello /admin (scheda Professionale): righe con finestre di validità (mai si modifica un importo passato, si chiude e se ne crea una nuova) e sconti percentuali/fissi, anche riservati a una singola email. ✅ Collaudato con un **abbonamento reale a pagamento** (1€, non un dato sintetico) end-to-end in produzione il 17-18 luglio 2026 — vedi EVD-pro-subscription-live-test: checkout → attestazione dal sito riconosciuta in fascia professionale → archivio con canale → cessazione dal Customer Portal → rimborso. Nel collaudo emersi e corretti tre difetti reali mai incontrati nei test locali (shape dell'API Stripe dell'account, `cancel_at_period_end` sempre `false`, doppio evento del Customer Portal per un click) — vedi ADR-P27. Nasce direttamente `active`, stesso criterio già applicato a CTL-cicd-pipeline, CTL-site-voucher-auth e CTL-convention-accounting: un controllo collaudato con dati reali, non solo dichiarato.

  • CTL-integrations-showcase attivo Vetrina pubblica Integrazioni (pre-moderata) + convenzioni partner software house con pool mensile
    come verificare

    Apri attestazione.spaziogenesi.org/integrazioni/ (P29: pagina statica su authweb, rigenerata a evento da imgauth): vetrina pubblica, solo le candidature approvate compaiono (logo, nome, descrizione, link `rel="noopener nofollow"`), con conteggio totale e spiegazione onesta che la presenza non è una certificazione del software. Chi ha una chiave API attiva o un abbonamento Professionale può candidare la propria applicazione dalla sezione "La tua integrazione" di attestazione.spaziogenesi.org/profilo/ (stesso login OAuth one-shot di CTL-dev-selfservice e CTL-pro-subscription): la candidatura risulta subito `pending`, mai pubblica finché il gestore non la approva dal pannello /admin (scheda Integrazioni) — pre-moderazione, a differenza di ogni altro flusso self-service del sistema. Qualunque modifica successiva — nome, URL, descrizione o solo il logo — riporta lo stato a `pending`: non esiste un canale per cambiare silenziosamente ciò che è già online. Il logo, se presente, è servito solo per le candidature approvate (`GET /integrazioni/logo/<id>`, 404 altrimenti) ed è stato validato all'upload sui magic bytes (PNG/JPEG/WebP, mai SVG, mai il Content-Type dichiarato). Le software house che attestano per conto dei propri utenti usano una convenzione con pool mensile dedicato (`convention_id` sulla chiave, `domains` placeholder che non può mai combaciare con un'email reale) — stesso meccanismo di CTL-convention-accounting, riusato senza nuovo codice sul motore. Il "forget" GDPR del pannello admin anonimizza anche il titolare di una candidatura e la ritira dalla vetrina.

  • CTL-responsible-disclosure attivo security.txt (RFC 9116) + policy pubblica di responsible disclosure
    come verificare

    `curl https://attestazione.spaziogenesi.org/.well-known/security.txt` e `curl https://imgauth.spaziogenesi.org/.well-known/security.txt` rispondono entrambi 200 con `Contact: mailto:it@spaziogenesi.org`, un `Expires` nel futuro e `Policy: https://attestazione.spaziogenesi.org/sicurezza/`. Quella pagina elenca l'ambito (i due domini, i servizi workers.dev attest-bot/attest-mcp-remote, i repo GitHub pubblici dell'organizzazione), l'impegno di safe harbor per la ricerca in buona fede, la presa in carico entro 5 giorni lavorativi, i limiti espliciti (niente DoS, social engineering, accesso fisico, dati reali di terzi) e un riepilogo in inglese. Il rinnovo di `Expires` è tracciato da PRC-security-txt-renewal.

  • CTL-build-provenance attivo Attestazione di provenienza SLSA/in-toto sui binari standalone di sg-attest (A1)
    come verificare

    Procedura senza alcuna credenziale (chiude il gap PRN-08 descritto in ADR-A1): clona il repository pubblico, installa le dipendenze di sviluppo ed esegui lo script di verifica indipendente su un binario scaricato dalla pagina Release pubblica (es. `sg-attest-linux-x64` dal tag `v0.4.2`, https://github.com/SPAZIO-GENESI/attest-mcp/releases): `git clone https://github.com/SPAZIO-GENESI/attest-mcp && cd attest-mcp && npm install && node scripts/verify-provenance.mjs ./sg-attest-linux-x64 --repo SPAZIO-GENESI/attest-mcp --tag v0.4.2` Esito atteso: verifica riuscita (exit 0), con un predicato SLSA che referenzia il commit, il workflow `release-binaries.yml` e il tag. Lo script interroga solo l'endpoint REST pubblico delle attestazioni GitHub (nessun header Authorization, verificato) e verifica il bundle Sigstore con la libreria npm `sigstore` contro l'infrastruttura pubblica di Sigstore (Rekor/Fulcio/TUF, nessun account). La stessa verifica su un file alterato di un solo byte fallisce (il digest cambia, quindi cambia la chiave stessa con cui si cerca l'attestazione) — provato, così come il caso di un file rinominato ma con contenuto intatto (verifica riuscita con avviso, perché il legame è sul digest del subject in-toto, non sul nome file). In alternativa, per chi ha già `gh` autenticato: `gh attestation verify sg-attest-linux-x64 --repo SPAZIO-GENESI/attest-mcp` fa la stessa verifica, ma **richiede** una sessione GitHub autenticata anche su questo repository pubblico (verificato: senza `gh auth login`/ `GH_TOKEN`, sia `gh attestation verify` sia `gh attestation download` rispondono exit 4, "please run gh auth login") — un limite del client, non del dato: l'endpoint REST sottostante è pubblico. Vedi ADR-A1 per la cronologia completa: il gap era nel client `gh`, non nella disponibilità del dato, e ora esiste una procedura equivalente che non ne eredita il limite.

  • CTL-scorecard-monitoring attivo Monitoraggio pubblico della postura di sicurezza via OpenSSF Scorecard (A3)
    come verificare

    Senza alcuna credenziale: `curl https://api.securityscorecards.dev/projects/github.com/SPAZIO-GENESI/imgauth` (e allo stesso modo per `/SPAZIO-GENESI/imgauthweb` e `/SPAZIO-GENESI/autart-signer`) restituisce il punteggio pubblico OpenSSF Scorecard più recente con la scomposizione per singolo check (Dependency-Update-Tool, Maintained, Dangerous-Workflow, Token-Permissions, Pinned-Dependencies, ecc.). Primo dato reale raccolto il 2026-08-13: imgauth 5.7/10, imgauthweb 5.4/10, autart-signer 2.9/10. Stesso giorno, in un secondo giro: branch protection attivata sui tre repo (no force-push, no cancellazione branch, status check `check` richiesto dove esiste — imgauth/imgauthweb; assente su autart-signer, che non ha alcun workflow su `pull_request`) e fine-grained PAT (`SCORECARD_TOKEN`, solo `Administration:Read-only`) aggiunto a `scorecard.yml` perché il `GITHUB_TOKEN` di default non può leggere le regole di branch protection — senza, il check Branch-Protection andava in errore (-1, escluso dal punteggio) invece di essere valutato. Con il fix: imgauth 7.6/10, imgauthweb 7.6/10, autart-signer 5.6/10, Branch-Protection 3/10 su tutti e tre ("not maximal": manca la PR obbligatoria, scartata di proposito perché il flusso di lavoro reale è a push diretto su main). Ogni repo ha un workflow schedulato settimanale (`scorecard.yml`, `workflow_dispatch` per un run manuale) che pubblica il risultato anche sul tab "Security → Code scanning" di GitHub — quella pagina però **richiede una sessione GitHub autenticata anche su repository pubblici** (verificato: `curl` a `github.com/.../security/code-scanning` senza credenziali risponde 404, e l'endpoint REST equivalente `api.github.com/.../code-scanning/alerts` risponde 401) — stesso tipo di limite del client `gh` già documentato in `CTL-build-provenance`/ADR-A1. L'API pubblica di securityscorecards.dev, usata sopra, non ha questo limite ed è la via primaria di verifica di questo controllo.

  • CTL-static-analysis attivo Analisi statica del codice (CodeQL) e aggiornamento automatico delle dipendenze (Dependabot) (A3)
    come verificare

    Senza alcuna credenziale: `curl https://api.github.com/repos/SPAZIO-GENESI/imgauth/actions/workflows/codeql.yml/runs?per_page=1` (endpoint REST pubblico, nessuna autenticazione — verificato) restituisce l'esito dell'ultimo run di CodeQL su main; ripetibile per gli altri quattro repo con codice JavaScript/TypeScript reale (imgauthweb, attest-mcp, attest-bot, attest-mcp-remote). ⚠️ Correzione rispetto al design doc originario: il tab web "Security → Code scanning" di GitHub **non è raggiungibile senza login anche su repository pubblici** (verificato il 2026-08-13: `curl` senza credenziali su `github.com/.../security/code-scanning` risponde 404, l'endpoint REST equivalente `api.github.com/.../code-scanning/alerts` risponde 401) — non è quindi utilizzabile come verify_howto per PRN-08. L'endpoint dei run del workflow, usato sopra, non ha questo limite. Verificato dal vivo il 2026-08-13, dopo il merge delle 10 PR di A3: CodeQL verde (`completed`/`success`) su tutti e cinque i repo. Dependabot ha aperto 24 pull request automatiche di aggiornamento dipendenze sullo stesso giro (visibili pubblicamente sulla tab Pull Requests di ciascun repo, `state=all` incluso via API senza autenticazione); il triage umano di questi alert è un processo ricorrente separato, già registrato come `PRC-security-alert-triage` (Federico Battisti, cadenza settimanale).

REQ-27001-inspiration-01

partial

ISO/IEC 27037

principi di identificazione e acquisizione della prova digitale

L'acquisizione dell'impronta digitale dell'opera deve essere documentata e riproducibile: algoritmo dichiarato, implementazione ispezionabile.

  • CTL-hash-client-side attivo L'impronta è calcolata nel browser; il file non lascia il dispositivo
    come verificare

    Apri attestazione.spaziogenesi.org con DevTools › Network, attesta un file: la richiesta a /api/hash contiene solo {sha256,name,type,size}, nessun byte del file.

  • CTL-iso27037-honest-positioning attivo Dichiarazione esplicita: applicazione parziale e per ispirazione di ISO/IEC 27037, non conformità forense piena
    come verificare

    Confronta i due requisiti collegati (REQ-27037-acq-01, REQ-27037-pres-01): entrambi dichiarano applicability "partial", mai "full". Il codice di acquisizione (hash client-side) e i tre meccanismi di conservazione (R2 immutabile, HMAC, TSA, OTS) sono verificabili nei repo pubblici imgauth/imgauthweb; l'assenza di log di catena di custodia consultabile e di ruoli forensi formalizzati è un gap dichiarato, non nascosto.

REQ-27037-acq-01

partial

ISO/IEC 27037

principi di conservazione della prova digitale

Le prove (certificato, ancoraggio) devono essere conservate con integrità dimostrabile e non alterabile silenziosamente nel tempo.

  • CTL-r2-eu-archive attivo Archivio certificati in giurisdizione EU, recuperabile solo da chi conosce l'hash
    come verificare

    GET /api/cert?hash=<sha256> restituisce il PDF archiviato per quell'hash; un hash malformato o assente risponde 400/404, mai il contenuto di un altro certificato.

  • CTL-ots-anchor attivo Ancoraggio indipendente in Bitcoin (OpenTimestamps), ridondato su 4 calendar
    come verificare

    Scarica la prova da /api/ots?hash=<sha256> e verificala con un client OTS pubblico o su opentimestamps.org, indipendentemente da questo servizio.

  • CTL-dogfooding-anchor attivo Ancoraggio dogfooding: lo storico di evidenze del GTF è attestato e ancorato in Bitcoin col proprio servizio
    come verificare

    Mensile: generators/anchor-monthly.mjs concatena i manifest.json degli snapshot settimanali non ancora coperti in un bundle deterministico (committato in snapshots/anchors/); un umano lo trascina su attestazione.spaziogenesi.org, genera l'attestazione e scarica il PDF (solo /api/cert-pdf innesca l'ancoraggio OpenTimestamps reale). Chiunque può ricalcolare l'hash del bundle nel repo gtf, confrontarlo con quello stampato sul certificato, e verificare la prova .ots indipendentemente su opentimestamps.org. Primo ancoraggio: periodo 2026-07 (bundle snapshots/anchors/2026-07-bundle.json, hash cd57b5d3a96947a2264cbb237b3c8eb26cb130e2703838e0597d7b3189e5629b).

  • CTL-iso27037-honest-positioning attivo Dichiarazione esplicita: applicazione parziale e per ispirazione di ISO/IEC 27037, non conformità forense piena
    come verificare

    Confronta i due requisiti collegati (REQ-27037-acq-01, REQ-27037-pres-01): entrambi dichiarano applicability "partial", mai "full". Il codice di acquisizione (hash client-side) e i tre meccanismi di conservazione (R2 immutabile, HMAC, TSA, OTS) sono verificabili nei repo pubblici imgauth/imgauthweb; l'assenza di log di catena di custodia consultabile e di ruoli forensi formalizzati è un gap dichiarato, non nascosto.

  • CTL-r2-offsite-backup attivo Backup offsite dell'archivio certificati, verificato con un restore drill reale
    come verificare

    Verbale pubblico dell'ultima prova di ripristino in gtf/docs/verbali/2026-07-restore-drill.md: campioni ripristinati dal backup (non dall'archivio primario) e verificati con gli strumenti pubblici standard (firma HMAC via /api/verify, ancoraggio Bitcoin, badge d'archivio). I dettagli operativi del backup (provider, regione, nomi) non sono pubblici per scelta — vedi ADR-GTF-013.

  • CTL-maintainer-succession bozza Procedura di successione tecnica, con garanzia residua verificabile indipendentemente dall'ente
    come verificare

    Due cose distinte sono verificabili oggi, senza credenziali, da chiunque — e vanno tenute separate. (1) La garanzia residua per chi possiede un certificato è verificabile ORA, in produzione, indipendentemente da questo documento: prendi un file attestato, il suo certificato PDF e il file .ots scaricato dopo l'emissione; verifica il file .ots con un client OpenTimestamps pubblico (es. https://opentimestamps.org, oppure il comando `ots verify` del client ufficiale) contro la blockchain Bitcoin — nessun server di Spazio Genesi coinvolto; apri il certificato PDF in un lettore PAdES conforme (es. Adobe Acrobat Reader) e verifica che la marca temporale risulti di terza parte attendibile (radice DigiCert nella Adobe Approved Trust List). (2) La procedura di successione stessa (piano-2026/umano/procedura-successione.md, repo privato img-auth-hub) NON è oggi pubblicata: è una bozza completata ma non ancora deliberata né pubblicata dall'organo competente. Il controllo resta "draft" anche per questo motivo, oltre che per l'assenza di un collaudo del recupero segreti da parte di una persona diversa dal maintainer (vedi ADR-B3). Diventerà verificabile per intero solo quando la procedura sarà pubblicata.

REQ-27037-pres-01

partial

ISO/IEC 27042

analisi e interpretazione della prova digitale

Deve esistere un modo per un terzo di analizzare e interpretare l'autenticità di una prova (il certificato) senza doversi fidare ciecamente di chi l'ha emessa.

  • CTL-cert-pdf-verification attivo Verifica indipendente dell'autenticità di un certificato a partire dal solo PDF
    come verificare

    Trascina un certificato PDF emesso nella scheda "Verifica" di attestazione.spaziogenesi.org: il semaforo mostra corrispondenza, autenticità della firma, integrità dei dati e stato dell'ancoraggio Bitcoin — senza dover fidarsi della sola parola del servizio.

REQ-27042-ver-01

partial

ISO/IEC 27043

processi di investigazione e risposta agli incidenti

Deve esistere un processo definito e provato di rilevazione e risposta ai guasti/incidenti, non solo una reazione ad hoc.

  • CTL-availability-monitoring attivo Stato semaforico pubblico dei servizi, con storico proporzionale e drill-down giornaliero
    come verificare

    Consulta https://attestazione.spaziogenesi.org/status/: stato live, barre 90 giorni con uptime % pesato sul tempo (P36, 2026-07-21: un disservizio breve mostra una tacca proporzionale alla sua durata reale, non più l'intera giornata colorata), drill-down sugli eventi fini di ogni giornata (anche quelle "verdi") con un riquadro d'impatto onesto.

  • CTL-responsible-disclosure attivo security.txt (RFC 9116) + policy pubblica di responsible disclosure
    come verificare

    `curl https://attestazione.spaziogenesi.org/.well-known/security.txt` e `curl https://imgauth.spaziogenesi.org/.well-known/security.txt` rispondono entrambi 200 con `Contact: mailto:it@spaziogenesi.org`, un `Expires` nel futuro e `Policy: https://attestazione.spaziogenesi.org/sicurezza/`. Quella pagina elenca l'ambito (i due domini, i servizi workers.dev attest-bot/attest-mcp-remote, i repo GitHub pubblici dell'organizzazione), l'impegno di safe harbor per la ricerca in buona fede, la presa in carico entro 5 giorni lavorativi, i limiti espliciti (niente DoS, social engineering, accesso fisico, dati reali di terzi) e un riepilogo in inglese. Il rinnovo di `Expires` è tracciato da PRC-security-txt-renewal.

REQ-27043-ir-01

partial

CAD + Linee guida AgID

formazione del documento informatico: integrità e riferimento temporale opponibile

Il certificato, come documento informatico, deve avere una firma e un riferimento temporale che ne rendano opponibile l'integrità nel tempo.

  • CTL-pades-blt-tsa attivo Firma PAdES B-LT con marca temporale RFC 3161 da TSA riconosciuta (Adobe AATL)
    come verificare

    Apri un certificato PDF emesso in Adobe Acrobat (o un lettore PAdES conforme): il pannello firme mostra la marca temporale da terza parte attendibile (Adobe AATL), verificabile senza fidarsi del servizio. Dal 2026-07-09 il codice che genera la firma è anche direttamente ispezionabile: github.com/SPAZIO-GENESI/autart-signer (AGPL-3.0).

REQ-cad-doc-01

partial

eIDAS 2.0

art. 46-equivalente — non discriminazione degli effetti giuridici delle prove elettroniche non qualificate

Il servizio deve informare in modo veritiero sul livello di garanzia offerto, senza lasciar intendere una qualificazione eIDAS che non possiede.

  • CTL-eidas-honest-positioning attivo Dichiarazione esplicita e stabile: attestazione non qualificata, non un servizio eIDAS qualificato
    come verificare

    Consulta https://trust.spaziogenesi.org, sezione "Cosa NON è questo servizio": il testo dichiara esplicitamente la natura non qualificata dell'attestazione. Se il testo dovesse sparire o essere annacquato in una futura revisione, il registro GTF lo renderebbe visibile (il record sorgente di questo controllo resta pubblico e versionato).

REQ-eidas-pos-01

full

GDPR

art. 13/14 — informativa all'interessato

Chi usa il servizio deve poter leggere in modo chiaro cosa viene trattato, con quale base giuridica e per quanto tempo.

  • CTL-privacy-policy-public attivo Informativa privacy pubblica e aggiornata
    come verificare

    Consulta privacy.html su attestazione.spaziogenesi.org: descrive cosa viene trattato, che il file non lascia mai il dispositivo, le basi giuridiche e i diritti dell'interessato.

  • CTL-dev-selfservice attivo Self-service API key con verifica email OAuth one-shot (Google/Microsoft/LinkedIn), post-moderazione
    come verificare

    Apri attestazione.spaziogenesi.org/developer/keys/ (P29: pagina statica su authweb, tre bottoni "Continua con Google/Microsoft/LinkedIn" ora fissi nell'HTML — trade-off accettato in FASE 3, non riflettono più la configurazione runtime del provider su imgauth): mostra l'informativa privacy sintetica prima del redirect (LinkedIn dal 2026-07-12, imgauth 1.20.0, ADR-P25-linkedin). Dopo il login, la callback OAuth reindirizza con la chiave sg_k_… SOLO nel fragment dell'URL (`#sgk=…`, mai in una risposta HTML del server, P29), mostrata una sola volta dal JS lato client e usabile su /api/hash come qualunque altra API key. Una seconda richiesta con la stessa email non emette una seconda chiave (fragment `#sgstate=gia-attiva`). Nel pannello /admin la chiave compare con colonna Titolare (email + provider); revocandola compare "Dimentica titolare" per l'anonimizzazione immediata (altrimenti automatica dopo 180 giorni dalla revoca, via cron).

  • CTL-telegram-channel attivo Canale Telegram (attest-bot): disclosure bloccante, hash in streaming, quote e retention
    come verificare

    Apri @SGAttestBot su Telegram e invia un file come primo messaggio: il bot risponde con l'avvertenza sul transito del file PRIMA di scaricare qualunque cosa (nessuna chiamata a getFile finché non accetti). Dopo l'accettazione, un file attestato produce un'impronta identica a quella calcolata a mano sullo stesso file (nessuna alterazione in transito). Una foto (non un documento) viene rifiutata con l'avviso sulla ricompressione, senza alcun download. Sesto file di attestazione nello stesso giorno (o ventunesima verifica) → messaggio di quota, nessuna chiamata a imgauth. Scrivendo a info@spaziogenesi.org si può chiedere la cancellazione anticipata dell'id utente e dei contatori d'uso.

  • CTL-pro-subscription attivo Fascia Professionale: abbonamento Stripe (Checkout/Portal hosted, nessun dato di carta sul Worker), pagina profilo, listino/sconti gestiti in D1
    come verificare

    Apri attestazione.spaziogenesi.org/profilo/ (P29: guscio statico su authweb, fetch verso imgauth), accedi con un login OAuth one-shot (Google/Microsoft/LinkedIn — stesso meccanismo di CTL-dev-selfservice e CTL-site-voucher-auth, nessuna password, nessun account). Senza abbonamento: prezzo del listino attivo mostrato dinamicamente (mai hardcodato) e bottone Checkout, che apre una pagina Stripe hosted — nessun campo carta compare mai su imgauth.spaziogenesi.org. Con un abbonamento attivo: stato, scadenza, log ricariche, consumo del mese, archivio dei certificati con canale di produzione (web/api/mcp/telegram), bottone "Gestisci o cessa l'abbonamento" che apre lo Stripe Customer Portal hosted (fatture, metodo di pagamento, cessazione — mai su questo Worker). Una cessazione dal Portal (comportamento di default per abbonamenti annuali: resta attivo fino alla scadenza già pagata) mostra un banner "cessazione programmata" senza perdere la fascia. La catena di precedenza convenzione→professionale→sviluppatore→base non blocca mai: quota o abbonamento esauriti degradano a Base con motivo esplicito (`fascia_motivo`), stessa logica già verificata per CTL-convention-accounting. Il listino e i codici sconto si gestiscono dal pannello /admin (scheda Professionale): righe con finestre di validità (mai si modifica un importo passato, si chiude e se ne crea una nuova) e sconti percentuali/fissi, anche riservati a una singola email. ✅ Collaudato con un **abbonamento reale a pagamento** (1€, non un dato sintetico) end-to-end in produzione il 17-18 luglio 2026 — vedi EVD-pro-subscription-live-test: checkout → attestazione dal sito riconosciuta in fascia professionale → archivio con canale → cessazione dal Customer Portal → rimborso. Nel collaudo emersi e corretti tre difetti reali mai incontrati nei test locali (shape dell'API Stripe dell'account, `cancel_at_period_end` sempre `false`, doppio evento del Customer Portal per un click) — vedi ADR-P27. Nasce direttamente `active`, stesso criterio già applicato a CTL-cicd-pipeline, CTL-site-voucher-auth e CTL-convention-accounting: un controllo collaudato con dati reali, non solo dichiarato.

  • CTL-integrations-showcase attivo Vetrina pubblica Integrazioni (pre-moderata) + convenzioni partner software house con pool mensile
    come verificare

    Apri attestazione.spaziogenesi.org/integrazioni/ (P29: pagina statica su authweb, rigenerata a evento da imgauth): vetrina pubblica, solo le candidature approvate compaiono (logo, nome, descrizione, link `rel="noopener nofollow"`), con conteggio totale e spiegazione onesta che la presenza non è una certificazione del software. Chi ha una chiave API attiva o un abbonamento Professionale può candidare la propria applicazione dalla sezione "La tua integrazione" di attestazione.spaziogenesi.org/profilo/ (stesso login OAuth one-shot di CTL-dev-selfservice e CTL-pro-subscription): la candidatura risulta subito `pending`, mai pubblica finché il gestore non la approva dal pannello /admin (scheda Integrazioni) — pre-moderazione, a differenza di ogni altro flusso self-service del sistema. Qualunque modifica successiva — nome, URL, descrizione o solo il logo — riporta lo stato a `pending`: non esiste un canale per cambiare silenziosamente ciò che è già online. Il logo, se presente, è servito solo per le candidature approvate (`GET /integrazioni/logo/<id>`, 404 altrimenti) ed è stato validato all'upload sui magic bytes (PNG/JPEG/WebP, mai SVG, mai il Content-Type dichiarato). Le software house che attestano per conto dei propri utenti usano una convenzione con pool mensile dedicato (`convention_id` sulla chiave, `domains` placeholder che non può mai combaciare con un'email reale) — stesso meccanismo di CTL-convention-accounting, riusato senza nuovo codice sul motore. Il "forget" GDPR del pannello admin anonimizza anche il titolare di una candidatura e la ritira dalla vetrina.

REQ-gdpr-info-01

full

GDPR

art. 5(1)(c) — minimizzazione

Trattare solo i dati strettamente necessari alla finalità di attestazione

  • CTL-hash-client-side attivo L'impronta è calcolata nel browser; il file non lascia il dispositivo
    come verificare

    Apri attestazione.spaziogenesi.org con DevTools › Network, attesta un file: la richiesta a /api/hash contiene solo {sha256,name,type,size}, nessun byte del file.

  • CTL-matomo-cookieless attivo Analytics senza cookie, nessun identificatore persistente
    come verificare

    Ispeziona i cookie impostati da attestazione.spaziogenesi.org con gli strumenti sviluppatore del browser: nessun cookie Matomo; coerentemente non è presente alcun banner di consenso cookie.

  • CTL-dev-selfservice attivo Self-service API key con verifica email OAuth one-shot (Google/Microsoft/LinkedIn), post-moderazione
    come verificare

    Apri attestazione.spaziogenesi.org/developer/keys/ (P29: pagina statica su authweb, tre bottoni "Continua con Google/Microsoft/LinkedIn" ora fissi nell'HTML — trade-off accettato in FASE 3, non riflettono più la configurazione runtime del provider su imgauth): mostra l'informativa privacy sintetica prima del redirect (LinkedIn dal 2026-07-12, imgauth 1.20.0, ADR-P25-linkedin). Dopo il login, la callback OAuth reindirizza con la chiave sg_k_… SOLO nel fragment dell'URL (`#sgk=…`, mai in una risposta HTML del server, P29), mostrata una sola volta dal JS lato client e usabile su /api/hash come qualunque altra API key. Una seconda richiesta con la stessa email non emette una seconda chiave (fragment `#sgstate=gia-attiva`). Nel pannello /admin la chiave compare con colonna Titolare (email + provider); revocandola compare "Dimentica titolare" per l'anonimizzazione immediata (altrimenti automatica dopo 180 giorni dalla revoca, via cron).

  • CTL-telegram-channel attivo Canale Telegram (attest-bot): disclosure bloccante, hash in streaming, quote e retention
    come verificare

    Apri @SGAttestBot su Telegram e invia un file come primo messaggio: il bot risponde con l'avvertenza sul transito del file PRIMA di scaricare qualunque cosa (nessuna chiamata a getFile finché non accetti). Dopo l'accettazione, un file attestato produce un'impronta identica a quella calcolata a mano sullo stesso file (nessuna alterazione in transito). Una foto (non un documento) viene rifiutata con l'avviso sulla ricompressione, senza alcun download. Sesto file di attestazione nello stesso giorno (o ventunesima verifica) → messaggio di quota, nessuna chiamata a imgauth. Scrivendo a info@spaziogenesi.org si può chiedere la cancellazione anticipata dell'id utente e dei contatori d'uso.

  • CTL-pro-subscription attivo Fascia Professionale: abbonamento Stripe (Checkout/Portal hosted, nessun dato di carta sul Worker), pagina profilo, listino/sconti gestiti in D1
    come verificare

    Apri attestazione.spaziogenesi.org/profilo/ (P29: guscio statico su authweb, fetch verso imgauth), accedi con un login OAuth one-shot (Google/Microsoft/LinkedIn — stesso meccanismo di CTL-dev-selfservice e CTL-site-voucher-auth, nessuna password, nessun account). Senza abbonamento: prezzo del listino attivo mostrato dinamicamente (mai hardcodato) e bottone Checkout, che apre una pagina Stripe hosted — nessun campo carta compare mai su imgauth.spaziogenesi.org. Con un abbonamento attivo: stato, scadenza, log ricariche, consumo del mese, archivio dei certificati con canale di produzione (web/api/mcp/telegram), bottone "Gestisci o cessa l'abbonamento" che apre lo Stripe Customer Portal hosted (fatture, metodo di pagamento, cessazione — mai su questo Worker). Una cessazione dal Portal (comportamento di default per abbonamenti annuali: resta attivo fino alla scadenza già pagata) mostra un banner "cessazione programmata" senza perdere la fascia. La catena di precedenza convenzione→professionale→sviluppatore→base non blocca mai: quota o abbonamento esauriti degradano a Base con motivo esplicito (`fascia_motivo`), stessa logica già verificata per CTL-convention-accounting. Il listino e i codici sconto si gestiscono dal pannello /admin (scheda Professionale): righe con finestre di validità (mai si modifica un importo passato, si chiude e se ne crea una nuova) e sconti percentuali/fissi, anche riservati a una singola email. ✅ Collaudato con un **abbonamento reale a pagamento** (1€, non un dato sintetico) end-to-end in produzione il 17-18 luglio 2026 — vedi EVD-pro-subscription-live-test: checkout → attestazione dal sito riconosciuta in fascia professionale → archivio con canale → cessazione dal Customer Portal → rimborso. Nel collaudo emersi e corretti tre difetti reali mai incontrati nei test locali (shape dell'API Stripe dell'account, `cancel_at_period_end` sempre `false`, doppio evento del Customer Portal per un click) — vedi ADR-P27. Nasce direttamente `active`, stesso criterio già applicato a CTL-cicd-pipeline, CTL-site-voucher-auth e CTL-convention-accounting: un controllo collaudato con dati reali, non solo dichiarato.

  • CTL-integrations-showcase attivo Vetrina pubblica Integrazioni (pre-moderata) + convenzioni partner software house con pool mensile
    come verificare

    Apri attestazione.spaziogenesi.org/integrazioni/ (P29: pagina statica su authweb, rigenerata a evento da imgauth): vetrina pubblica, solo le candidature approvate compaiono (logo, nome, descrizione, link `rel="noopener nofollow"`), con conteggio totale e spiegazione onesta che la presenza non è una certificazione del software. Chi ha una chiave API attiva o un abbonamento Professionale può candidare la propria applicazione dalla sezione "La tua integrazione" di attestazione.spaziogenesi.org/profilo/ (stesso login OAuth one-shot di CTL-dev-selfservice e CTL-pro-subscription): la candidatura risulta subito `pending`, mai pubblica finché il gestore non la approva dal pannello /admin (scheda Integrazioni) — pre-moderazione, a differenza di ogni altro flusso self-service del sistema. Qualunque modifica successiva — nome, URL, descrizione o solo il logo — riporta lo stato a `pending`: non esiste un canale per cambiare silenziosamente ciò che è già online. Il logo, se presente, è servito solo per le candidature approvate (`GET /integrazioni/logo/<id>`, 404 altrimenti) ed è stato validato all'upload sui magic bytes (PNG/JPEG/WebP, mai SVG, mai il Content-Type dichiarato). Le software house che attestano per conto dei propri utenti usano una convenzione con pool mensile dedicato (`convention_id` sulla chiave, `domains` placeholder che non può mai combaciare con un'email reale) — stesso meccanismo di CTL-convention-accounting, riusato senza nuovo codice sul motore. Il "forget" GDPR del pannello admin anonimizza anche il titolare di una candidatura e la ritira dalla vetrina.

REQ-gdpr-min-01

partial

GDPR

artt. 15-22 — diritti dell'interessato (accesso, cancellazione, opposizione...)

L'interessato deve poter esercitare i propri diritti sui dati trattati. Applicabilità parziale: per chi attesta un'opera, il servizio non crea account, non tratta il file originale (mai inviato al server) e l'analytics è senza identificatori persistenti — la maggior parte dei diritti classici (accesso al proprio profilo, portabilità di dati account) non si applica per assenza stessa del dato, non per una scelta di non conformità. Dalla P22 (2026-07-10) esiste una seconda categoria di interessati con dati reali da gestire: gli sviluppatori che richiedono una chiave API self-service (email + provider OAuth). Per loro il diritto di cancellazione è self-service-adiacente (richiesta al gestore → anonimizzazione immediata dal pannello admin) e comunque automatico dopo 180 giorni dalla revoca della chiave. Dalla P23 (2026-07-11) esiste una terza categoria: chi usa il bot Telegram (id utente + contatori d'uso). Anche qui la cancellazione è su richiesta al gestore e comunque automatica dopo 90 giorni. Dalla P27 (2026-07-17/18) esiste una quarta categoria: chi sottoscrive l'abbonamento Professionale (email, provider, riferimenti abbonamento Stripe, log delle attestazioni, profilazione facoltativa). L'interessato può gestire in autonomia dalla pagina `/profilo` la profilazione facoltativa (salvataggio/rimozione, nessuna richiesta al gestore necessaria) e chiedere la cancellazione anticipata dei dati dell'abbonamento — con un trade-off esplicito e dichiarato: perde l'accesso al proprio archivio consultabile (i certificati restano comunque validi e recuperabili per impronta, come per chiunque), e può chiedere anche l'eliminazione dei PDF archiviati non condivisi con altre attestazioni.

  • CTL-hash-client-side attivo L'impronta è calcolata nel browser; il file non lascia il dispositivo
    come verificare

    Apri attestazione.spaziogenesi.org con DevTools › Network, attesta un file: la richiesta a /api/hash contiene solo {sha256,name,type,size}, nessun byte del file.

  • CTL-matomo-cookieless attivo Analytics senza cookie, nessun identificatore persistente
    come verificare

    Ispeziona i cookie impostati da attestazione.spaziogenesi.org con gli strumenti sviluppatore del browser: nessun cookie Matomo; coerentemente non è presente alcun banner di consenso cookie.

  • CTL-dev-selfservice attivo Self-service API key con verifica email OAuth one-shot (Google/Microsoft/LinkedIn), post-moderazione
    come verificare

    Apri attestazione.spaziogenesi.org/developer/keys/ (P29: pagina statica su authweb, tre bottoni "Continua con Google/Microsoft/LinkedIn" ora fissi nell'HTML — trade-off accettato in FASE 3, non riflettono più la configurazione runtime del provider su imgauth): mostra l'informativa privacy sintetica prima del redirect (LinkedIn dal 2026-07-12, imgauth 1.20.0, ADR-P25-linkedin). Dopo il login, la callback OAuth reindirizza con la chiave sg_k_… SOLO nel fragment dell'URL (`#sgk=…`, mai in una risposta HTML del server, P29), mostrata una sola volta dal JS lato client e usabile su /api/hash come qualunque altra API key. Una seconda richiesta con la stessa email non emette una seconda chiave (fragment `#sgstate=gia-attiva`). Nel pannello /admin la chiave compare con colonna Titolare (email + provider); revocandola compare "Dimentica titolare" per l'anonimizzazione immediata (altrimenti automatica dopo 180 giorni dalla revoca, via cron).

  • CTL-telegram-channel attivo Canale Telegram (attest-bot): disclosure bloccante, hash in streaming, quote e retention
    come verificare

    Apri @SGAttestBot su Telegram e invia un file come primo messaggio: il bot risponde con l'avvertenza sul transito del file PRIMA di scaricare qualunque cosa (nessuna chiamata a getFile finché non accetti). Dopo l'accettazione, un file attestato produce un'impronta identica a quella calcolata a mano sullo stesso file (nessuna alterazione in transito). Una foto (non un documento) viene rifiutata con l'avviso sulla ricompressione, senza alcun download. Sesto file di attestazione nello stesso giorno (o ventunesima verifica) → messaggio di quota, nessuna chiamata a imgauth. Scrivendo a info@spaziogenesi.org si può chiedere la cancellazione anticipata dell'id utente e dei contatori d'uso.

  • CTL-pro-subscription attivo Fascia Professionale: abbonamento Stripe (Checkout/Portal hosted, nessun dato di carta sul Worker), pagina profilo, listino/sconti gestiti in D1
    come verificare

    Apri attestazione.spaziogenesi.org/profilo/ (P29: guscio statico su authweb, fetch verso imgauth), accedi con un login OAuth one-shot (Google/Microsoft/LinkedIn — stesso meccanismo di CTL-dev-selfservice e CTL-site-voucher-auth, nessuna password, nessun account). Senza abbonamento: prezzo del listino attivo mostrato dinamicamente (mai hardcodato) e bottone Checkout, che apre una pagina Stripe hosted — nessun campo carta compare mai su imgauth.spaziogenesi.org. Con un abbonamento attivo: stato, scadenza, log ricariche, consumo del mese, archivio dei certificati con canale di produzione (web/api/mcp/telegram), bottone "Gestisci o cessa l'abbonamento" che apre lo Stripe Customer Portal hosted (fatture, metodo di pagamento, cessazione — mai su questo Worker). Una cessazione dal Portal (comportamento di default per abbonamenti annuali: resta attivo fino alla scadenza già pagata) mostra un banner "cessazione programmata" senza perdere la fascia. La catena di precedenza convenzione→professionale→sviluppatore→base non blocca mai: quota o abbonamento esauriti degradano a Base con motivo esplicito (`fascia_motivo`), stessa logica già verificata per CTL-convention-accounting. Il listino e i codici sconto si gestiscono dal pannello /admin (scheda Professionale): righe con finestre di validità (mai si modifica un importo passato, si chiude e se ne crea una nuova) e sconti percentuali/fissi, anche riservati a una singola email. ✅ Collaudato con un **abbonamento reale a pagamento** (1€, non un dato sintetico) end-to-end in produzione il 17-18 luglio 2026 — vedi EVD-pro-subscription-live-test: checkout → attestazione dal sito riconosciuta in fascia professionale → archivio con canale → cessazione dal Customer Portal → rimborso. Nel collaudo emersi e corretti tre difetti reali mai incontrati nei test locali (shape dell'API Stripe dell'account, `cancel_at_period_end` sempre `false`, doppio evento del Customer Portal per un click) — vedi ADR-P27. Nasce direttamente `active`, stesso criterio già applicato a CTL-cicd-pipeline, CTL-site-voucher-auth e CTL-convention-accounting: un controllo collaudato con dati reali, non solo dichiarato.

  • CTL-integrations-showcase attivo Vetrina pubblica Integrazioni (pre-moderata) + convenzioni partner software house con pool mensile
    come verificare

    Apri attestazione.spaziogenesi.org/integrazioni/ (P29: pagina statica su authweb, rigenerata a evento da imgauth): vetrina pubblica, solo le candidature approvate compaiono (logo, nome, descrizione, link `rel="noopener nofollow"`), con conteggio totale e spiegazione onesta che la presenza non è una certificazione del software. Chi ha una chiave API attiva o un abbonamento Professionale può candidare la propria applicazione dalla sezione "La tua integrazione" di attestazione.spaziogenesi.org/profilo/ (stesso login OAuth one-shot di CTL-dev-selfservice e CTL-pro-subscription): la candidatura risulta subito `pending`, mai pubblica finché il gestore non la approva dal pannello /admin (scheda Integrazioni) — pre-moderazione, a differenza di ogni altro flusso self-service del sistema. Qualunque modifica successiva — nome, URL, descrizione o solo il logo — riporta lo stato a `pending`: non esiste un canale per cambiare silenziosamente ciò che è già online. Il logo, se presente, è servito solo per le candidature approvate (`GET /integrazioni/logo/<id>`, 404 altrimenti) ed è stato validato all'upload sui magic bytes (PNG/JPEG/WebP, mai SVG, mai il Content-Type dichiarato). Le software house che attestano per conto dei propri utenti usano una convenzione con pool mensile dedicato (`convention_id` sulla chiave, `domains` placeholder che non può mai combaciare con un'email reale) — stesso meccanismo di CTL-convention-accounting, riusato senza nuovo codice sul motore. Il "forget" GDPR del pannello admin anonimizza anche il titolare di una candidatura e la ritira dalla vetrina.

REQ-gdpr-rights-01

Rischi

probabilità: low impatto: low

Pannello admin credenziali agente: accesso protetto in profondità

Mitigato da

  • CTL-agent-admin-panel attivo Pannello admin credenziali agente: elenco/emissione/revoca/quota con accesso riservato
  • CTL-cloudflare-access-admin attivo Cloudflare Access (Zero Trust) davanti al pannello admin credenziali agente
  • CTL-rate-limiting attivo Rate limiting per-IP su emissione certificati e hash/verify

RSK-admin-panel-weak-auth

probabilità: low impatto: low

Abuso di una credenziale agente valida (partner o sessione compromessa)

Mitigato da

  • CTL-agent-access attivo Accesso agenti: bearer token D1-backed (API key + device flow), bypass del solo Turnstile
  • CTL-hmac-signing attivo Token HMAC che vincola hash, timestamp server-side e metadati dichiarati
  • CTL-rate-limiting attivo Rate limiting per-IP su emissione certificati e hash/verify
  • CTL-site-voucher-auth attivo Voucher stateless firmato per l'accesso email dal sito ("attesta con la tua email")

RSK-agent-credential-abuse

probabilità: low impatto: high

Alterazione dei certificati archiviati o della cronologia di emissione

Mitigato da

  • CTL-r2-eu-archive attivo Archivio certificati in giurisdizione EU, recuperabile solo da chi conosce l'hash
  • CTL-ots-anchor attivo Ancoraggio indipendente in Bitcoin (OpenTimestamps), ridondato su 4 calendar
  • CTL-dogfooding-anchor attivo Ancoraggio dogfooding: lo storico di evidenze del GTF è attestato e ancorato in Bitcoin col proprio servizio
  • CTL-r2-offsite-backup attivo Backup offsite dell'archivio certificati, verificato con un restore drill reale

RSK-archive-tampering

probabilità: medium impatto: medium

Abuso automatizzato del servizio (spam di attestazioni)

Mitigato da

  • CTL-turnstile-antibot attivo Challenge anti-bot prima di qualunque emissione di attestazione
  • CTL-rate-limiting attivo Rate limiting per-IP su emissione certificati e hash/verify

RSK-bot-abuse

probabilità: medium impatto: medium

Un processo ricorrente dichiarato smette di essere eseguito senza che nessuno se ne accorga

Mitigato da

  • CTL-cadence-monitoring attivo Controllo automatico delle cadenze ricorrenti (revisioni, drill, ancoraggio) con avviso Telegram

RSK-cadence-drift

probabilità: low impatto: high

Falsificazione di un certificato (hash falso o data retrodatata)

Mitigato da

  • CTL-hmac-signing attivo Token HMAC che vincola hash, timestamp server-side e metadati dichiarati
  • CTL-pades-blt-tsa attivo Firma PAdES B-LT con marca temporale RFC 3161 da TSA riconosciuta (Adobe AATL)
  • CTL-cert-pdf-verification attivo Verifica indipendente dell'autenticità di un certificato a partire dal solo PDF

RSK-cert-forgery

probabilità: low impatto: medium

Il log delle convenzioni associa una persona (email) alle opere che attesta

Mitigato da

  • CTL-convention-accounting attivo Convenzioni per dominio email: pool mensile dell'ente, degrado mai blocco, log minimizzato alle sole emissioni tassate

RSK-convention-data-linkage

probabilità: low impatto: high

Invio non necessario del file originale dell'utente al server

Mitigato da

  • CTL-hash-client-side attivo L'impronta è calcolata nel browser; il file non lascia il dispositivo

RSK-data-exfiltration

probabilità: low impatto: low

Emissione automatizzata o ripetuta di chiavi self-service (signup abuse)

Mitigato da

  • CTL-dev-selfservice attivo Self-service API key con verifica email OAuth one-shot (Google/Microsoft/LinkedIn), post-moderazione
  • CTL-hmac-signing attivo Token HMAC che vincola hash, timestamp server-side e metadati dichiarati
  • CTL-rate-limiting attivo Rate limiting per-IP su emissione certificati e hash/verify

RSK-dev-selfservice-signup-abuse

probabilità: medium impatto: high

Indisponibilità temporanea o permanente dell'unico maintainer tecnico

Mitigato da

  • CTL-maintainer-succession bozza Procedura di successione tecnica, con garanzia residua verificabile indipendentemente dall'ente

RSK-maintainer-unavailability

probabilità: medium impatto: high

Comunicare al pubblico un livello di garanzia legale superiore a quello reale

Mitigato da

  • CTL-eidas-honest-positioning attivo Dichiarazione esplicita e stabile: attestazione non qualificata, non un servizio eIDAS qualificato

RSK-overclaim-eidas

probabilità: low impatto: medium

Far intendere una piena conformità forense ISO/IEC 27037 che non esiste

Mitigato da

  • CTL-iso27037-honest-positioning attivo Dichiarazione esplicita: applicazione parziale e per ispirazione di ISO/IEC 27037, non conformità forense piena

RSK-overclaim-iso27037

probabilità: low impatto: medium

Dipendenza da Stripe per l'intero ciclo di vita del pagamento; disallineamento di stato su eventi webhook persi o in ritardo

Mitigato da

  • CTL-pro-subscription attivo Fascia Professionale: abbonamento Stripe (Checkout/Portal hosted, nessun dato di carta sul Worker), pagina profilo, listino/sconti gestiti in D1
  • CTL-cicd-pipeline attivo Catena di rilascio: staging replicato + CI + gate di produzione con approvazione umana (P24)

RSK-payment-integration

probabilità: low impatto: low

Profilazione facoltativa (segmento/regione, applicazione/OS/ambiente) — dato aggiuntivo oltre il minimo necessario all'erogazione del servizio

Mitigato da

  • CTL-pro-subscription attivo Fascia Professionale: abbonamento Stripe (Checkout/Portal hosted, nessun dato di carta sul Worker), pagina profilo, listino/sconti gestiti in D1

RSK-profiling-data

probabilità: low impatto: high

Compromissione o perdita di HMAC_SECRET / SIGN_SECRET

Mitigato da

  • CTL-secrets-escrow attivo Recupero possibile dei segreti critici senza dipendere dalla sola memoria del maintainer
  • CTL-hmac-canary attivo Canary esterno che rileva una rotazione errata del segreto HMAC

RSK-secret-compromise

probabilità: medium impatto: medium

Interruzione di un componente critico (Azure authart, R2, Worker Cloudflare)

Mitigato da

  • CTL-availability-monitoring attivo Stato semaforico pubblico dei servizi, con storico proporzionale e drill-down giornaliero

RSK-single-jurisdiction-outage

probabilità: low impatto: low

File dell'opera in transito su infrastruttura terza (Telegram) e sul Worker del bot

Mitigato da

  • CTL-telegram-channel attivo Canale Telegram (attest-bot): disclosure bloccante, hash in streaming, quote e retention
  • CTL-hmac-signing attivo Token HMAC che vincola hash, timestamp server-side e metadati dichiarati
  • CTL-rate-limiting attivo Rate limiting per-IP su emissione certificati e hash/verify

RSK-telegram-file-transit

probabilità: low impatto: low

Contenuto di terzi (nome, URL, logo) pubblicato nella vetrina Integrazioni sotto il nome Spazio Genesi

Mitigato da

  • CTL-integrations-showcase attivo Vetrina pubblica Integrazioni (pre-moderata) + convenzioni partner software house con pool mensile

RSK-third-party-content

probabilità: low impatto: medium

Una vulnerabilità reale non viene mai segnalata, o viene resa pubblica senza coordinamento

Mitigato da

  • CTL-responsible-disclosure attivo security.txt (RFC 9116) + policy pubblica di responsible disclosure

RSK-undisclosed-vulnerability

probabilità: medium impatto: medium

Modifica deployata in produzione senza mai essere stata osservata "dal vivo"

Mitigato da

  • CTL-cicd-pipeline attivo Catena di rilascio: staging replicato + CI + gate di produzione con approvazione umana (P24)

RSK-unobserved-production-deploy

Decisioni

Isolamento core ↔ tenant e versione per componenteaccepted

Il motore del Genesis Trust Framework aveva un solo progetto cablato dentro ai suoi generatori: 67 riferimenti al progetto attestazione, distribuiti su sette file, mescolavano struttura del registro e dati di un singolo tenant. Applicare il framework a un secondo progetto (RADART, P46) avrebbe richiesto un fork del motore, non un secondo pacchetto di configurazione.

Decisione: Separare motore e dati in due alberi distinti dentro lo stesso repository (P44): il motore agnostico in core/ (schemi, generatori, guardia di isolamento) e i dati di ogni progetto in tenants/<id>/ (registro, snapshot, configurazione). Una guardia CI (check-core-isolation.mjs) impedisce che nomi di progetto rientrino in core/. Ogni componente ha ora una propria versione SemVer: il motore (core/package.json) e ogni pacchetto tenant (tenants/<id>/tenant.config.json), mostrate entrambe nel footer del Trust Center.

Conseguenze: Un secondo tenant è ora un pacchetto di configurazione da aggiungere, non un fork del motore da mantenere. Il punteggio resta calcolato con la stessa formula per tutti i tenant. Nessun controllo di sicurezza nuovo: è una decisione di struttura, non di rischio. Nulla di pubblicato cambia di sostanza (stesso dominio, stessi record, stesso punteggio) tranne la nuova pagina changelog (F4) e i due numeri di versione nel footer (F5).

ADR-GTF-014

B3 — Il rischio dell'indisponibilità del maintainer, reso visibile nel registroaccepted

ADR-GTF-011 ha emendato la formula di MET-governance perché il progetto ha un solo maintainer e la revisione tra pari via pull request non si applica: con la formula originale lo score sarebbe sceso da 90 a circa 77 per un motivo che non riguarda la qualità del lavoro, ma la sua struttura organizzativa. La decisione era metodologicamente corretta — 0 sarebbe stato un numero falso. Perché questo rischio mancava dal registro, esplicitamente: la formula emendata di MET-governance misura la qualità del processo di rilascio (quota di run verdi di validate.yml, quota di gate umano approvato in P24) — un dato sulla robustezza del PROCESSO. Non misura, e non era mai stato pensato per misurare, la continuità della PERSONA che lo gestisce. Sono due domande diverse, e nessuna delle due copriva l'altra: un processo di rilascio robusto (CI + gate) non implica che esista un secondo umano in grado di approvarlo se il primo sparisce. Il rischio è concreto, non astratto: la fascia Professionale (P27) vende una garanzia contrattuale di custodia dei certificati a cinque anni, un impegno pluriennale verso terzi paganti sostenuto oggi dalla disponibilità continuativa di una sola persona.

Decisione: Aperti in questo giro `RSK-maintainer-unavailability` (likelihood medium, impact high — dichiarato onestamente, non addolcito: nessun secondo referente tecnico risulta individuato oggi) e `CTL-maintainer-succession`, che nasce **`draft`** e referenzia `piano-2026/umano/procedura-successione.md` (repo privato img-auth-hub). Collegato a `REQ-27037-pres-01` (conservazione delle prove nel tempo, non `REQ-27043-ir-01`: il punto di leva di questo controllo è la garanzia residua del §6 della procedura — la prova sopravvive all'ente — non la gestione dell'incidente in sé). In questa stessa sessione: (1) ricostruito un inventario delle risorse operative del progetto (account Cloudflare, Azure, organizzazione GitHub, npm, Stripe, backup offsite, bot Telegram, registrar del dominio) leggendo i repo invece che affidandosi alla memoria di qualcuno — tre fatti confermati con verifiche dirette e read-only (`gh auth status`/`gh api`, `wrangler whoami`, `az account show`), il resto marcato esplicitamente "da confermare col gestore" dove nessun file lo documentava (in particolare: registrar del dominio, account Stripe, account BotFather del bot Telegram — nessuna deduzione). (2) Verificate nel codice, non copiate dalla bozza, le due affermazioni tecniche del §6 della procedura: il file `.ots` prodotto da `imgauth/worker.js` (`createOtsProof`) è un `DetachedTimestampFile` standard che commit-ta l'operazione OpSHA256 sul digest reale dell'opera (non su un artefatto interno), verificabile con qualunque client OpenTimestamps indipendente dai server di Spazio Genesi; il certificato PDF porta davvero una marca temporale di terza parte (`authart/signer_app/app.py`, `HTTPTimeStamper` su `TSA_URL`, default DigiCert, radice AATL — dichiarato anche nel commento del codice sorgente). Un terzo elemento che la bozza dava per scontato — un verificatore offline dell'integrità del certificato — è risultato NON ancora esistente (voce A4 dello stesso piano, `status: da fare`): la procedura finale lo dichiara esplicitamente come parte futura della garanzia, non presente. (3) Preparato (non configurato) il codice per un secondo destinatario dell'allarme Telegram di `CTL-cadence-monitoring` (`gtf/generators/check-cadences.mjs`, nuovo parametro opzionale `TELEGRAM_CHAT_ID_SECONDARY` — nessun comportamento cambia finché il secret non viene creato: chi sarà il secondo destinatario è una decisione del gestore, non presa qui). Nota per PRN-02 ("ogni CTL che scrivi referenzia almeno una EVD con freshness dichiarata"): questo CTL non referenzia oggi alcuna EVD, deliberatamente. Non esiste ancora un'evidenza reale da citare — nessun secondo referente è stato individuato, nessun collaudo del recupero segreti da parte di terzi è stato eseguito, la procedura non è pubblicata — e inventarne una avrebbe violato l'invariante più vincolante del progetto (CLAUDE.md §2: mai scrivere un dato non verificato). Lo schema del registro non richiede `evidenced_by` per un controllo `draft`, solo per uno `active`. La prima `EVD` reale arriverà dal collaudo del recupero segreti da parte di una persona diversa dal gestore (§4/§7 della procedura) — un'azione umana, elencata in `STATO.md`.

Conseguenze: `CTL-maintainer-succession` nasce **`draft`**: la promozione ad `active` non è autorità di questa sessione (invariante vincolante del progetto, CLAUDE.md §2) e comunque non sarebbe onesta oggi — nessuno dei passaggi umani che chiudono davvero questa voce (secondo referente, collaudo dell'escrow da parte di terzi, delibera, pubblicazione) è avvenuto. Elenco delle azioni che restano solo al gestore, dettagliato in `STATO.md` (repo privato img-auth-hub): individuare un secondo referente tecnico o dichiarare per iscritto che non esiste; far provare a una persona diversa da sé il recupero dei segreti seguendo la procedura scritta, senza aiuto, e registrarne l'esito come `EVD`; compilare le voci marcate "da confermare col gestore" nell'inventario; portare la procedura all'organo competente e ottenere delibera; pubblicarla; decidere il secondo destinatario dell'allarme di cadenza. Fuori scopo, come da design doc: qualunque modifica alla gestione reale dei segreti; la configurazione effettiva del secondo destinatario dell'allarme; la delibera; la pubblicazione; la promozione del controllo ad `active`.

ADR-B3

A3 — Parte 4: rendere visibili sul Trust Center i controlli automatici già attivi (Scorecard, CodeQL, Dependabot)accepted

A3 (Parti 1-3) era chiusa e verificata con dati reali il 2026-08-13: OpenSSF Scorecard raccolto su imgauth (5.7), imgauthweb (5.4), autart-signer (2.9); CodeQL verde su cinque repo; `PRC-security-alert-triage` registrato. Nessuno di questi fatti era però referenziato da un `CTL` nel registro — quindi non compariva su trust.spaziogenesi.org e non entrava nel calcolo di `MET-automation`. Il lavoro tecnico era già fatto; mancava solo la sua rappresentazione nel registro.

Decisione: Scritti **due `CTL` separati**, non uno solo, perché misurano cose diverse con `verify_howto` diversi: `CTL-scorecard-monitoring` (punteggio OpenSSF Scorecard sui tre repo) e `CTL-static-analysis` (CodeQL + Dependabot sui cinque repo con codice JS reale). Entrambi collegati a `REQ-27001-inspiration-01` (stessa famiglia di `CTL-cicd-pipeline`/`CTL-build-provenance`). `PRC-security-alert-triage` (già esistente, invariato) resta il riferimento del triage umano, citato in prosa nel `verify_howto` di `CTL-static-analysis` — nessun campo formale dello schema collega un `CTL` a un `PRC`. Due nuovi `EVD` con `collection: auto`: `EVD-scorecard-scores` (referenzia `snapshots/2026-W33/scorecard.json`) e `EVD-codeql-runs-green` (referenzia i run pubblici di `codeql.yml` sui cinque repo). Nessun intervento su `score.mjs`: è il campo `collection: auto` a spostare il rapporto automatico/manuale di `MET-automation`, non la formula (PRN-07). **Correzione rispetto al design doc, verificata prima di scrivere il `verify_howto` (PRN-06/§8 del CLAUDE.md di sessione)**: il design doc proponeva "la scheda pubblica Security → Code scanning di ciascun repo (pubblica di default sui repo pubblici)" come procedura di verifica senza credenziali. Verificato con `curl` a ambiente pulito che è **falso**: `github.com/<org>/<repo>/security/code-scanning` risponde 404 senza sessione autenticata, e l'endpoint REST equivalente `api.github.com/.../code-scanning/alerts` risponde 401 — anche su repository pubblici. Stessa classe di gotcha già documentata in ADR-A1 per `gh attestation verify`. Trovato e verificato un endpoint pubblico realmente senza credenziali che copre lo stesso fatto (CodeQL verde): `api.github.com/repos/<org>/<repo>/actions/workflows/codeql.yml/runs` (200, nessun header Authorization) — usato come `verify_howto` primario di `CTL-static-analysis`, con la correzione dichiarata esplicitamente nel campo stesso, non silenziata. Entrambi i `CTL` nascono **`draft`**: nessuna promozione ad `active` decisa in questa sessione (non è la sua autorità, vedi `consequences`) — nonostante il collaudo con dati reali sia già avvenuto il 2026-08-13 sotto supervisione diretta del gestore, la promozione resta una decisione sua, da presentare esplicitamente e non da assumere per analogia con `CTL-build-provenance`/`CTL-pro-subscription`/ `CTL-site-voucher-auth` (stesso criterio, applicazione diversa).

Conseguenze: `CTL-scorecard-monitoring` e `CTL-static-analysis` restano `draft` finché il gestore non decide esplicitamente la promozione — punto presentato nel riepilogo di fine sessione, non deciso qui. `PRC-security-alert-triage` non è stato toccato. Nessuna modifica a `score.mjs`: se/quanto si sposta `MET-automation` dipende solo dall'aver aggiunto due `EVD` reali con `collection: auto`, verificabile con `npm run build` dopo il merge.

ADR-A3-parte4

A1 — Provenienza di build firmata sugli artefatti rilasciati (attest-mcp)accepted

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

P41 — Versione inglese del servizio: la lingua è resa, mai un fatto attestatoaccepted

Il servizio è nato interamente in italiano, ma i suoi canali programmabili (pacchetto npm, MCP Registry, GitHub Action, repository pubblici) sono per natura internazionali: chi arriva da lì incontrava documentazione, pagine pubbliche e certificati in una lingua che spesso non legge. Rendere il servizio bilingue tocca però superfici molto diverse per rischio — pagine statiche, pagine generate dal Worker, messaggi d'errore dell'API e, la più delicata, il contenuto del certificato PDF, cioè l'artefatto probatorio.

Decisione: Scopo **MVP** deliberato: il sito resta italiano, con un percorso inglese **completo** per chi arriva dal canale programmabile — sei pagine (home, fasce e condizioni, pagina sviluppatori e le sue due sottopagine chiave API e CLI, informativa privacy) duplicate sotto `/en/`, più le due pagine HTML servite dal Worker (`/c/<sha256>`, `/agent/authorize`) e i messaggi d'errore di `/api/hash` e `/api/cert-pdf` via campo facoltativo `lang` (fallback `Accept-Language`). Restano fuori le pagine che riguardano chi è già dentro (profilo, stato, sicurezza, vetrina, changelog) e lo storico: si estendono una pagina per volta, sulla stessa infrastruttura. **L'invariante che regge tutto il piano: `lang` non entra mai in `hmacMessage`.** Il messaggio firmato resta l'attestazione più gli eventuali metadati dichiarati in forma canonica, identico a prima di P41. La lingua è una scelta di **resa**, non un fatto sull'opera: legarla alla firma invaliderebbe in un colpo solo la verifica di tutti i certificati già emessi, senza aggiungere alcuna garanzia. Verificato in modo diretto, non assunto: la stessa coppia attestazione+HMAC produce il certificato in entrambe le lingue, `/api/verify` (che non ha né deve avere un parametro `lang`) risponde `hmac_valido: true` in entrambi i casi, e un certificato pre-P41 resta verificabile senza eccezioni. Due scelte di trasparenza, non di comodità. **(1)** L'informativa privacy inglese è dichiarata in testa alla pagina come **traduzione di cortesia**, con la versione italiana che fa fede: chi arriva dal canale inglese incontra il flusso OAuth self-service, e servirgli l'informativa solo in italiano era una debolezza reale rispetto all'art. 12 GDPR — ma tradurre un testo con effetti giuridici senza revisione legale non lo rende ufficiale, e dirlo è più onesto che lasciarlo intendere. **(2)** Nessun rilevamento automatico della lingua e nessun reindirizzamento: solo un selettore visibile. La lingua la sceglie chi legge. Contro il rischio di divergenza fra le due versioni — la lezione delle pagine parallele che il progetto ha già pagato una volta — una guardia di CI obbligatoria: un registro delle coppie con l'impronta SHA-256 del file italiano al momento della traduzione, e un workflow che fallisce se l'italiano cambia senza che l'inglese sia stato aggiornato. Nessuno step di build: le pagine EN sono file statici, come le italiane. Corollari di perimetro accettati per l'MVP: le etichette AcroForm del template PDF restano italiane anche nel certificato inglese (sono grafica del template: tradurle significherebbe un secondo template da mantenere e riverificare nelle coordinate), quindi il certificato EN è misto; il QR e l'URL di verifica stampati nel PDF restano invariati per lingua e puntano alla stessa pagina canonica, che si apre nella lingua chiesta da chi la visita, non da chi ha emesso.

Conseguenze: Il contratto API cambia in modo **puramente additivo**: senza il campo `lang` ogni risposta, ogni pagina e ogni PDF restano bit-identici a prima — criterio di accettazione verificato, non dichiarato. Nessun endpoint nuovo, nessun dato nuovo, nessun trattamento nuovo: l'informativa è stata **tradotta**, non estesa, e nessuna nuova categoria di interessati compare. Per questo P41 non introduce alcun rischio né alcun controllo nuovo nel registro: la lingua non cambia il modello di minaccia, e l'unica superficie sensibile — il messaggio firmato — è esplicitamente fuori dal suo raggio. Rilasciato in due tempi per una ragione strutturale, non per una svista: il sito statico non ha un gate di produzione separato (pubblica al push), il Worker sì (approvazione umana). Le pagine inglesi sono quindi andate live prima del motore bilingue; nessuna dipendenza fra le due parti, verificato prima di procedere. Il deploy del Worker (imgauth 1.33.0) è passato dalla pipeline con gate approvato dal gestore, come ogni rilascio di produzione. Il criterio per decidere se estendere lo scopo — altre pagine, altre lingue — è misurabile e già fissato: il traffico reale sulle pagine `/en/` nelle settimane successive. Se non si materializza, l'estensione non si fa.

ADR-P41

P40 — Eseguibili standalone della CLI sg-attest (Windows/macOS/Linux)accepted

Seguito naturale di P39 (CLI `sg-attest` + `attest-action`): il gestore ha chiesto se, oltre a `npx`, fosse possibile distribuire anche un pacchetto binario (node → exe) — per macchine o runner senza Node.js installato. La logica del percorso CLI (`src/cli.js`) non ha dipendenze esterne (solo moduli nativi Node e i moduli interni condivisi con il server MCP), il che la rende compilabile senza bundling complesso.

Decisione: Compilazione con **Bun `--compile`** (cross-compilazione da un solo runner CI, sei target: linux-x64/arm64, macos-x64/arm64, windows-x64/arm64 — il sesto è un bonus scoperto disponibile solo in fase di ricognizione, non previsto nel piano originale). Due blocker di compilazione risolti in `src/cli.js` senza toccare il percorso npm: lettura versione con fallback a un identificatore iniettato a compile-time (`bun build --define`), e guardia d'ingresso estesa a riconoscere `import.meta.main` (vero sotto Bun). Nuovo workflow `release-binaries.yml` in attest-mcp: ad ogni tag `v*`, compila tutti i target, genera `SHA256SUMS.txt` e pubblica una GitHub Release con i binari allegati — nessun segreto nuovo (solo `GITHUB_TOKEN` automatico). **Firma del codice esplicitamente rinviata per v1**: si accetta l'avviso "editore sconosciuto" di Windows SmartScreen/macOS Gatekeeper, con il checksum SHA-256 come garanzia d'integrità alternativa — la spesa reale (bassa, macOS verosimilmente gratis per l'ETS via la fee waiver Apple per no-profit) resta una decisione di spesa separata, non presa in questo giro. Auto-attestazione dei binari sul servizio stesso (idea coerente con l'ethos del progetto, come il whitepaper P38) valutata ma **rinviata esplicitamente**: richiede una credenziale reale con autorizzazione umana (Turnstile), non automatizzabile senza esporre una chiave in CI. Due bug reali scoperti nel primo rilascio taggato (`v0.4.0`), non previsti dal design: (1) il primo push di un tag non attivava alcun workflow (nessuna consegna del webhook per un commit già presente su `main`) — risolto cancellando e ripushando il tag; (2) `softprops/action-gh-release` crea una Release come **bozza non pubblica** per default se non si passa `draft: false` esplicito — scoperto perché l'endpoint pubblico della Release rispondeva 404 nonostante il workflow avesse riportato successo; corretto pubblicandola a mano e fissando il workflow per i tag successivi. Nuova pagina pubblica `attestazione.spaziogenesi.org/developer/cli/` (tabella di download per OS/architettura con link "latest" sempre aggiornati, istruzioni di verifica checksum, spiegazione dell'avviso editore-sconosciuto), linkata dal blocco CLI di `/developer/` e aggiunta come terza voce della colonna Sviluppatori nel footer di tutte le pagine del sito di attestazione.

Conseguenze: Un canale di distribuzione in più (binario), non una sostituzione: `npx`/ `npm` restano il percorso primario e quello usato da `attest-action` in CI. Nessuna nuova superficie privacy o dato trattato: il binario è lo stesso sorgente `src/cli.js` compilato, stessa impronta locale, stesso HMAC, stessa firma, stesso ancoraggio Bitcoin — nessun endpoint nuovo, nessuna modifica al device flow, zero segreti server-side aggiuntivi. Nessun nuovo rischio o controllo: la CLI compilata riusa interamente `CTL-agent-access` (bearer token P21), già presente nel registro.

ADR-P40

P39 — CLI sg-attest e GitHub Action: attestazione da terminale e da CIaccepted

Quarto lavoro adottato dall'analisi esterna sulla roadmap del 21/07 (`img-auth-hub/VALUTAZIONE-analisi-esterna-roadmap-2026-07-21.md` §2, punto 4): mancava un percorso pensato per sviluppatori da riga di comando e per pipeline di CI/CD — l'unico canale programmabile fino a quel momento era il server MCP `attest-mcp` (stdio, pensato per agenti AI, non per uno script di build). La logica di hash locale streaming e di autenticazione bearer era già interamente presente in `attest-mcp`: serviva solo un'interfaccia diversa sullo stesso motore.

Decisione: Nuovo terzo `bin` (`sg-attest`) nello stesso pacchetto npm `@spazio-genesi/attest-mcp` (nessun pacchetto separato: SDK e CLI condividono i moduli `api.js`/`hash.js`/`config.js` già esistenti), pubblicato come **0.3.1**. Comandi: `authorize` (device flow P21), `attest` (hash locale streaming + `--pdf` opzionale), `verify`, `status`. Nuovo repo pubblico separato `attest-action` (`SPAZIO-GENESI/attest-action`, MIT, tag `v1.1.0`+`v1`): action composite YAML sottile sopra la CLI, per attestare artefatti di build in pipeline CI — installa `@spazio-genesi/attest-mcp`, esegue `sg-attest attest` con la chiave passata via variabile d'ambiente (mai su argv, mai in log). Entrambi sono client puri del contratto esistente di imgauth: nessun endpoint nuovo, nessuna modifica al device flow, zero segreti sul lato server. Due bug reali corretti nel percorso, non previsti dal design originale: (1) la CLI 0.3.0 non partiva mai se installata da npm su Linux/macOS — la guardia d'ingresso confrontava `import.meta.url` con `process.argv[1]` senza risolvere i symlink (npm collega i bin come symlink su quelle piattaforme, uno shim `.cmd` su Windows nascondeva il problema in sviluppo); usciva silenziosamente senza stampare nulla. Scoperto dal self-test dell'action, non da un test locale su Windows. Fix in 0.3.1 (`realpathSync`) e nuova CI (`packaged-bins`) che installa il tarball impacchettato e verifica l'output dei comandi, non solo l'exit code. (2) `attest` da solo non archiviava nulla — il link `/c/` che l'action pubblicizzava come "Verifica" restava 404. Deciso con il gestore di emettere sempre il certificato PDF (`action.yml` chiama sempre `--pdf`, file temporaneo cancellato a meno che `download-pdf: true`); il self-test verifica ora che la pagina di verifica risponda 200, non solo che gli output non siano vuoti.

Conseguenze: Uno sviluppatore arriva da `/developer/` alla prima attestazione da terminale e al primo workflow CI in pochi minuti, senza leggere altro che il README. Nessuna nuova superficie privacy: la CLI e l'action calcolano l'hash in locale/sul runner esattamente come `attest-mcp` — confermato, non assunto, con lo stesso principio applicato in P26. Zero modifiche a imgauth/authweb: entrambi i repo sono client puri, come attest-mcp/attest-bot/attest-mcp-remote. Nessun nuovo rischio o controllo: la CLI e l'action riusano interamente `CTL-agent-access` (bearer token P21), già presente nel registro.

ADR-P39

P38 — Whitepaper tecnico pubblico: architettura, garanzie e limitiaccepted

Secondo lavoro adottato dall'analisi esterna sulla roadmap del 21/07 (`img-auth-hub/VALUTAZIONE-analisi-esterna-roadmap-2026-07-21.md` §2.2, "la voce di maggior valore"): mancava un documento tecnico pubblico che spiegasse con rigore che cosa il sistema di attestazione garantisce, con quali meccanismi, e — con lo stesso peso — che cosa non garantisce. Pubblico di riferimento: prestatori di servizi fiduciari, CDA e assemblea dell'ente, accademie e partner convenzionati, revisori esterni indipendenti, ricercatori e sviluppatori.

Decisione: Pubblicato `whitepaper-v1.0.pdf` (18 pagine, licenza CC BY 4.0, autore "Spazio Genesi ETS") su trust.spaziogenesi.org, con pagina di accompagnamento `whitepaper.html` (abstract EN+IT, indice, download, impronta SHA-256, storia delle revisioni). Sorgente Markdown pubblico e versionato in questo stesso repository (`docs/whitepaper/`), con mappa completa claim→fonte in `FONTI.md` per ogni affermazione tecnica. Contenuto: flusso end-to-end e sei canali di attestazione; le tre àncore temporali indipendenti (HMAC, marca RFC 3161/AATL, ancoraggio Bitcoin) e la loro analisi di indipendenza; un modello di minaccia esplicito (avversari, mitigazioni, residui accettati — incluso il caso reale dell'audit iniziale che trovò e portò a correggere una falsificabilità, citato come prova di metodo, non nascosto); i fondamenti crittografici di SHA-256 (le tre proprietà distinte e a chi servono, stato dell'arte verificato su fonti pubbliche datate, cosa accadrebbe in caso di indebolimento futuro); una sezione limiti dichiarati che è la più estesa del documento (identità self-signed, metadati auto-dichiarati, impronta dal client, chiave HMAC singola, risoluzione di 30 minuti dello storico di stato); il posizionamento onesto rispetto a eIDAS (riusando il testo già validato del Trust Center) e rispetto a C2PA/Content Credentials (complementare, non concorrente — nessuna adozione in questa versione, con le ragioni esplicitate). Processo: fasi 0-1 di ricognizione e assemblaggio da materiale esistente (Sonnet), fase 2 di stesura delle sezioni tecniche nuove e fact-check completo del documento (Opus) — cinque errori corretti, tutti dello stesso tipo (overclaim per generalizzazione: un'affermazione vera in un caso presentata come vera in tutti). La revisione del gestore (fase 3) si è svolta punto per punto in chat, non come approvazione in blocco: il gestore ha verificato di persona sul testo consolidato di EUR-Lex i tre riferimenti eIDAS citati (articoli 25, 41, 46), confermandone numeri e sostanza e affinando la formulazione sulla presunzione di legge riservata alle sole marche temporali qualificate. Su sua richiesta è stato aggiunto un paragrafo che dichiara la natura di ente del terzo settore senza scopo di lucro come ragione strutturale coerente con le scelte di trasparenza del documento — bilanciata esplicitamente ("nessuna forma giuridica garantisce l'onestà di per sé") per non scivolare in materiale promozionale, cosa che il documento esclude fin dalla prima riga. Chiusura circolare: il PDF pubblicato è stato a sua volta attestato sul servizio che descrive (stesso pattern del bundle mensile di evidenze, ADR-GTF-008) — pagina pubblica di verifica https://attestazione.spaziogenesi.org/c/898ec96815e6bee1f85f93651fb64b6d1ad289510f4ac2fd9fbaa92fe01de452.

Conseguenze: Un lettore tecnico scettico può verificare da sé ogni affermazione del documento: leggendo il codice pubblico e il registro citati, ricalcolando l'impronta del PDF, e confrontando la pagina di verifica pubblica. Il file `whitepaper-v1.0.pdf` è ora immutabile per design (la sua impronta è attestata): eventuali correzioni future generano una nuova versione numerata, mai una sostituzione silenziosa. Nessun cambio di architettura o di controllo di sicurezza: il whitepaper descrive il sistema esistente, non lo modifica — `ARCHITECTURE.md` non è stato toccato. Zero modifiche a imgauth/authweb: l'unica interazione con il servizio di produzione è stata l'attestazione stessa (dogfooding), eseguita dal gestore dal sito con Turnstile umano.

ADR-P38

P37 — security.txt (RFC 9116) e policy di responsible disclosureaccepted

Primo lavoro adottato dall'analisi esterna sulla roadmap del 21/07 (`img-auth-hub/VALUTAZIONE-analisi-esterna-roadmap-2026-07-21.md` §2.1). Verificato lo stesso giorno: `/.well-known/security.txt` rispondeva 404 sia su `attestazione.spaziogenesi.org` sia su `imgauth.spaziogenesi.org` — un ricercatore che avesse trovato una vulnerabilità non aveva un canale dichiarato per segnalarla, né una garanzia esplicita di non conseguenze per una ricerca in buona fede (vedi RSK-undisclosed-vulnerability).

Decisione: Pubblicato un `security.txt` conforme RFC 9116 su entrambi i domini (`imgauthweb/.well-known/security.txt`, servito da GitHub Pages; `imgauth/public/.well-known/security.txt`, servito come static asset del Worker, priorità sugli asset già attiva in wrangler.toml — zero righe toccate in worker.js) e una pagina pubblica di policy (`attestazione.spaziogenesi.org/sicurezza/`): ambito dichiarato (i due domini, i servizi workers.dev attest-bot/attest-mcp-remote, i repo GitHub pubblici dell'organizzazione), contatto `mailto:it@spaziogenesi.org`, impegno di safe harbor per la ricerca in buona fede, tempi di risposta (5 giorni lavorativi), limiti espliciti (niente DoS, social engineering, accesso fisico, dati reali di terzi), riconoscimento pubblico invece di bug bounty (ETS no profit, lessico onesto). Solo la collocazione RFC corrente (`.well-known/`); niente campo `Encryption` (nessuna chiave PGP pubblicata, nessuna creata per questo piano); `Expires` a un anno, con un nuovo processo (`PRC-security-txt-renewal`) che allarma via Telegram circa un mese prima della scadenza — il rinnovo non dipende dalla memoria di nessuno, stesso principio di `PRC-restore-drill`/`PRC-review-trimestrale`. `trust.spaziogenesi.org` e il sito principale restano fuori perimetro, estendibili in un giro successivo. Nella stessa valutazione esterna (§4-5), quattro proposte sono state esaminate e scartate, non ignorate: **certificazione ISO 27001** (costo d'audit sproporzionato per un ETS di questa scala; il GTF la usa già "a ispirazione" nella Compliance Map, coerente con REQ-27001-inspiration-01); **SOC 2** (standard USA per vendor B2B enterprise, zero rilevanza per il pubblico di riferimento italiano; l'Open Trust Score del GTF è la risposta equivalente e più verificabile, registro committato e riproducibile offline); **Sigstore** (categoria sbagliata — firma keyless di artefatti software, non di opere digitali; l'idea trasferibile del transparency log resta annotata nel whitepaper, non adottata); **pentest commerciale dedicato** (costo sproporzionato ora; coperture alternative già in campo: l'audit black-box P7, e la possibilità di chiedere a Radixia una componente di security testing nella review annuale già pianificata).

Conseguenze: Un ricercatore esterno che parta da uno qualunque dei due domini trova in ≤2 click canale di contatto, ambito, safe harbor e tempi di risposta — non più un vuoto. Il controllo nasce direttamente `active`: i due file e la pagina sono live e verificati (200 su entrambe le URL, redatti secondo il design doc P37), non solo dichiarati — stesso criterio già applicato a P24/P25/P27. Nessun nuovo trattamento di dati personali (il file è statico, la pagina non raccoglie nulla). Il rinnovo annuale di `Expires` è un processo ricorrente in più da tracciare, ma con allarme automatico (nessun affidamento alla memoria umana, stesso principio di RSK-cadence-drift).

ADR-P37

P36 — /status onesto in entrambe le direzioni: tacca proporzionale e uptime pesato sul tempoaccepted

Il bot di monitoraggio ha segnalato un timeout del firmatario >8s (cold-start Azure, rientrato da sé in pochi minuti). La pagina pubblica `/status/` mostrava però la barra dell'intera giornata colorata di rosso e l'uptime abbassato come se il servizio fosse stato giù 24 ore: il rollup giornaliero (`recordHistory`) conservava un solo stato per giorno — il peggiore osservato. Il difetto non era nel firmatario ma in come lo stato viene riassunto e raccontato al pubblico, su una pagina che il Trust Center rimanda come prova di trasparenza (`CTL-availability-monitoring`).

Decisione: Due correzioni distinte, entrambe rilasciate. (A, solo display, authweb 1.24.1) il dettaglio del giorno mostra un riquadro d'impatto ricavato dagli eventi reali di `/api/health-log` (episodi, orari, "il resto è stato regolare") invece di implicare un guasto esteso a tutta la giornata — non tocca l'uptime. (B, dato + rendering, imgauth 1.30.0 + authweb 1.25.0) il valore-giorno del rollup passa da una stringa-stato a una mappa di 48 fasce da 30 minuti (la cadenza reale del cron: risoluzione onesta massima, sotto la quale si inventerebbe un dato che non esiste), scritta con un max idempotente per fascia; la barra diventa verde con una tacca proporzionale alla durata reale del disservizio (soglia minima 2px perché un incidente vero non sparisca mai visivamente); l'uptime passa da binario per-giorno a pesato sul tempo. Il contratto `GET /api/status-history` cresce in modo additivo (campo `b` con il conteggio delle fasce, assente sui giorni già passati in vecchio formato) — nessun consumatore esistente si rompe. Deciso di non fare backfill dei giorni passati in v1: tecnicamente possibile da `health_log` ma a basso ritorno (l'ancoraggio, in particolare, non ha quasi mai avuto degradi reali dopo P15).

Conseguenze: Onestà bidirezionale: un disservizio breve non appare più come un blackout di 24h (il caso reale del 21/07 passa da 97,3% a 99,9% di uptime giornaliero sul componente firmatario), ma un disservizio prolungato continua a tingere la barra in proporzione — nessuna delle due direzioni è stata sacrificata per l'altra. Bug reale scoperto solo in produzione, non nei test: la migrazione soft del formato, alla prima scrittura del nuovo rollup, ha sostituito il valore del giorno *in corso* (ancora in vecchio formato, con l'incidente già registrato) con una mappa vuota valorizzata sulla sola fascia corrente — il blip delle 09:58 è sparito dalla barra mentre il riquadro di (A) continuava a riportarlo. Corretto ricostruendo a mano la mappa dagli eventi reali di `health_log` per quel solo giorno/componente. Lezione generale: qualunque migrazione soft di un formato di rollup va verificata anche sul periodo *in corso* al momento dello switch, non solo su quelli già passati. Nessun nuovo trattamento di dati personali, nessun nuovo rischio: stesso perimetro dei dati già raccolti (health_log, rollup R2), cambia solo la granularità con cui vengono riassunti e mostrati.

ADR-P36

Backup offsite dell'archivio R2 verso un secondo provider, verificato con un restore drill realeaccepted

L'archivio dei certificati (bucket R2 in giurisdizione EU) non aveva mai avuto una copia fuori da Cloudflare: un lockout dell'account o una cancellazione applicativa avrebbe potuto compromettere lo storico probatorio. Il piano era già scritto (img-auth-hub, RESILIENZA-E-BACKUP.md Parte 2) ma mai eseguito — MET-conservation era n/d anche per questo (un restore drill senza un backup reale non ha da cosa ripristinare).

Decisione: Copia notturna (mai sincronizzazione: solo aggiunte, nessuna cancellazione a monte propagata) dell'archivio verso un secondo provider indipendente, in una regione coerente con la giurisdizione EU del bucket primario. Il workflow che esegue la copia vive in un repository privato: i suoi log elencherebbero, se pubblici, l'insieme completo delle impronte di tutte le opere attestate — oggi recuperabili solo da chi conosce il singolo hash, un modello di fiducia che un elenco pubblico completo cambierebbe. Verificato con un primo run reale (conteggio oggetti e byte identici tra sorgente e destinazione, zero differenze) e un secondo run immediatamente successivo (quasi nessun oggetto trasferito, incrementalità confermata), poi con un vero restore drill (vedi CTL-r2-offsite-backup): 3 opere campione ripristinate dal backup e verificate con gli stessi strumenti pubblici — firma HMAC, ancoraggio Bitcoin, badge d'archivio — tutte e tre con esito positivo.

Conseguenze: MET-conservation passa da n/d a un valore reale (componente drill: 100/100, esito riuscito il 2026-07-19). Il gap dichiarato in ARCHITECTURE.md §6.2 punto 3 ("backup provato, non solo pianificato") è chiuso. La prova non è ripetibile automaticamente dal collettore settimanale del GTF (repo privato, non leggibile senza autenticazione): l'evidenza del ciclo di backup resta manuale, con verifica periodica del log del workflow (vedi EVD-r2-offsite-backup). Il prossimo restore drill è dovuto entro la cadenza dichiarata in PRC-restore-drill.

ADR-GTF-013

MET-privacy: attivata con scanner dedicato su schema SQL + prefissi R2 di imgauth (v1)accepted

MET-privacy era n/d: nessuno scanner del codice rilevava i flussi di dati personali non ancora registrati come DAT, quindi non era verificabile che i 4 DAT esistenti coprissero tutto ciò che il codice tratta davvero. Inventario manuale (P32/B0, letto da privacy.html §3.1-3.8 e dagli schemi pubblici di imgauth) ha individuato 8 flussi non ancora documentati come DAT: email self-service sviluppatori, log convenzioni per dominio email, candidature alla vetrina integrazioni, abbonamento Professionale, due profilazioni facoltative (Professionale e Sviluppatore), utenti del bot Telegram (repo attest-bot) e — scoperto solo durante la progettazione dello scanner, non nell'inventario iniziale — l'email del beneficiario di un codice sconto riservato (pro_discounts.restricted_email, esistente da P27 ma mai citata in privacy.html §3.8).

Decisione: Scritti 8 nuovi record DAT (i 7 attesi più DAT-pro-discount-email). Nuovo generatore `scan-privacy.mjs`, eseguito nel giro settimanale del collettore: scarica `worker.js` e `schema/*.sql` di imgauth (repo pubblico) via raw.githubusercontent.com all'ultimo tag di release (non `main`, per uno scan riferito a una versione precisa e riproducibile), rileva (a) tabelle SQL con almeno una colonna dal nome sensibile (`email|owner|member|name|user|phone|address|customer`, alto recall deliberato) e (b) scritture R2 sotto prefissi noti (`pdf/`, `ots/`, `meta/cert/`, `integrations/`, più i prefissi non personali `status/` e i contatori `meta/*-count`). Il terzo rilevatore previsto in fase di progettazione (host esterni nelle fetch di worker.js) è stato valutato e scartato per il v1: i ~15 host individuati a mano (Google/Microsoft/ LinkedIn OAuth, calendar OpenTimestamps, Turnstile, Telegram per le sole notifiche all'amministratore) sono tutti provider terzi già documentati in privacy.html §5 "Destinatari" o già coperti da un DAT esistente (Turnstile) — avrebbe prodotto solo falsi positivi da dichiarare, senza aumentare il recall reale sui flussi B0. Il mapping flusso→DAT vive in `generators/lib/privacy-map.json`: ogni flusso rilevato è marcato "coperto" (uno o più DAT), "falso positivo dichiarato" (con motivo — es. `conventions.name` è il nome dell'ente, non di una persona; `health_log.check_name` è il nome di un controllo di salute) o "non coperto". Formula: `privacyRatio = mappati / (rilevati − falsi positivi dichiarati)`. Snapshot assente o fetch fallito → indicatore `null`, mai un valore inventato.

Conseguenze: MET-privacy passa da n/d a un valore reale (atteso 100/100 con la copertura raggiunta: 8 DAT esistenti + gli 8 nuovi coprono tutti i flussi rilevati dallo scanner v1, nessuno lasciato "non coperto"). Score complessivo: 8/10 indicatori disponibili. Limite dichiarato nella nota pubblica dell'indicatore: lo scanner v1 copre solo il repo imgauth — i flussi che vivono in repo client (attest-bot, il bot Telegram) restano fuori, con DAT-telegram-bot-users scritto ugualmente nel registro ma non verificato automaticamente; possibile estensione v2. Il rilevamento host esterni resta una possibilità futura se emergerà un'integrazione esterna che condivide dati personali non ancora documentata.

ADR-GTF-012

MET-governance: formula emendata per maintainer singolo (validazione CI + gate umano P24)accepted

MET-governance era n/d: la formula originale misurava la quota di merge nel registro avvenuti via pull request con revisione tra pari, dato non ancora raccolto offline. Il collettore ora legge questo dato: il repo gtf conta 0 pull request su ~60 commit, tutti push diretti su main. Con la formula originale il valore sarebbe 0 e lo score complessivo scenderebbe da 90 a circa 77 — non perché la governance sia debole, ma perché il progetto ha un solo maintainer e la revisione tra pari via PR non si applica.

Decisione: La formula è emendata alla media di due controlli reali e verificabili via API GitHub pubbliche: (1) quota di run `success` di `validate.yml` su gtf/main (validazione automatica del registro ad ogni commit, 30/30 negli ultimi run raccolti) e (2) quota di run di `ci.yml` su imgauth/main in cui il job `deploy-production` è concluso con un record di approvazione dell'environment (gate umano P24, 15/17 — i due run mancanti hanno conclusione `cancelled` per un push successivo, non un'approvazione negata). Valore risultante: 94. Il termine "quota PR" resta escluso dal calcolo v1 e non applicabile a un maintainer singolo; il conteggio PR è comunque raccolto nello snapshot settimanale (`governance-prs.json`) per trasparenza sul perché è escluso, e la formula si riapre se il progetto guadagnerà più contributor. Decisione presa dal gestore mostrando entrambi i numeri (0 vs 94; score 77 vs 90).

Conseguenze: MET-governance passa da n/d a 94/100 (nota pubblica con i conteggi espliciti). Score complessivo: 7/10 indicatori disponibili, 90/100 (invariato nell'arrotondamento). `registry/metrics/MET-governance.yaml` aggiornato con la nuova formula; `generators/score.mjs` implementa `governanceRatio(week)` sullo stesso modello di `releaseTagRatio`; i dati arrivano dal collettore settimanale (`governance-validate-runs.json`, `governance-prod-gate.json`, `governance-prs.json`), mai da chiamate a rete in `score.mjs` — principio offline dello score invariato.

ADR-GTF-011

P29 — Pagine pubbliche su authweb, il Worker torna a fare solo API e flussiaccepted

Quattro pagine destinate agli utenti (`/docs`, `/integrazioni`, `/developer/keys`, `/profilo`) erano servite dal Worker imgauth invece che da authweb, l'interfaccia pubblica del servizio — un'incoerenza di dominio (due host per un solo servizio agli occhi di chi naviga e dei motori di ricerca) accumulata fase dopo fase (P22, P26, P27, P28) senza mai essere corretta. In più, la pagina `/developer/keys` mostrava la chiave `sg_k_…` appena emessa in una risposta HTML del Worker — non un rischio concreto (nessun log la registra), ma un'occasione di irrigidire ulteriormente la garanzia "la chiave non tocca mai un server" già usata per il voucher `#sgv=` (P25 §2.7).

Decisione: Migrare tutte e quattro le pagine su authweb (GitHub Pages, HTML puro), lasciando imgauth a fare solo API ed endpoint di flusso (OAuth, Stripe, device flow). Ogni fase rilasciata solo dopo aver confermato **live la pagina authweb prima di attivare il 301** sul vecchio path (mai un redirect verso una pagina non ancora pubblicata) — per la vetrina Integrazioni, l'unica con una dipendenza dati (serve `GET /api/integrations` per generarsi), questo ha richiesto due deploy separati del Worker. La chiave API self-service non è più renderizzata in nessuna pagina HTML: la callback OAuth (`purpose='key'`) fa sempre un 302 con l'esito SOLO nel fragment dell'URL (`#sgk=…`, `#sgstate=…`, `#sgerr=…`) — un miglioramento di privacy non richiesto esplicitamente dal problema originale, aggiunto perché lo stesso pattern era già collaudato per il voucher. Aggiunto anche il 301 di `imgauth.spaziogenesi.org/c/*` verso la canonica su attestazione (decisione esplicita del gestore): elimina contenuto duplicato, QR e certificati stampano già quell'URL.

Conseguenze: Nessun nuovo trattamento di dati personali, nessuna modifica al contratto `/api/*` (cresce di un solo endpoint pubblico, `GET /api/integrations`, sorgente machine-readable per la CI di authweb) — la migrazione è di superficie (dove vive l'HTML), non di sostanza. Due bug reali scoperti nel collaudo dal vivo, nessuno dei due nei test locali: (1) il `GITHUB_TOKEN` di default di GitHub Actions è read-only sull'org, i due nuovi workflow di sync (openapi.json, vetrina Integrazioni) fallivano silenziosamente sul `git push` finché non è stato aggiunto `permissions: contents: write` esplicito — scoperto lanciando un workflow a mano, non da un test automatizzato; (2) testo invisibile sui bottoni provider di `/developer/keys/`: un selettore CSS generico (`section.blocco a`) vinceva per specificità su quello dedicato (`.provider-link`), stesso colore oro di testo e sfondo — stessa classe di bug già vista in P25 (verificare il contrasto reso in browser, non solo la presenza nel DOM). Trade-off accettato: le pagine statiche (`/developer/keys/`, `/profilo/`) non riflettono più la configurazione runtime dei provider OAuth su imgauth (bottoni sempre presenti, prima sparivano se client id/secret mancanti) — nessun impatto di sicurezza, solo di UX in un caso raro (provider temporaneamente non configurato). Collaudo reale del gestore sul round-trip Stripe completo su `/profilo/` (login, portale, riattivazione di un abbonamento sospeso, ritorno con stato aggiornato) prima di considerare la FASE 4 chiusa.

ADR-P29

P28 — Vetrina pubblica Integrazioni + convenzioni partner software houseaccepted

Una software house che integra l'attestazione aveva un percorso incompleto: una chiave Sviluppatore (P22) basta per costruire e testare, ma la sua quota bassa non è pensata per la produzione, e non esisteva un modo per l'ente di riconoscere pubblicamente le applicazioni che integrano il servizio. Due domande di design: (1) come far arrivare in produzione un'app che attesta per conto di molti utenti finali senza costruire un nuovo tipo di credenziale; (2) come dare visibilità alle integrazioni senza che contenuto di terzi (nome, URL, logo) finisca online sotto il nome Spazio Genesi senza controllo.

Decisione: Due modelli di produzione, entrambi già coperti dal meccanismo esistente: **modello A** (l'app è un client puro, l'utente finale porta il proprio abbonamento Professionale o la propria convenzione — zero codice nuovo) e **modello B** (convenzione con la software house: riuso quasi integrale di P25 — una convenzione con `domains` **placeholder** che non può mai combaciare con un'email reale, es. `partner:nomesoftware`, e una chiave `sg_k_…` con `convention_id` che scala dal pool mensile dell'ente). Per la visibilità: vetrina pubblica `/integrazioni` con **pre-moderazione** — a differenza di ogni altro flusso self-service del sistema (tutti post-moderazione: la conseguenza è immediata, la revisione arriva dopo), qui niente va online prima di un'approvazione esplicita del gestore, perché il contenuto è pubblico e accostato al nome dell'ente. Candidatura da `/profilo` (voucher, eleggibile con chiave attiva o abbonamento Professionale), logo validato sui **magic bytes** (mai il Content-Type dichiarato, mai SVG), ogni modifica successiva — anche di una candidatura già approvata — riporta lo stato a `pending`.

Conseguenze: Il modello B ha richiesto un'estensione non prevista esplicitamente dal design doc, scoperta solo in fase di implementazione: `owner_email` reso **obbligatorio** quando l'admin emette una chiave con `convention_id`, perché la contabilità del pool (`accountConventionUsage`) scrive `member_email` in `convention_attestations` con un vincolo NOT NULL — senza, l'attestazione avrebbe comunque funzionato (fascia `convenzione` restituita in sincrono) ma l'INSERT di log sarebbe fallito in modo silenzioso (fire-and-forget via `ctx.waitUntil`, errore inghiottito), rompendo silenziosamente la contabilità del pool per quel partner. Collaudo end-to-end in `wrangler dev` con dati reali (convenzione fittizia, chiave partner, ciclo completo pending→approved→pending-dopo- modifica, logo SVG/mime-spoofed/oversize rifiutati) prima del rilascio; smoke test su staging e produzione dopo ogni deploy gated (imgauth 1.24.0, poi patch 1.24.1 per loghi più grandi, richiesta dal gestore dopo il collaudo reale in produzione). Nessun nuovo trattamento di dati personali sensibile: l'unico dato personale è l'email del titolare della candidatura, già trattata dallo stesso meccanismo OAuth one-shot di P22/P25/P27 (mai persistita se non come colonna esistente su `agent_credentials`/`integrations.owner_email`); ciò che diventa pubblico (nome app, URL, descrizione, logo) è scelto volontariamente dall'interessato e revocabile in ogni momento.

ADR-P28

P27 — Fascia Professionale: abbonamento annuale, pagina profilo, pagamenti interamente su Stripeaccepted

`/condizioni/` prometteva da tempo una fascia Professionale (200 attestazioni/mese, garanzia di recupero certificato ≥5 anni, "senza account e senza password") mai attivata. Tre domande di design aperte: (1) come gestire l'account senza costruire un vero sistema di account — opzione C scelta: pagina profilo in sola lettura, tutte le azioni di pagamento (fatture, metodo, cessazione) delegate allo Stripe Customer Portal hosted, mai dati di carta sul Worker; (2) come tenere il listino senza hardcodare un prezzo non ancora deciso dal CDA — listino in D1 gestito dal pannello admin, con validità a finestre e codici sconto, applicato al Checkout come `price_data` inline (mai un oggetto Price/Coupon su Stripe); (3) se varare con il fiscale ETS e il prezzo definitivo ancora in discussione — deciso di sì: nessun consumo reale esiste ancora, quindi la produzione stessa è il collaudo, con una riga di listino simbolica (1€) sostituibile senza codice quando il prezzo sarà approvato.

Decisione: Fascia Professionale IN PRODUZIONE con: pagina `imgauth.spaziogenesi.org /profilo` (stati anonimo → onboarding → attivo/past_due → cessato, costruita sullo stesso voucher stateless di P25 §2.7 con un nuovo `purpose='profile'`); catena di precedenza fascia **convenzione → professionale → sviluppatore → base** (mai un blocco, degrado con motivo esplicito, stessa logica già in produzione per le convenzioni); canale di produzione (web/api/mcp/telegram) tracciato nel sidecar e nei log per la prima volta; profilazione facoltativa doppia — Professionale (segmento/regione) e, decisione presa a valle del collaudo, Sviluppatore (applicazione/OS/ambiente di sviluppo) — entrambe su consenso esplicito e cancellabili dall'interessato. PayPal esplicitamente rinviato: un solo processore di pagamento al lancio.

Conseguenze: **Collaudato con un abbonamento reale (1€), non solo in test**: checkout → attestazione dal sito riconosciuta in fascia professionale → archivio con canale corretto → cessazione dal Customer Portal → rimborso. Il collaudo a soldi veri ha scoperto tre difetti reali mai emersi nei test sintetici, tutti nella versione API Stripe dell'account (`2026-02-25.clover`, `billing_mode: flexible`): `current_period_end` è migrato dal livello subscription al livello subscription-item; `cancel_at_period_end` risulta sempre `false` — la cancellazione a fine periodo (comportamento di default del Customer Portal per abbonamenti annuali pagati in anticipo, confermato come quello desiderato) si legge invece dal campo `cancel_at`; il Customer Portal invia più eventi `customer.subscription.updated` reali e distinti per un solo click utente, richiedendo un filtro cosmetico (non un problema di correttezza: l'idempotenza su `event.id` già impediva un vero doppio conteggio). Nessuno di questi tre bug era rilevabile dalla sola lettura della documentazione Stripe generica — lezione generale: le versioni API di un fornitore esterno vanno verificate con una chiamata reale, non assunte. `CTL-pro-subscription` nasce direttamente `active` (non `draft`): il criterio già applicato a `CTL-cicd-pipeline`, `CTL-site-voucher-auth` e `CTL-convention-accounting` — un controllo collaudato end-to-end con dati reali, non solo dichiarato.

ADR-P27

Radixia srl confermata come revisore esterno indipendente dei controlli periodici umaniaccepted

La review esterna annuale (PRC-review-esterna-annuale) era l'unica cadenza ricorrente del GTF senza un titolare nominato. ARCHITECTURE.md §9.1 suggeriva come opzione un docente/ricercatore dell'Accademia — a costo zero, ma con indipendenza debole: la stessa istituzione da cui nasce Spazio Genesi ETS. La ricerca di una terza parte realmente indipendente era aperta dal 2026-07-09.

Decisione: Radixia srl (Milano, https://www.radixia.ai — società attiva in Enterprise AI, Open Cloud e ricerca applicata; membro dell'Eclipse Foundation) ha confermato il 2026-07-14 la propria disponibilità come terza parte per i controlli periodici umani previsti dal GTF, a partire dalla review esterna annuale del registro e dei controlli. Nessun legame societario o istituzionale con Spazio Genesi ETS né con l'Accademia: il requisito di indipendenza è soddisfatto. `PRC-review-esterna-annuale.owner` aggiornato; perimetro, materiali e passi operativi della prima review sono definiti in `docs/piano-review-esterna-2026.md` (nel repo pubblico gtf), così la review può partire senza ulteriore lavoro preparatorio.

Conseguenze: Tutte le cadenze ricorrenti del GTF hanno ora un owner nominato. La prima review produrrà un verbale pubblico, registrato come evidenza (EVD) con eventuali azioni correttive (ACT) e con l'aggiornamento di `PRC-review-esterna-annuale.last_run`. Il costo lato ETS resta dentro il budget di §9.3 (~2 h/anno). L'indicatore Governance dell'Open Trust Score beneficerà della prima evidenza di audit indipendente quando il verbale sarà committato — non prima: il punteggio riflette evidenze, non disponibilità dichiarate.

ADR-GTF-010

Header di sicurezza HTTP iniettati all'edge Cloudflare per i siti staticiaccepted

Una scansione securityheaders.com su spaziogenesi.org segnalava tre header mancanti: Strict-Transport-Security, Content-Security-Policy e Permissions-Policy. I siti pubblici (sito principale, interfaccia di attestazione) sono GitHub Pages, che non permette header di risposta custom: l'unico punto di iniezione è l'edge Cloudflare che li proxa.

Decisione: Configurati a livello di zona Cloudflare (dashboard, non versionabile nei repo): HSTS via impostazione dedicata (max-age un anno, includeSubDomains, senza preload) e una Transform Rule che aggiunge a tutte le risposte una Content-Security-Policy baseline e una Permissions-Policy restrittiva. La CSP mantiene deliberatamente 'unsafe-inline' negli script: sito principale e interfaccia di attestazione hanno JavaScript inline (l'interfaccia è un singolo file HTML per design); rimuoverlo richiederebbe hash CSP fragili o un refactoring a script esterni, rinviato a una decisione dedicata. Il valore della baseline sta nelle direttive object-src 'none', frame-ancestors 'self', base-uri 'self' e upgrade-insecure-requests.

Conseguenze: I tre header rispondono su tutti gli host proxati della zona, incluse le risposte del Worker imgauth. Verificato senza regressioni: nessun evento securitypolicyviolation in browser reale, Turnstile e flusso di attestazione integri, CORS di imgauth intatto. Il Trust Center (trust.spaziogenesi.org) è DNS-only e non riceve gli header finché il suo record non viene proxato. Nessun nuovo rischio introdotto: la modifica è puramente additiva sulle risposte. Aggiornamento 2026-07-14 (stesso giorno): i due residui inizialmente accettati sono stati risolti. (1) Access-Control-Allow-Origin: * di GitHub Pages rimosso con una Transform Rule mirata ai soli host Pages (spaziogenesi.org, www., attestazione.) — mai zone-wide, il CORS di imgauth resta intatto (verificato). (2) 'unsafe-inline' rimosso da script-src esternalizzando ogni script eseguibile del sito principale e di authweb in file JS same-origin — refactoring più ampio del previsto (Astro inlinea di default anche gli script senza is:inline), nel corso del quale è stato scoperto e corretto un bug reale non legato alla CSP: alcuni script che Astro compilava come moduli (scope isolato per file) collidevano sugli identificatori una volta esternalizzati come script classici, risolto ripristinando type="module" dove serviva. Verificato con Playwright sia su una build locale completa (56 pagine, CSP rigorosa simulata) sia dal vivo in produzione (11 pagine reali, CSP genuina già attiva): zero violazioni, zero errori JS in entrambi i giri. Nessuna decisione aperta residua su questo controllo.

ADR-edge-security-headers

P25 (C) — "Attesta con la tua email" dal sito: voucher stateless invece di una sessioneaccepted

Le convenzioni (P25 B, ADR-P25-conventions) erano fruibili solo tramite chiave API o client MCP, ma il loro pubblico reale (studenti di accademie d'arte, professionisti non tecnici) non usa strumenti da sviluppatore: deve poter attestare direttamente dal sito, come fa già chiunque in forma anonima. Serviva un modo per riconoscere l'email istituzionale di chi usa il sito SENZA introdurre cookie, sessioni server o un secondo sistema di login che avrebbe complicato il modello di minimizzazione dati già in vigore (il percorso anonimo resta a zero dati personali).

Decisione: Stesso OAuth one-shot già usato per le chiavi API (Google/Microsoft/ LinkedIn, §3.5), ma l'esito non è una riga persistita in D1: è un **voucher stateless firmato** — `base64url(JSON{email,conv,exp}) + '.' + HMAC-SHA256(HMAC_SECRET, 'VOUCHER:'+payload)`, TTL 8 ore — consegnato nel **fragment dell'URL** (`#sgv=`, per costruzione mai inviato a un server) e tenuto in `sessionStorage` dal browser (niente cookie, niente riga di sessione server-side, sparisce alla chiusura della scheda o al click su "Esci"). Il campo `conv` nel payload è solo un HINT scritto al momento dell'emissione: la fonte di verità sullo stato della convenzione resta sempre una query fresca (`matchConvention`) a ogni singolo uso del voucher su `/api/hash` — così una convenzione disattivata dopo l'emissione del voucher smette di dare vantaggi senza bisogno di alcun meccanismo di revoca (il voucher, semplicemente, scade da solo e non è mai "attivo" oltre quanto la convenzione lo sia davvero). La contabilità pool/tetto individuale riusa lo stesso meccanismo delle chiavi API (funzione condivisa `accountConventionUsage`, rifattorizzata da questo lavoro), quindi il comportamento — mai un blocco, solo degrado a fascia Base con motivo esplicito — è identico su entrambi i canali.

Conseguenze: Nessun nuovo tipo di credenziale bearer, nessuna modifica al percorso anonimo del sito, nessuna riga D1 per il solo fatto di accedere (a differenza delle chiavi API, che restano in agent_credentials anche se inutilizzate). Testato in locale (wrangler dev, D1 reale): voucher valido con/senza convenzione, tetto individuale, pool esaurito, convenzione disattivata dopo l'emissione (correttamente senza vantaggio), voucher scaduto/manomesso (403), CORS preflight con l'header X-SG-Voucher; frontend verificato in browser con Playwright. Rilasciato lo stesso giorno (imgauth 1.21.2, tag v1.21.2; authweb 1.17.0, tag v1.17.0), poi verificato con un login OAuth reale in produzione: attestazione e certificato PDF generati dal sito e riconosciuti in convenzione — prima prova end-to-end del canale con un vero provider OAuth. `CTL-site-voucher-auth` promosso da `draft` ad `active`. **Incidente di concorrenza, risolto in pochi minuti**: il codice è arrivato in produzione tramite il commit di una sessione di lavoro concorrente sullo stesso checkout — non un rilascio deliberato attraverso il gate P24 — *prima* che la migrazione schema/voucher.sql (colonna dev_oauth_state.purpose) fosse applicata alla D1 remota. Poiché handleDevOAuthStart referenzia la colonna su ogni chiamata, l'intero /developer/keys (P22, in uso da utenti reali) ha risposto 500 fino all'applicazione della migrazione. Nessuna perdita di dati; nessuna nuova voce di rischio: la classe è già coperta da RSK-unobserved-production-deploy (P24) — l'incidente è avvenuto proprio perché il rilascio non è passato dal gate. Nel primo uso reale sono emersi e stati corretti due difetti non rilevabili dai test locali: i bottoni dei provider resi invisibili da una classe CSS riusata fuori dal contesto per cui era stata scritta (i test verificavano la presenza nel DOM, non la posizione visiva) e il pannello /admin che non ricaricava i dati alla riapertura di una scheda (emerge solo con due sessioni in parallelo). `/condizioni/`: rimosso il tag "in arrivo" sul canale sito per la sola fascia Convenzione (resta per Professionale, non ancora costruita).

ADR-P25-site-voucher

P25 (B) — Convenzioni per dominio email: pool mensile dell'ente, degrado mai blocco, log minimizzatoaccepted

Il primo design P25 (2026-07-11) prevedeva una quota INDIVIDUALE per membro di convenzione (`student_quota` per-chiave). In revisione il modello è stato corretto: un ente ragiona per **monte collettivo mensile** dell'istituzione, non per persona — la contabilità giusta è un pool mensile condiviso tra tutti i membri dell'ente, con un tetto individuale come secondo argine anti-drenaggio, non come unità di misura primaria. Restava da decidere il comportamento a pool o tetto esauriti — bloccare l'utente (con il rischio di fermare uno studente a metà di una sessione d'esame) oppure degradare con avviso: scelto il degrado — e quanto loggare per rendere possibile un report all'ente senza trasformare in tracciamento esteso un sistema oggi a zero dati personali per chi attesta dal percorso anonimo.

Decisione: Contabilità a **pool mensile dell'ente** (`conventions.monthly_quota`, contato con `COUNT(*)` su `convention_attestations` filtrato per `(convention_id, ym)`, `ym` in Europe/Rome) più un **tetto individuale** anti-drenaggio (`member_cap`, default 50/mese — la stessa soglia del self-service ordinario, così la convenzione non è mai percepita come un downgrade del gratuito). Esaurito l'uno o l'altro, l'emissione **degrada silenziosamente alla fascia Base** (certificato comunque emesso, con avviso esplicito nella risposta — `fascia_motivo: 'pool_esaurito'` o `'tetto_individuale'`): mai un 429, mai un blocco. Il log `convention_attestations` (convenzione→membro→impronta) si scrive SOLO per le emissioni effettivamente taggate in convenzione — non per il self-service individuale né per il percorso anonimo — perché è l'unica associazione identità→opere dell'intero sistema e la sua esistenza va giustificata caso per caso (garanzia di persistenza contrattuale, contabilità del pool, report all'ente), non generalizzata. Nello stesso giro è stata **varata `/condizioni/`** (pagina pubblica delle fasce, `attestazione.spaziogenesi.org/condizioni/`): pubblicata `noindex` l'11/7 in attesa di una delibera assembleare, linkata e indicizzata il 12/7 senza attendere quella delibera — scelta esplicita di rendere pubbliche le regole prima ancora che il meccanismo di convenzione sottostante fosse deployato, sui contenuti già scritti (nessuna modifica ai numeri).

Conseguenze: Codice completo e verificato in locale (`wrangler dev`, convenzione di test: match dominio, scadenza/disattivazione, pool esaurito, tetto individuale, log solo per emissioni tassate, sidecar `meta/cert/`), poi **rilasciato in produzione lo stesso giorno** (imgauth 1.21.0, tag `v1.21.0`, via pipeline P24 — schema `conventions.sql` applicato a D1 staging e produzione PRIMA della promozione, per evitare che `authenticateAgent` rompesse su una colonna mancante). **Verificato con un login OAuth reale in produzione**: prima convenzione vera creata (dominio `spaziogenesi.org`), login Google reale, chiave emessa con `convention_id` corretto, attestazione con `fascia:'convenzione'`, riga di log corretta in `convention_attestations` — tutto confermato via query diretta su D1 di produzione, non solo dalla risposta dell'API. `CTL-convention-accounting` promosso da `draft` ad `active` — il registro descrive solo ciò che è verificato in produzione. Emerso e risolto lungo il percorso un problema di configurazione preesistente, non collegato al codice P25: l'URI di redirect OAuth per Google era registrato con un carattere iniziale mancante (`redirect_uri_mismatch`). **Approssimazione nota nel sidecar**: il campo `tier` di `meta/cert/<hash>.json` riflette l'appartenenza STATICA della credenziale bearer alla convenzione (lookup read-only, non tocca quota), non l'esito dinamico pool/tetto calcolato al momento esatto di `/api/hash` — un certificato degradato a Base può comparire `tier:'convenzione'` nel sidecar. Accettato perché `tier` non è ancora consumato da nessuna logica di retention differenziata (P26+); da rivalutare quando lo sarà. **Varo anticipato di `/condizioni/`**: le regole pubblicate potrebbero cambiare se/quando l'assemblea delibera — la pagina non promette immutabilità dei numeri, solo l'onestà di quanto già vero oggi (minimi garantiti, non retroattività).

ADR-P25-conventions

P26 — Server MCP remoto, zero installazione: attestazione per impronta, mai per uploadaccepted

Il pacchetto MCP `attest-mcp` (P21) richiede un'installazione locale (Node, `npx`) e resta il canale con accesso pieno al filesystem. La domanda di partenza era se estendere l'accesso agli agenti AI con un server MCP raggiungibile per URL, senza installazione — e se farlo ripristinando un canale di upload come nel bot Telegram (P23). La verifica sulla spec MCP corrente (revisione 2025-11-25) ha chiuso la domanda: gli argomenti dei tool sono JSON che transitano nel contesto del modello, non esiste un canale upload client→server affidabile. Passare un file in base64 sarebbe impraticabile oltre pochi KB e non affidabile (un modello che ricopia base64 può corromperlo — un certificato valido su un'impronta sbagliata è il fallimento peggiore possibile per questo servizio).

Decisione: **`attest_hash`, non `attest_file`**: il tool riceve solo l'impronta SHA-256, calcolata dall'agente eseguendo codice in locale (`sha256sum`/`certutil`/`shasum`). Nuovo repo pubblico `attest-mcp-remote` (Cloudflare Worker con `McpAgent`, agents SDK, Durable Object SQLite per lo stato di sessione), client puro dell'API pubblica di imgauth — zero modifiche al motore, zero segreti propri nel Worker. Due strade di autenticazione, entrambe riuso di P21 senza modifiche: header `Authorization: Bearer` pass-through per client con header personalizzati (Claude Code), device flow in sessione per client che non li supportano (claude.ai, che non offre header custom sui connettori remoti — verificato in FASE 0). Il token di sessione vive solo nello stato del Durable Object di quella connessione, mai loggato né restituito dopo il claim. Tool pubblici (service_status, check_anchor, verify_attestation, lookup_certificate) e tool con credenziale (authorize, complete_authorization, attest_hash, create_certificate_pdf — risposta sempre link, mai base64 del PDF nel contesto).

Conseguenze: Il vincolo del protocollo diventa un vantaggio: il file non transita nemmeno dal Worker remoto, un livello di privacy pari al sito e superiore al bot Telegram (che deve scaricare i byte per calcolarne l'hash). Nessuna nuova superficie di dati personali: nessun id utente, nessun contatore persistito — l'unico stato è il token di sessione effimero nel Durable Object, mai scritto su disco persistente al di fuori di quel contesto. **Nessun nuovo rischio privacy**: `privacy.html` resta invariata, verificato esplicitamente con il gestore, non solo assunto. Limite onesto accettato: la sessione MCP è legata alla connessione — una riconnessione azzera il token e richiede una nuova autorizzazione; su claude.ai questo è anche l'unico modo di attestare, quindi una chiave API `sg_k_…` (quota Sviluppatore o pool Convenzione) non è utilizzabile lì — reso esplicito nella developer page (matrice credenziale×client) PRIMA che l'utente scelga il client. Nel collaudo reale su claude.ai (giro completo: hash calcolato dall'agente → autorizzazione con Turnstile umano → attestazione → certificato PDF) è emerso e stato corretto un bug reale di produzione latente da P21 (imgauth 1.21.3): il bottone "Autorizza" della pagina `/agent/authorize` faceva una fetch relativa a `/api/agent/approve`, mai instradata al Worker sul dominio pubblico (route solo `/agent/*`) — l'approvazione dalla pagina non era mai stata esercitata in produzione prima d'ora (i test di P21 usavano una chiave interna, saltando quel passaggio). Scoperto solo testando dal vivo, non dalla sola lettura del codice — stesso pattern già visto in P23.

ADR-P26

P25 — LinkedIn come quarto provider OAuth self-serviceaccepted

Il primo design P25 (2026-07-11) aveva valutato e rinviato LinkedIn come provider OAuth per `/developer/keys`: pubblico marginale per una pagina di API key, perché LinkedIn è percepito come identità professionale, non da developer, a differenza di GitHub (primario) e GitLab (opzionale). La valutazione è stata rovesciata alla definizione delle fasce di utilizzo pubblicate su `/condizioni/` (Base / Sviluppatore / Professionale "in arrivo" / Convenzione): quella stessa caratteristica è un vantaggio — la futura fascia Professionale (fotografi, artisti, professionisti che si presentano con un profilo pubblico, non un account Google generico) non aveva ancora un'identità OAuth adatta, e LinkedIn è l'unico provider del lotto P25 che comunica esplicitamente "professionista".

Decisione: Aggiunto LinkedIn a `DEV_PROVIDERS` in `worker.js` (imgauth 1.20.0) con lo stesso pattern OIDC generico già usato per Google/Microsoft (nessun caso speciale come il previsto per GitHub): authorization code flow one-shot, `userinfo` letto una sola volta, token scartato subito dopo (stesso invariante di ADR-P22). Unica differenza tecnica dagli altri due provider: scope **`openid profile email`** invece di `openid email` — il prodotto "Sign In with LinkedIn using OpenID Connect" concede i tre scope in blocco e un sottoinsieme rischia `invalid_scope`; il campo `scope` è stato reso configurabile per provider (`handleDevOAuthStart` ora legge `cfg.scope`) invece di restare hardcoded uguale per tutti. Il controllo `email_verified === false` (prima solo su Google) è esteso a LinkedIn. Nessun aggancio automatico oggi tra provider LinkedIn e fascia Professionale: è solo un quarto bottone su `/developer/keys`, alla pari di Google/Microsoft — l'attribuzione "email LinkedIn ⇒ fascia Professionale" resta da disegnare quando quella fascia esce dal perimetro P26.

Conseguenze: **In produzione dal 2026-07-12** (imgauth 1.20.0, tag `v1.20.0`): FASE 0 completata (app registrata su LinkedIn Developer Portal, prodotto "Sign In with LinkedIn using OpenID Connect" provisionato, redirect URI registrate), login reale verificato in locale, poi rilascio via pipeline P24 (push → check+deploy-staging automatici → gate `production` approvato dal gestore → `wrangler versions upload`+`deploy`). `CTL-dev-selfservice` esteso a menzionare LinkedIn nello stesso giro del deploy: il controllo descrive solo ciò che è verificabile in produzione. Superficie di abuso invariata rispetto a Google/Microsoft (stessa quota 50/mese, stesso indice UNIQUE parziale, stessa notifica Telegram) — nessun nuovo rischio, copertura di `RSK-dev-selfservice-signup-abuse` invariata.

ADR-P25-linkedin

P24 — Catena DevOps CI/CD con ambiente staging replicatoaccepted

Fino a questa decisione esisteva un solo ambiente — la produzione. Ogni modifica veniva provata in `wrangler dev` locale (che non replica service binding, rete reale, D1/R2 remoti) e poi deployata direttamente sull'ambiente che gli utenti usano: nessuna versione era mai stata osservata "dal vivo" prima che un utente reale la vedesse. Un fix di produzione del 2026-07-11 (propagazione errori del firmatario) ne è l'esempio: verificabile solo simulando in locale, deployato senza osservazione diretta sull'ambiente reale.

Decisione: Introdurre, componente per componente, un ambiente di **staging replicato** (stesso `wrangler.toml`, blocco `[env.staging]` — il config non può divergere strutturalmente), **CI su GitHub Actions** (check automatici su ogni PR, deploy staging automatico + smoke test su merge in main) e un **gate umano formalizzato** per la produzione (GitHub Environment con required reviewer, `wrangler versions upload` a traffico zero prima della promozione, `wrangler rollback` come via d'emergenza). Lo staging non contiene mai dati né segreti di produzione (D1/R2/secret propri, generati apposta); `HMAC_SECRET` di produzione non lascia mai il suo perimetro. Esecuzione per fasi piccole e verificabili (design completo in `P24-DESIGN-devops-cicd.md`, hub privato), ciascuna con criteri di accettazione concreti prima di procedere alla successiva.

Conseguenze: FASE 0-4 e 7 completate il 2026-07-11 (FASE 7 eseguita prima di 5-6 RADART su richiesta esplicita — nessuna dipendenza tra le due): inventario, codice imgauth reso multi-ambiente (CORS configurabile), staging `imgauth-staging` in funzione (D1/R2/secret propri, cron disattivato), CI collaudata end-to-end su run reali pubblici (push diretto, PR normale, merge con deploy automatico, PR con errore introdotto ad arte che fa fallire il check senza mai arrivare al merge), **gate di produzione attivo e collaudato**: GitHub Environment `production` con required reviewer, job `deploy-production` che resta in attesa di approvazione prima di eseguire qualunque step, `versions upload` (0% traffico) seguito da `versions deploy` (promozione al 100%) solo dopo l'approvazione, rollback provato dal vivo su staging. Primo rilascio gated della storia del progetto completato (run pubblico https://github.com/SPAZIO-GENESI/imgauth/actions/runs/29153958055). **Interfaccia di staging** (FASE 7): repo pubblico `attestazione-staging` (GitHub Pages, solo `*.github.io`, D2), generato dal branch `staging` di imgauthweb, puntato a `imgauth-staging`. Collaudo browser reale con Playwright (non solo curl): footer con versione del motore confermata via CORS, attestazione completa con Turnstile di test, PDF non firmato scaricato e verificato, link permanente presente. Emersi e corretti nel collaudo due bug nel codice **condiviso con la produzione** (comportamento in produzione verificato invariato dopo il deploy): `turnstileEnabled()` non riconosceva le sitekey di test Cloudflare (estesa da `/^0x/` a `/^[0-3]x/`); i percorsi assoluti risolvono alla radice del dominio, corretto in produzione (dominio custom) ma non nel sottopercorso del repo Pages secondario (fix solo nella copia generata). Il controllo pubblico collegato (CTL-cicd-pipeline) è stato promosso da `status: draft` a **`active`**: il gate di produzione è ora collaudato quanto lo staging, non solo dichiarato — coerente con il principio del registro (evidenze, non dichiarazioni). RADART ha il proprio staging/CI pianificato (FASE 5-6) ma non ancora eseguito: il controllo copre oggi imgauth e authweb, non ancora RADART.

ADR-P24

P23 — Bot Telegram di attestazione come canale comodità dichiaratoaccepted

Il servizio raggiunge chi arriva sul sito. Molti utenti vivono già dentro Telegram e non aprirebbero mai un browser per attestare un file — un bot avrebbe portato l'attestazione dove le persone già sono. Il vincolo era la privacy: la promessa pubblica del sito ("il file non lascia mai il tuo dispositivo") si basa sul calcolo dell'impronta nel browser dell'utente; un bot Telegram non ha equivalente — un file inviato in chat DEVE transitare per i server di Telegram e per il nostro Worker prima che l'impronta possa essere calcolata. Una nota di design precedente (P21, 2026-07-09) aveva scartato l'idea per questo motivo, rimandando la decisione a una sessione dedicata con il gestore.

Decisione: **Due canali, due livelli di privacy dichiarati** — non un compromesso nascosto. Il sito resta il canale full-privacy, invariato. Il bot (`attest-bot`, repo pubblico, Cloudflare Worker webhook, client puro dell'API pubblica imgauth — zero modifiche al motore) è un canale di **comodità dichiarata**: prima di scaricare qualunque file, mostra una disclosure bloccante (il file transita per Telegram e per il nostro server; l'impronta si calcola in streaming con `crypto.DigestStream`; i byte si scartano subito; nulla viene salvato) e richiede un'accettazione esplicita, una volta per utente. Le garanzie crittografiche restano identiche su entrambi i canali (HMAC, timestamp server, ancoraggio Bitcoin): cambia solo dove transita il file, mai l'integrità dell'attestazione. Credenziale dedicata (`sg_k_…`, label `telegram-bot`) che bypassa solo la challenge Turnstile, stesso perimetro di P21. Foto rifiutate (Telegram le ricomprime, l'impronta non corrisponderebbe più all'originale); tetto 20 MB (limite Bot API); quote giornaliere per utente (5 attestazioni, 20 verifiche).

Conseguenze: Amplia l'accesso al servizio senza toccare imgauth né indebolire il perimetro di sicurezza esistente. Introduce una nuova, piccola superficie di dati personali: id utente Telegram e contatori d'uso giornalieri (D1 dedicata `attest-bot`, distinta da quella del motore), cancellati automaticamente dopo 90 giorni o su richiesta. Il file dell'opera non viene mai salvato (hash in streaming), ma transita — un compromesso esplicito, accettato dall'utente prima di ogni download, non una scorciatoia silenziosa. Durante l'implementazione sono emersi e stati corretti due bug reali nell'estrazione dei dati da un certificato PDF (interpretazione dei nomi filtro PDF e della sintassi esadecimale del testo) e un bug di design (un PDF veniva sempre trattato come "candidato certificato", rifiutando l'attestazione di PDF qualsiasi) — tutti scoperti solo testando contro dati reali di produzione, non dalla sola lettura del codice.

ADR-P23

Cloudflare Access (Zero Trust) davanti al pannello adminaccepted

ADR-P21-admin aveva previsto la protezione applicativa del pannello admin come primo strato, con piano di rafforzarla con Cloudflare Access (Zero Trust) appena configurabile da dashboard.

Decisione: Il gestore ha configurato una policy Cloudflare Zero Trust Access davanti al percorso del pannello admin. La protezione applicativa resta attiva come secondo strato (se mantenerla o rimuoverla è una decisione aperta, non urgente).

Conseguenze: Il rischio tracciato in RSK-admin-panel-weak-auth passa a un accesso a doppio strato, con identità verificata a monte prima di raggiungere il Worker — impatto residuo abbassato da medium a low. La configurazione di Access vive nella dashboard Cloudflare (Zero Trust), fuori dalla portata di un deploy automatico.

ADR-P21-admin-cfaccess

P21 follow-up — Pannello admin credenziali agenteaccepted

Dopo il rollout di P21 (ADR-P21), la sola via per emettere/revocare/monitorare le credenziali agente era uno script CLI locale (`scripts/issue-agent-key.mjs`) più query SQL a mano su D1 — sufficiente per una manciata di partner, non per gestirne decine. Serviva un'interfaccia più comoda, ma senza esporre un endpoint self-service pubblico né rimandare la sicurezza a "poi".

Decisione: Pannello HTML servito dal Worker con lo stesso identico perimetro dello script CLI: crea/elenca/revoca/modifica quota di `agent_credentials`, mai l'emissione di certificati (che resta governata solo da `HMAC_SECRET`). Accesso riservato al gestore con una protezione applicativa, prevista fin da subito come primo strato da rafforzare con **Cloudflare Access** (Zero Trust), configurabile dalla dashboard Cloudflare. Nessuna nuova route Cloudflare: il pannello è coperto dalla route esistente del dominio del Worker.

Conseguenze: Guadagno operativo immediato (gestione via browser invece di SQL a mano) con il perimetro d'azione confinato alla gestione delle credenziali agente, senza mai toccare la firma dei certificati. Il rafforzamento dell'accesso con Cloudflare Access è tracciato come azione aperta in RSK-admin-panel-weak-auth, poi completato (vedi ADR-P21-admin-cfaccess) — non silenziosamente accettato a tempo indeterminato.

ADR-P21-admin

P22 — Self-service API key con verifica email OAuth one-shot, post-moderazioneaccepted

L'emissione delle API key `sg_k_…` era interamente manuale (email a it@spaziogenesi.org), un attrito che scoraggia l'adozione da parte di sviluppatori terzi proprio mentre P21 apriva il servizio agli agenti AI. Un self-service completamente anonimo avrebbe però rimosso l'unica leva utile contro l'abuso: sapere chi ha chiesto una chiave. Serviva un modo di verificare un'identità minima (un'email reale) senza costruire un sistema di account con password, senza raccogliere più dati del necessario, e senza indebolire il perimetro di sicurezza già stabilito in P21 (il bypass resta limitato alla sola challenge Turnstile).

Decisione: OAuth **one-shot**, non login: l'authorization code flow di Google o Microsoft (scope minimo `openid email`) serve a UNA sola chiamata a `userinfo` per leggere l'email verificata dal provider; l'access/id token vengono scartati subito dopo — nessuna sessione, nessun cookie, nessun token OAuth persistito in D1 o nei log. La chiave emessa (`GET /developer/keys` → `/api/dev/oauth/start` → `/api/dev/oauth/callback/<provider>`) è una normale `sg_k_…` (stessa tabella, stesso meccanismo bearer di P21), con `owner_email`/`owner_provider` associati in chiaro (non hashati, a differenza del secret) per consentire **post-moderazione**: quota 50/mese, un'email = una chiave attiva (indice UNIQUE parziale in D1, non solo applicativo), emissione immediata + Telegram al gestore ad ogni emissione + revoca a un tap dal pannello admin. Niente pre-approvazione (attrito minimo per il pubblico developer, decisione esplicita del gestore) e niente Turnstile su questo flusso (il consenso OAuth è già una barriera più forte, e la sitekey attuale è vincolata a un altro hostname). Email in **D1** (non R2 EU come i PDF): l'alternativa "email in R2 EU + solo id in D1" è stata valutata e scartata perché avrebbe complicato pannello admin e vincolo di unicità per un beneficio marginale — D1 non offre la stessa garanzia di giurisdizione di R2, coperta invece dal DPA Cloudflare + SCC. Retention: email attiva finché la chiave è attiva + 180 giorni dalla revoca (traccia anti-abuso), poi anonimizzata da cron; cancellazione anticipata su richiesta via `forget` nel pannello admin.

Conseguenze: Attrito di emissione ridotto a un login OAuth, senza indebolire nessuna garanzia crittografica: il bypass concesso da una chiave self-service resta identico a quello di P21 (solo Turnstile su /api/hash), HMAC e rate-limit invariati. Introduce il **primo trattamento di dati personali** del sistema (email+provider di chi richiede una chiave) — gli utenti che attestano restano a zero dati personali, invariato. Superficie nuova: abuso di email verificate temporaneamente (alias, caselle condivise) — limitato dalla quota bassa (50/mese) e dalla notifica immediata al gestore per revoca rapida (vedi RSK-dev-selfservice-signup-abuse). Il `MS_OAUTH_CLIENT_SECRET`, a differenza degli altri segreti del Worker, **scade** per policy Entra ID — nuovo elemento nel ciclo di vita dei segreti da tracciare nel caveau (piano B se il tenant riblocca la creazione di secret: certificato + `private_key_jwt`).

ADR-P22

P21 — Accesso agenti: API key + device flow, bypass del solo Turnstileaccepted

L'onda di agenti AI e client automatici (assistenti, integrazioni, MCP) è l'unico canale di crescita del servizio con potenziale non lineare: un agente può attestare/verificare per conto di un umano senza passare dal browser. Il flusso browser esistente richiede però una challenge Turnstile a ogni attestazione — pensata per un umano davanti a uno schermo, non automatizzabile da un agente senza rompere il modello anti-bot per tutti. Serviva un secondo percorso di accesso che non indebolisse le garanzie esistenti (HMAC, timestamp server, rate limit) né aprisse un endpoint self-service senza controllo.

Decisione: Due credenziali, un solo meccanismo: bearer token D1-backed (in D1 sta solo sha256(secret), mai il secret in chiaro). **API key** (`sg_k_<id>_<secret>`) per partner convenzionati — emessa a mano, quota mensile, nessun endpoint admin (solo script locale `scripts/issue-agent-key.mjs` + `wrangler d1 execute` per la revoca). **Session token** (`sg_s_<id>_<secret>`) per uso personale via agenti/MCP — ottenuto con un device flow: l'umano autorizza una volta su una pagina servita dal Worker (`/agent/authorize`, stesso widget Turnstile del sito), l'agente polla fino a ricevere il token (consegnato una sola volta), valido 24h/20 attestazioni. Una credenziale valida su `/api/hash` sblocca SOLO il bypass della challenge Turnstile: HMAC, timestamp server e rate limit per-IP restano identici al percorso browser. Client di riferimento pubblicato come repo separato (`attest-mcp`, npm, stdio): calcola l'hash in locale, non invia mai i byte del file — la stessa full privacy del sito, estesa agli agenti.

Conseguenze: Nessun indebolimento delle garanzie crittografiche esistenti: una credenziale compromessa può al massimo emettere certificati VERI entro la sua quota, mai falsificarne uno (vedi ARCHITECTURE.md Assunzione 7). Superficie nuova da monitorare: quota/allarmi Telegram (sessione autorizzata, soglia 80/100%, >10 credenziali invalide/giorno) coprono l'abuso entro quota e i tentativi ripetuti di credenziali errate — non eliminano il rischio residuo di abuso di una credenziale legittima da parte del titolare stesso, mitigato solo da quota + revoca manuale (vedi RSK-agent-credential-abuse). Gotcha operativo scoperto in fase di deploy: `wrangler deploy`/`wrangler triggers deploy` non sincronizzano route zone NUOVE — richiede creazione manuale dalla dashboard Cloudflare a ogni route aggiunta (non specifico di questa decisione, ma osservato durante il rollout: vedi memoria operativa `imgauth-wrangler-routes-trap`).

ADR-P21

Convenzione tag di release + monitoraggio automatico delle cadenze con avviso Telegramaccepted

Due lacune emerse nella stessa sessione. (1) MET-integrity dichiarava un terzo componente ("quota di release con tag git") mai implementato: nessuno dei tre repo pubblici (imgauth, imgauthweb, autart-signer) aveva mai avuto un tag. (2) L'utente ha chiesto esplicitamente chi controlla se un processo ricorrente (restore drill, revisione trimestrale, review esterna annuale, ancoraggio dogfooding) viene dimenticato: si è scoperto che il PRC.schema non tracciava affatto una data di ultima esecuzione, quindi né il maintainer né un'assistenza AI potevano saperlo dal registro — solo "ricordarselo", la stessa fragilità che il GTF esiste per eliminare altrove. L'utente ha preferito un avviso Telegram a un'email o un'issue GitHub (rischio di perdersi tra le email).

Decisione: Adottata la convenzione: ogni bump di versione (PRC-release-coordinata aggiornato) è accompagnato da un tag annotato vX.Y.Z pushato nel repo del componente. Primi tag creati il 2026-07-09: imgauth v1.15.1, imgauthweb v1.16.3, autart-signer v1.2.0. collect-evidence.mjs raccoglie ora anche i tag (GitHub API /repos/.../tags) e i commit di autart-signer (prima mancanti); score.mjs aggiunge un proxy iniziale (quota repo con almeno un tag, non ancora quota storica di versioni taggate). Aggiunto frequency_days/last_run allo schema PRC; creati i due processi mancanti (PRC-review-trimestrale 90gg, PRC-review-esterna-annuale 365gg; PRC-restore-drill portato a 180gg). Scritto generators/check-cadences.mjs: gira settimanalmente dentro collect-evidence.yml, confronta ogni PRC (o l'ultimo bundle dogfooding) con la sua cadenza dichiarata e invia un messaggio Telegram SOLO se qualcosa è scaduto — nessuno spam se tutto è in regola. Aggiunta una modalità di test esplicita (input test_telegram su workflow_dispatch, stesso pattern del canary HMAC di imgauth) perché altrimenti non c'era modo di verificare che il canale funzionasse senza aspettare mesi. Registrati RSK-cadence-drift e CTL-cadence-monitoring.

Conseguenze: MET-integrity ha ora un dato reale anche sul terzo componente (2/3 al primo giro del collettore dopo i tag di imgauth/imgauthweb, 3/3 atteso al prossimo giro con anche autart-signer). I tre secret Telegram sono stati aggiunti dall'utente sul repo gtf e il test è stato verificato funzionante lo stesso giorno (EVD-cadence-check-runs). Le quattro cadenze ricorrenti del GTF sono ora machine-checkable, non più dipendenti dalla sola memoria umana.

ADR-GTF-009

Primo ancoraggio dogfooding e sua incorporazione nel calcolo di MET-integrityaccepted

Il meccanismo di ancoraggio dogfooding (GTF-ARCH §6.4) esisteva in codice (generators/anchor-monthly.mjs) ma non era mai stato eseguito: MET-integrity dichiarava tre componenti nella formula ma ne calcolava solo uno (sonda HMAC). L'utente ha prodotto il primo bundle cumulativo e lo ha attestato sul servizio stesso, chiedendo di completare la registrazione.

Decisione: Registrato il primo ciclo (periodo 2026-07, bundle snapshots/anchors/2026-07-bundle.json, hash cd57b5d3a96947a2264cbb237b3c8eb26cb130e2703838e0597d7b3189e5629b) come CTL-dogfooding-anchor + EVD-dogfooding-anchor + IMP-gtf-anchor-monthly. Corretti nello stesso giro due difetti scoperti nel processo: score.mjs non escludeva snapshots/anchors/ dalla ricerca dello snapshot settimanale più recente (azzerava l'indicatore Integrità appena creata la cartella); REQ-27037-pres-01.satisfied_by e RSK-archive-tampering.mitigated_by non includevano il nuovo controllo, rendendolo invisibile sul Trust Center (build-site.mjs legge quella direzione, non CTL.satisfies/mitigates). Incorporato poi il componente nel calcolo numerico: quota di bundle mensili committati rispetto ai mesi trascorsi da GTF_BIRTH_MONTH (2026-07), in media con la sonda HMAC.

Conseguenze: MET-integrity passa da "un solo componente calcolabile" a due su tre reali (manca ancora la quota di release taggate, vedi ADR-GTF-009). Stabilita la cadenza mensile per i cicli successivi (prossimo: agosto 2026), sorvegliata da CTL-cadence-monitoring (ADR-GTF-009).

ADR-GTF-008

Pubblicazione di autart-signer (chiusura P11)accepted

ADR-P11 (2026-06-12) aveva già ripulito la storia git di autart-signer in blocco (commit unico 9e524b2, LICENSE AGPL, README riscritto) ma lasciava un'azione manuale in sospeso: rendere il repo effettivamente pubblico su GitHub. Fino a quel momento CTL-pades-blt-tsa dipendeva solo dalla fiducia nel pannello firme di Acrobat, senza che il codice che genera la firma fosse ispezionabile da terzi.

Decisione: Il repo è stato eliminato e ricreato (non solo reso pubblico in-place), come raccomandato dall'ADR-P11 per garanzia assoluta contro oggetti orfani raggiungibili lato server da vecchi riferimenti. Verificato via API pubblica GitHub, non sulla sola parola: repository pubblico, commit unico 9e524b2, licenza AGPL-3.0 confermata.

Conseguenze: EVD-git-authart e IMP-authart-pades-tsa passano da internal a public; verify_howto di CTL-pades-blt-tsa aggiornato per includere il link al codice sorgente ispezionabile, non solo la verifica nel lettore PDF. Con questo, tutti e tre i repository di prodotto (imgauth, imgauthweb, autart-signer) sono pubblici — si chiude l'ultimo elemento esterno del backlog P0-P19 di CLAUDE.md (img-auth-hub).

ADR-GTF-007

Collettore di evidenze settimanale (§6.3) e Integrità parzialeaccepted

In M3 si era scelto deliberatamente di calcolare l'Open Trust Score solo dal registro committato, senza collettore né chiamate di rete, per restare riproducibile offline da chiunque clona il repo. Questo lasciava però l'indicatore Integrità permanentemente n/d, perché il suo primo componente (esito della sonda HMAC) esiste solo come stato live di /api/status, mai registrato da nessuna parte.

Decisione: Costruito generators/collect-evidence.mjs: raccoglie settimanalmente (+ workflow_dispatch) stato live, storico 90gg, health-log 7gg, issue del monitor e commit recenti dei repo pubblici — sole letture pubbliche, nessun dato privato — in snapshots/YYYY-Www/, con manifest.json (SHA-256 di ogni file). Aggiorna anche last_seen sulle EVD corrispondenti (sostituzione mirata sul testo grezzo, per non stravolgere la formattazione con un dump YAML completo). score.mjs legge poi SOLO l'ultimo snapshot già committato (mai una chiamata di rete propria): la riproducibilità offline resta intatta, cambia solo la fonte del dato.

Conseguenze: Integrità passa da sempre n/d a parzialmente calcolabile (100/50/0 sul solo componente "worker"), ma resta marcata esplicitamente come parziale — sia nel JSON (campo note) sia nel Trust Center (asterisco + tooltip) — perché non include ancora storico release taggate né esito ancore OTS mensili (questi restano gap dichiarati in MET-integrity). Primo snapshot reale (2026-W28) committato: score 92→94, 5→6 indicatori disponibili.

ADR-GTF-006

Attivazione del canary HMAC esterno (chiusura P17-B)accepted

P17-B era in backlog da tempo (vedi ADR-P17): la sonda interna di /api/status non rileva una rotazione ERRATA di HMAC_SECRET, perché rifà un round-trip firma+verifica col segreto ATTUALE — resta "coerente" anche se qualcuno cambia il segreto per errore. Serviva un riferimento firmato una volta col segreto corretto e conservato FUORI dal Worker.

Decisione: Aggiunto un secondo job "canary" a monitor.yml (imgauth): POST a /api/verify con un'attestazione nota (variabili di repo non-secret CANARY_HASH/CANARY_ATTESTAZIONE/CANARY_HMAC, ottenute con un'attestazione reale generata in produzione), issue+Telegram separati (label hmac-canary-alert) se hmac_valido risulta false. CTL-hmac-canary passa da draft ad active solo dopo la prima verifica reale riuscita (Telegram "test canary HMAC ok", 2026-07-09T11:26:37Z) — non al solo merge del codice, per non dichiarare attivo un controllo non ancora provato contro produzione.

Conseguenze: Score: Trasparenza/Tracciabilità/Riproducibilità salgono con un controllo attivo in più a catena completa (90→92). Nessun impatto su imgauth oltre al nuovo job; rate limiting invariato (una chiamata ogni 15 min è ben sotto soglia RL_API).

ADR-GTF-005

Collocazione degli snapshot di evidenzaaccepted

Il collettore settimanale di evidenze (GTF-ARCH §6.3) produce snapshot JSON che devono restare immutabili e verificabili nel tempo. Vanno collocati dentro il repo `gtf` stesso (versionati in git, diffabili, pubblici senza altra infrastruttura) oppure in un bucket R2 dedicato (più adatto se il volume cresce molto).

Decisione: Partire con gli snapshot dentro il repo `gtf` (cartella `snapshots/`), perché a basso volume settimanale git è più che sufficiente ed è già pubblico e verificabile via storia; migrare a R2 solo se le dimensioni del repo diventano un problema concreto.

Conseguenze: Nessun costo aggiuntivo nella fase iniziale; da rivalutare quando il volume degli snapshot cresce (nessuna soglia numerica fissata ora — lo si osserva in fase M4).

ADR-GTF-004

Lingua del registroaccepted

Il registro deve essere leggibile sia da modelli AI diversi (interoperabilità con schemi/standard, spesso in inglese) sia dagli stakeholder italiani del progetto (studenti, ricercatori, collezionisti).

Decisione: Record bilingui minimi: campi strutturali (id, tipo, enum di stato) in inglese; campi discorsivi (title, statement, context, decision) in italiano. Già applicato di fatto a tutti i 122 record di M0-M3 (PRN, CTL, REQ, RSK, ADR, MET...); una versione inglese del Trust Center resta possibile in un secondo tempo se richiesta, senza dover toccare il registro.

Conseguenze: Una scelta "tutto italiano" semplificherebbe la scrittura ma renderebbe più difficile il riuso degli schemi da parte di modelli o revisori non italiani.

ADR-GTF-003

URL pubblico del Trust Centeraccepted

Il Trust Center (GTF-ARCH §7) deve avere un URL pubblico stabile. Due opzioni: instradarlo sotto il dominio già usato dal servizio (attestazione.spaziogenesi.org/trust/) via route Cloudflare + Worker proxy leggero — stesso pattern già rodato per /c/*; oppure un sottodominio dedicato (trust.spaziogenesi.org) con CNAME diretto su GitHub Pages, senza passare dal Worker.

Decisione: Sottodominio dedicato trust.spaziogenesi.org (CNAME diretto su GitHub Pages, nessun coinvolgimento del Worker imgauth). Motivo del cambio rispetto alla propensione iniziale dell'architettura (che favoriva la route sotto lo stesso dominio): il principio di indipendenza dei tre repository (CLAUDE.md) vale anche per gtf, che deve potersi pubblicare da solo senza mai richiedere modifiche a imgauth. Il ragionamento "un solo dominio" ha senso per /c/<hash> (fa parte del percorso di verifica di un'opera, serve continuità di esperienza) ma non per il Trust Center, che è una pagina istituzionale/di trasparenza, non parte di quel flusso.

Conseguenze: DNS: record CNAME "trust" → spazio-genesi.github.io. Impostazione del custom domain nelle GitHub Pages settings del repo gtf rimandata alla fase M3 (Trust Center + Score), quando site/ avrà davvero un contenuto da servire. Nessuna modifica necessaria a imgauth/wrangler.toml.

ADR-GTF-002

Nome del framework e del repositoryaccepted

Il progetto era stato disegnato come "Open Trust Framework" (OTF). Il gestore ha chiesto di rinominarlo per coerenza col nome dell'ente titolare.

Decisione: Il framework si chiama "Genesis Trust Framework" (GTF); il repository pubblico che ne è la Single Source of Truth è `spazio-genesi/gtf`. I nomi di componenti interni ("Trust Center", "Trust Registry", "Open Trust Score") restano invariati: sono nomi funzionali, non il brand del framework.

Conseguenze: Tutti i riferimenti a OTF/ADR-OTF-* nell'architettura sono stati rinominati a GTF/ADR-GTF-* prima della creazione del repo, per evitare di partire con un nome già da correggere.

ADR-GTF-001

P19 — Redesign dell'interfaccia: identità B, tablist ARIA, lessico onestoaccepted

L'interfaccia originale non comunicava bene la promessa del servizio fin dall'alto della pagina e presentava contrasti sotto soglia WCAG AA.

Decisione: Sviluppato come bozza index2.html (pubblicata noindex per valutazione), poi promossa a index.html (bozza rimossa, vecchia UI conservata in index.html.old): H1 = "Attestazione delle opere digitali" per le corrispondenze SEO, promessa in lingua piana come sottotitolo; tablist ARIA sticky (Attesta / Verifica / Come funziona); demo "L'impronta, dal vivo" (SHA-256 live di una frase digitata); eliminato ovunque il verbo "caricare" (15 occorrenze) a favore di scegli/apri/trascina, perché contraddiceva la garanzia di full privacy (P16); contrasti ≥4.5:1, focus visibile, target ≥44px, aria-label sugli input con solo placeholder.

Conseguenze: Nessuna doppia pagina divergente (stessa lezione già applicata con beta.html in P12); il lessico onesto diventa un principio esplicito e durevole del progetto, non solo una scelta di questa release.

ADR-P19

P18 — Interfaccia installabile sulla home screen, senza service workeraccepted

Si voleva un'esperienza "app" per gli utenti mobile, senza i costi e la divergenza di codice di un'app nativa.

Decisione: Aggiunto manifest.json (display standalone, lingua italiana) e icone PNG generate dal favicon SVG (monogramma); deliberatamente senza service worker, per non dover invalidare una cache offline — la pagina resta sempre live da GitHub Pages; avviso discreto "Aggiungi alla schermata Home" visibile solo su smartphone e nascosto se l'app è già installata (via media query display-mode: standalone).

Conseguenze: Zero costi aggiuntivi, zero store, nessun secondo codebase da mantenere (alternativa scartata: app nativa Android/iOS, 99€/anno solo Apple più review e divergenza). Un possibile seguito (Web Share Target, "Condividi → Attesta" dalla galleria Android) richiederebbe però un service worker, non ancora implementato.

ADR-P18

P17 — La sonda interna che fa fallire /api/status se l'HMAC è rottoaccepted

Un audit interno ha rilevato che /api/status non esercitava affatto la logica di emissione: con HMAC_SECRET assente o rotto il servizio emetteva comunque attestazioni senza firma valida (hmac null) e cert-pdf falliva con 503, ma lo stato semaforico restava verde.

Decisione: Round-trip signHmac + verifyHmac su una stringa fissa a ogni campionamento di stato; il risultato confluisce nel componente "worker" di /api/status (down se il round-trip fallisce). Dichiarato inoltre che HMAC_SECRET non va mai ruotato: la rotazione invaliderebbe per sempre la verifica di tutti i certificati già emessi (nessun supporto a doppia chiave).

Conseguenze: La sonda interna non rileva però una rotazione ERRATA del segreto (una firma con un segreto nuovo ma sbagliato risulterebbe comunque "internamente coerente"). Identificato il bisogno di un canary esterno che confronti un'attestazione nota firmata col segreto giusto — pianificato nel GTF come CTL-hmac-canary (vedi il registro per lo stato attuale del controllo, aggiornato indipendentemente da questa ADR storica).

ADR-P17

P16 — Full privacy: l'opera non lascia mai il dispositivo dell'utenteaccepted

Fino a questo punto, sia per l'attestazione sia per la verifica, il file transitava comunque verso il server per il calcolo dell'hash.

Decisione: SHA-256 calcolato nel browser via WebCrypto (funzione sha256Hex); /api/hash accetta sha256 dal client come percorso primario (il campo image in base64 resta accettato solo per retrocompatibilità con client in cache); /api/verify reso capace di verificare la sola firma HMAC senza file; tetto dimensione alzato da ~75MB effettivi a 1GB lato client (WebCrypto non è streaming, il file va letto in memoria).

Conseguenze: Garanzia anti-retrodatazione intatta: timestamp e HMAC restano generati server-side. Nessuna versione "parallela" del servizio né gating a donazione — un solo percorso per tutti (stessa lezione di beta.html: le pagine doppie divergono). La video-guida è stata rigenerata interamente in locale (wrangler dev) per non toccare la produzione durante le riprese.

ADR-P16

P15 — Da 2 a 4 calendar OpenTimestamps, e una misura di latenza più onestaaccepted

Si osservavano falsi "rallentamenti" dell'ancoraggio su /status, causati da un singolo calendar lento, anche se l'ancoraggio reale ha bisogno di una sola risposta su più calendar interrogati.

Decisione: Emissione: da 2 calendar (alice, bob) a 4 (alice, bob, finney, catallaxy), interrogati in parallelo con timeout (OTS_SUBMIT_TIMEOUT 8s) invece che in sequenza senza scadenza; misura di /status basata sul tempo del primo calendar a rispondere (Promise.any) invece che del più lento (Promise.all); "degraded" solo se cadono tutti.

Conseguenze: Più ridondanza sull'ancoraggio senza rischio che un calendar appeso rallenti o blocchi l'emissione del certificato; niente più falsi "degradati" per un singolo calendar lento.

ADR-P15

P14 — Pagina pubblica permanente di verifica per ogni opera attestataaccepted

L'unica prova portabile di un'attestazione era il PDF; mancava un URL pubblico stabile da condividere (per esempio con una galleria o un collezionista) senza dover allegare il file.

Decisione: Nuovo endpoint GET /c/<sha256>: pagina HTML server-rendered con impronta, data, ancoraggio OpenTimestamps e QR (che codifica l'URL permanente stesso), servita via route Cloudflare su attestazione.spaziogenesi.org/c/* (dominio già proxato) e anche su imgauth.spaziogenesi.org/c/* (custom domain del Worker); dati letti da un sidecar meta/cert/<sha256>.json scritto all'emissione, in modo idempotente e non-blocking; il QR del certificato ora punta a questa pagina invece che alla sola verifica con hash precompilato.

Conseguenze: Stesso modello di fiducia di /api/cert (recuperabile solo da chi conosce l'hash); i certificati pre-1.14.0 mostrano comunque impronta e ancoraggio, con data ricostruita per fallback dalla chiave del PDF.

ADR-P14

P13 — Storicizzazione fine di malfunzionamenti e rallentamenti su D1accepted

/api/status dava solo lo stato istantaneo, senza storico degli eventi fini — inclusi i rallentamenti sotto soglia che non degradano il servizio ma indicano cosa è migliorabile.

Decisione: I check di /api/status misurano la latenza (runChecks); logHealth scrive su Cloudflare D1 (tabella health_log, binding DB) solo gli eventi notevoli — errore, degrado, o esito ok con latenza oltre soglia "watch" — con scrittura non-blocking; nuovo endpoint GET /api/health-log?day=; il drill-down giorno di /status/ mostra questi eventi anche nei giorni "verdi".

Conseguenze: I rallentamenti si vedono prima che diventino guasti; un errore D1 non interrompe mai il Worker (try/catch attorno alla scrittura).

ADR-P13

P12 — Tre funzionalità gratuite ad alto valoreaccepted

Si volevano tre funzionalità (recupero certificato, verifica via PDF, badge) a costo zero, sfruttando l'infrastruttura già esistente (R2, hash, endpoint).

Decisione: Sviluppate su una pagina di appoggio beta.html (stesso dominio → stesso CORS delle API di produzione), verificate live a step, poi promosse in index.html: GET /api/cert per il recupero del certificato smarrito (nuovo schema pdf/<sha256>/certificato_<stamp>.pdf); pdf.js auto-ospitato (nessuna CDN di terze parti) per leggere e verificare il certificato lato client; badge SVG "Opera attestata", verde solo se l'hash è realmente in archivio.

Conseguenze: beta.html eliminata subito dopo la promozione, per non far divergere due pagine (lezione riapplicata più volte nelle fasi successive); i certificati pre-1.8.0 non sono recuperabili per hash (nessuna migrazione fatta).

ADR-P12

P11 — Apertura del codice sorgente: licenze e ripulitura della storiaaccepted

Si è deciso di pubblicare il codice del motore e dell'interfaccia, ma la storia git di autart-signer conteneva un file signer.p12 recuperabile e una password P12 hardcoded (già ruotati da tempo, mai riusati).

Decisione: Licenza AGPL-3.0 per imgauth (chi riusa il motore per un servizio deve ripubblicare le modifiche; la licenza copre il codice, non il servizio — i segreti HMAC/firma restano server-side, un clone non supera la verifica ufficiale); licenza MIT per imgauthweb; storia git di autart-signer sostituita in blocco con un commit unico contenente LICENSE AGPL e README riscritto (il precedente descriveva uno stack .NET errato).

Conseguenze: img-auth-hub resta privato per scelta (contiene materiale operativo). Il repo autart-signer è stato reso pubblico il 2026-07-09 (eliminato e ricreato, come previsto, per garanzia contro oggetti orfani lato server): github.com/SPAZIO-GENESI/autart-signer, commit unico 9e524b2 verificato via API pubblica.

ADR-P11

P10 — Ancoraggio decentralizzato in Bitcoin via OpenTimestampsaccepted

Mancava una prova di esistenza indipendente dal servizio stesso, che restasse verificabile anche se Spazio Genesi ETS smettesse di esistere.

Decisione: In /api/cert-pdf, dopo la verifica HMAC e prima della firma, costruzione della prova OpenTimestamps (calendar alice+bob.btc.calendar. opentimestamps.org) serializzata a mano nel formato DetachedTimestampFile e validata contro la libreria ufficiale python-opentimestamps; riga "Ancoraggio blockchain" nel blocco Dettagli tecnici del PDF con link a /api/ots; link visibile in authweb dopo "Scarica PDF".

Conseguenze: Tre àncore indipendenti su ogni certificato: HMAC (server), marca temporale TSA (authart), Bitcoin (OpenTimestamps); l'identità del firmatario resta self-signed.

ADR-P10

P9 — Marca temporale da terza parte riconosciuta nel certificatoaccepted

La sola firma self-signed non dava alla data del certificato alcun riconoscimento di terze parti in lettori come Adobe Acrobat.

Decisione: authart firma PAdES B-LT con marca temporale RFC 3161 da una TSA in Adobe AATL (default DigiCert, configurabile via TSA_URL) più LTV (catena TSA/OCSP embedded nel DSS); fail-open se TSA/OCSP sono irraggiungibili (firma senza marca, mai un errore all'utente); corretto il key usage del certificato di produzione a "digital_signature" (il default pyhanko per PAdES è non_repudiation, incompatibile col p12 esistente).

Conseguenze: La data del certificato è ora attestata da terza parte attendibile anche restando self-signed sull'identità del firmatario; upgrade futuro possibile con un sigillo elettronico qualificato eIDAS (a pagamento).

ADR-P9

P8 — Metadati dell'opera dichiarati dall'autore, vincolati alla firmaaccepted

Gli utenti chiedevano di poter dichiarare titolo, autore, anno e note dell'opera nel certificato.

Decisione: Campi facoltativi normalizzati in forma canonica (cleanMeta: whitespace collassato, solo WinAnsi, tetti di lunghezza) e accodati al messaggio firmato (hmacMessage): immutabili dopo l'emissione. Senza metadati il messaggio coincide con la sola attestazione, per compatibilità coi certificati pre-1.6.0. /api/verify richiede gli stessi metadati per una firma valida.

Conseguenze: Fix collaterale: il box ATTESTAZIONE (32.6pt) troncava da sempre firma HMAC, emittente e versione motore — ora visibili nel blocco "Dettagli tecnici". La prima collocazione dei dati dichiarati (1.6.0) era poco leggibile (fine print), corretta poco dopo (1.6.2) in un blocco separato e leggibile.

ADR-P8

P7 — Hardening dopo audit esterno: falsificabilità di /api/cert-pdfaccepted

Un audit black-box esterno ha scoperto che /api/cert-pdf non aveva autenticazione e si fidava del JSON inviato dal client, permettendo di far firmare certificati con hash falsi e date retrodatate.

Decisione: Introdotto un token HMAC emesso da /api/hash e verificato da /api/cert-pdf (rifiuta hash/timestamp incoerenti con 400, token invalido con 403, segreto assente con 503); tetto 100MB sulle opere; messaggi d'errore generici; HMAC_SECRET impostato come secret Cloudflare (prima era assente, l'HMAC era inattivo); rate limiting nativo per-IP (RL_CERT, RL_API); anti-bot Turnstile — prima versione solo su "Scarica PDF", poi spostata su "Genera attestazione" per chiudere il buco per cui l'attestazione (incluso il .txt) si otteneva senza alcuna challenge.

Conseguenze: Momento fondativo dell'attuale modello di sicurezza (vedi CTL-hmac-signing, CTL-rate-limiting, CTL-turnstile-antibot); authweb non ha richiesto modifiche di codice perché già round-trippava attestazione+hmac da /api/hash a /api/cert-pdf.

ADR-P7

P6 — Cambio di dominio pubblico a attestazione.spaziogenesi.orgaccepted

Il servizio era pubblicato sotto imgauthweb.spaziogenesi.org, un nome meno chiaro per gli utenti finali.

Decisione: Rinominato il CNAME GitHub Pages, aggiornato ALLOWED_ORIGIN in imgauth, aggiornati QR e footer del certificato verso il nuovo dominio; documentazione (CLAUDE.md, sito) allineata nello stesso giro.

Conseguenze: Nessun redirect server necessario (nessun certificato era ancora stato emesso col vecchio dominio); URL utente-finale unificate.

ADR-P6

P5 — Versioning esplicito per componente e supporto a qualunque formatoaccepted

Nessuna versione era visibile all'utente; authweb accettava solo file image/*, mentre l'hash SHA-256 è indipendente dal tipo di file.

Decisione: Introdotto SemVer esplicito per ciascun componente (package.json per imgauth, APP_VERSION per authweb e authart), reso visibile in /ping, nel footer e nell'health-check; rimosso il vincolo image/* in authweb.

Conseguenze: Tracciabilità delle versioni in produzione; il servizio accetta opere di qualunque formato, con anteprima a miniatura per le immagini e icona con estensione per gli altri tipi.

ADR-P5

P4 — QR dinamico e footer del certificato generati a runtimeaccepted

Il template PDF aveva un QR statico e un footer con testi non corrispondenti al servizio reale (indirizzo e URL sbagliati).

Decisione: QR generato a runtime con la libreria uqr (puntando all'URL di verifica con l'hash); footer (indirizzo, URL) disegnato a runtime, centrato dinamicamente per larghezza del font Times Roman a 7pt; il content stream del template ripulito dai testi glyph-encoded del vecchio footer; authweb legge ?hash= dall'URL per precompilare il campo di verifica.

Conseguenze: Certificato coerente col servizio reale; testo del footer copiabile (vettoriale, non raster).

ADR-P4

P3 — Pulizia delle dipendenze e conferma delle scelte infrastrutturaliaccepted

Revisione di qualità del codice di authart e delle scelte infrastrutturali ereditate dalle fasi precedenti.

Decisione: Rimossa la dipendenza inutilizzata psycopg2-binary da authart, aggiunto gunicorn come server WSGI (già attivo come Startup command Azure); confermato che sgart.azurewebsites.net resta un dominio interno (nessun dominio custom necessario) e che Matomo resta lo strumento di analytics per scelta.

Conseguenze: Codebase più pulita; nessuna azione infrastrutturale ulteriore su domini/analytics richiesta in questa fase.

ADR-P3

P2 — Prima cintura di sicurezza architetturaleaccepted

Dopo il cablaggio end-to-end (ADR-P1), mancavano difese di base tra i tre componenti del sistema.

Decisione: CORS di imgauth ristretto al dominio dell'interfaccia utente; /api/verify esteso a controllare anche la firma HMAC, non solo la corrispondenza dell'hash; l'endpoint /sign di authart reso raggiungibile solo con l'header X-Sign-Secret condiviso tra i due componenti.

Conseguenze: Base su cui più tardi si sono costruiti i controlli CTL-cors-restricted e CTL-hmac-signing.

ADR-P2

P1 — Collegamento end-to-end tra imgauth e authartaccepted

Il flusso hash → certificato → firma non era ancora cablato end-to-end: imgauth non chiamava davvero authart per firmare i PDF.

Decisione: Cablata la chiamata server-to-server da /api/cert-pdf all'endpoint /sign di authart; il certificato p12 stoccato come P12_BASE64 nelle App Settings di Azure, decodificato in un file temporaneo all'avvio del processo.

Conseguenze: Primo PDF firmato generato e testato con successo in produzione.

ADR-P1

P0 — Fix di sicurezza immediatiaccepted

L'audit iniziale del progetto ha rilevato una password P12 hardcoded nel codice di authart e authweb ancora puntato al worker di anteprima invece che a quello di produzione.

Decisione: Spostata P12_PASSWORD in variabile d'ambiente e ruotato il certificato p12 (rimosso anche dalla storia git); corrette le URL in authweb verso il worker di produzione (https://imgauth.spaziogenesi.org).

Conseguenze: Nessun segreto in chiaro nel codice da questo punto in poi; authweb funzionante contro l'ambiente reale.

ADR-P0