IT·EN

Genesis Trust Framework

Attestazione delle opere digitali

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

Trasparenza97
Integrità *89
Tracciabilità100
Documentazione100
Automazione57
Audit *100
Conservazione *100
Riproducibilità97
Privacy *100
Governance *78
Saldo92/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 (tutte le fasi che riguardano l'attestazione sono completate — vedi https://attestazione.trust.spaziogenesi.org/devops/ per la tabella completa): ogni pull request sul repo pubblico imgauth fa girare automaticamente check di sintassi/contratto/build (tab Actions, workflow "CI") più l'analisi statica CodeQL (workflow separato, stesso trigger su push/PR — vedi CTL-static-analysis); 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. Questo controllo copre imgauth (motore) e authweb (interfaccia).

  • 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. **Aggiornamento 2026-08-19 (P54/F8)**: la protezione base (no force-push, no cancellazione branch) è stata estesa da questi tre repo ad `attest-mcp`, `attest-bot`, `attest-mcp-remote` — verificato con `curl` che tutti e sei ora hanno la stessa configurazione. **Nessuna PR obbligatoria su nessuno dei sei**: il gestore, ririchiesto esplicitamente vista la scelta del 13/08, ha confermato di voler mantenere il push diretto su main. Gli alert Scorecard `BranchProtectionID`/`CodeReviewID` restano quindi aperti per scelta dichiarata su tutti e sei i repo, non solo sui tre originari.

  • 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).

  • CTL-best-practices-badge attivo Badge OpenSSF Best Practices — livello passing su attest-mcp
    come verificare

    Senza alcuna credenziale: `curl https://www.bestpractices.dev/projects/14159.json` (API pubblica del progetto OpenSSF Best Practices Badge, ex CII Best Practices) restituisce `"badge_level":"passing"` e `"achieve_passing_status":"Met"`. Il badge stesso è embeddabile da `https://www.bestpractices.dev/projects/14159/badge` (SVG pubblico, aggiornato dal servizio) ed è visibile nel README del repo: `https://github.com/SPAZIO-GENESI/attest-mcp#readme`. Ottenuto il 2026-08-20 (`first_achieved_passing_at`), durante il piano P54, come seguito diretto del triage CodeQL/Scorecard (vedi `ADR-P54`): la prima candidatura possibile era `imgauth`, scartata perché priva di una suite di test automatica — criterio obbligatorio del badge. Il candidato corretto, `attest-mcp`, aveva già 11 test reali. Durante la compilazione, tre criteri erano stati classificati per errore come "SHOULD, non bloccante" in fase di preparazione, quando in realtà sono "MUST": `report_responses`, `vulnerability_report_response` (risposta entro 14 giorni alle segnalazioni — soddisfatti per assenza di casi, nessuna segnalazione mai ricevuta) e `warnings`/`warnings_fixed`/ `test_policy` (questi ultimi hanno richiesto un fix reale, non solo testuale: aggiunto ESLint — `eslint.config.js`, zero errori — e una riga di policy sui test nel README). L'ultimo blocco residuo (`enhancement_responses`) è stato trovato incrociando il file ufficiale dei criteri del progetto BadgeApp (`github.com/coreinfrastructure/best-practices-badge`) con lo stato reale via API, non per tentativi.

  • CTL-cloudflare-access-admin attivo Cloudflare Access (Zero Trust) davanti al pannello admin credenziali agente
    come verificare

    Una richiesta non autenticata al pannello admin riceve un redirect al login Cloudflare Access, prima ancora di raggiungere il Worker e la sua protezione applicativa. Realizza il rafforzamento previsto in ADR-P21-admin — vedi ADR-P21-admin-cfaccess.

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

    Apri il pannello /admin (sezione Convenzioni), crea una convenzione di test (dominio, pool mensile, tetto individuale, date). Con un'email verificata OAuth su quel dominio, GET /developer/keys → chiave emessa con quota = tetto individuale ed expires_at = fine convenzione. Attestazioni da quella chiave rispondono con fascia:'convenzione' finché pool e tetto non sono esauriti; superata una delle due soglie, fascia degrada a 'base' con fascia_motivo esplicito (mai un errore/blocco) e nessuna riga aggiuntiva compare in convention_attestations. Il report per membro (/admin/api/conventions/<id>/report) è visibile solo nel pannello del gestore — verso l'ente va condiviso in forma aggregata. Verificato in produzione il 2026-07-12 con la prima convenzione reale (dominio spaziogenesi.org): login OAuth reale, chiave taggata correttamente, attestazione con fascia:'convenzione', riga di log corretta. Dal 2026-07-12 la STESSA contabilità (accountConventionUsage) serve anche il canale voucher dal sito (via='site', nessuna chiave coinvolta) — vedi CTL-site-voucher-auth per il meccanismo di autenticazione via voucher, non ancora verificato con un login reale in produzione a differenza di questo controllo.

  • CTL-dnssec attivo DNSSEC attivo sulla zona spaziogenesi.org, con catena di fiducia chiusa fino al registro .org
    come verificare

    Tre controlli, tutti eseguibili da chiunque senza credenziali. (1) Il registro `.org` deve dichiarare la delega firmata: interrogando `https://rdap.publicinterestregistry.org/rdap/domain/spaziogenesi.org` il campo `secureDNS.delegationSigned` deve essere `true`, con `dsData` che riporta `keyTag 2371, algorithm 13 (ECDSAP256SHA256), digestType 2 (SHA-256)`. (2) Il record DS deve essere visibile via DNS e la firma deve validare: una query per il tipo `DS` su `spaziogenesi.org` presso un resolver validante (8.8.8.8, 1.1.1.1) deve restituire il record **e** il flag `AD` (Authenticated Data) a `true`. Lo stesso `AD=true` deve comparire sui nomi proxati, in particolare sul dominio di verifica `attestazione.spaziogenesi.org` e sui record `MX` del dominio. (3) La zona deve essere effettivamente firmata: una query con il bit DNSSEC attivo (`do=1`) deve restituire una `RRSIG` per ogni insieme di record, con `signer` uguale a `spaziogenesi.org.`, algoritmo 13, key tag della ZSK pubblicata, e una finestra di validità che comprende il momento del controllo. ⚠️ Atteso e non un difetto: `AD=false` su `trust.spaziogenesi.org` e `attestazione.trust.spaziogenesi.org`. Sono in modalità DNS-only e il loro CNAME — **firmato da noi** — punta a `spazio-genesi.github.io`, zona priva di DS e quindi non firmata: l'autenticazione non si eredita per dati che vivono in una zona non firmata. Vedi il residuo dichiarato in RSK-dns-hijacking. ⚠️ Da non confondere: la presenza di record `DNSKEY` in risposta a una query **non** prova che la zona sia firmata — il provider restituisce le chiavi condivise della propria infrastruttura anche per zone non firmate. La prova è la `RRSIG` più il `DS` al genitore.

  • CTL-site-voucher-auth attivo Voucher stateless firmato per l'accesso email dal sito ("attesta con la tua email")
    come verificare

    Da attestazione.spaziogenesi.org, scheda Attesta → "Hai una convenzione? Accedi con la tua email istituzionale" → login OAuth reale (Google, Microsoft o LinkedIn) → redirect a attestazione.spaziogenesi.org/#sgv=<voucher>. Verificare: il fragment sparisce subito dall'URL (history.replaceState), il voucher non compare in alcuna richiesta di rete verso il server (resta solo in sessionStorage), il banner mostra l'email e se una convenzione è riconosciuta. Attestare un file: nessun widget Turnstile richiesto, risposta di /api/hash con fascia:'convenzione' (o 'base' con fascia_motivo se pool/tetto sono esauriti). Un voucher manomesso o oltre le 8 ore restituisce 403 {error:"voucher_scaduto"}; una convenzione disattivata dopo l'emissione del voucher azzera il vantaggio al successivo uso (il voucher non è mai la fonte di verità sullo stato della convenzione). Il bypass riguarda SOLO il widget Turnstile: HMAC, timestamp server e rate-limit per-IP restano invariati, stesso principio già verificato per CTL-agent-access. ✅ Verificato in locale (wrangler dev, D1 reale, voucher emesso da uno script di test che replica la firma del server — EVD-site-voucher-local-test) e poi con un **login OAuth reale in produzione dal gestore** (2026-07-12, EVD-site-voucher-live-test): attestazione e certificato PDF generati dal sito e riconosciuti in convenzione. Durante quel primo uso reale sono emersi due difetti non coperti dai test automatizzati (bottoni provider invisibili, pannello /admin senza auto-refresh), corretti lo stesso giorno — vedi ADR-P25-site-voucher. Promosso da `draft` ad `active`, stesso criterio già applicato a CTL-cicd-pipeline e CTL-convention-accounting.

  • CTL-tdm-reservation attivo Riserva dei diritti sull'addestramento AI, dichiarata e verificabile
    come verificare

    Attesta un'opera scegliendo "Non acconsento" nel menu facoltativo "Riserva dei diritti sull'addestramento AI", accanto ai dati dichiarati. Il certificato PDF e la pagina /c/<hash> riportano la scelta; GET https://imgauth.spaziogenesi.org/api/rights?hash=<impronta>; la restituisce pubblicamente (CORS aperto), senza bisogno del file né del certificato. POST /api/verify con un valore diverso da quello dichiarato risponde hmac_valido:false — la dichiarazione è vincolata alla stessa firma HMAC dell'attestazione, non modificabile dopo l'emissione.

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: dal 2026-08-17 (P49, ADR-P49) la costruzione del bundle è un pulsante GitHub (.github/workflows/anchor-monthly.yml, workflow_dispatch), non più un comando da terminale. generators/anchor-monthly.mjs concatena i manifest.json degli snapshot settimanali non ancora coperti in un bundle deterministico (committato in snapshots/anchors/) e rifiuta di sovrascriverlo se esiste già per lo stesso mese; un umano scarica il file dal riepilogo del run, lo trascina su attestazione.spaziogenesi.org, genera l'attestazione e scarica il PDF (solo /api/cert-pdf innesca l'ancoraggio OpenTimestamps reale — resta l'unico passo umano per scelta, non per limite tecnico). 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 attivo 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) Il meccanismo di successione stesso è oggi collaudato con dati reali, non solo scritto: un secondo referente tecnico ha ricevuto e usato un accesso operativo verificato con comando (Azure, `EVD-azure-succession-access`) e ha completato da solo, senza aiuto, il recupero dei segreti critici dal caveau esterno (`EVD-succession-drill`). Entrambe le evidenze sono interne (stesso principio di `CTL-secrets-escrow`: descrivere i dettagli operativi del recupero riduce sicurezza senza aggiungere fiducia verificabile da terzi), quindi non riproducibili da un terzo senza credenziali — ciò che è pubblicamente verificabile è il fatto, dichiarato qui, che il controllo è passato ad "active" solo dopo un collaudo reale, non alla scrittura del documento. La procedura scritta stessa (piano-2026/umano/procedura-successione.md, repo privato img-auth-hub) resta un documento interno: non ancora deliberata né pubblicata dall'organo competente. La pubblicazione del documento è un passo organizzativo successivo, non una condizione per il collaudo tecnico già avvenuto.

  • CTL-permanent-archival bozza Conservazione permanente del codice, del whitepaper e delle pagine pubbliche fuori dal perimetro dell'ente (Software Heritage + Zenodo + Internet Archive)
    come verificare

    Tre archivi indipendenti, tutti verificabili da chiunque senza alcuna credenziale, coprono tre cose diverse: il **codice sorgente** dei repository pubblici del sistema di attestazione (imgauth, imgauthweb, autart-signer, gtf, attest-mcp, attest-mcp-remote, attest-action, attest-bot) su Software Heritage; il **whitepaper v1.0 e i bundle mensili di evidenze del registro** su Zenodo, con DOI citabile; lo **stato pubblico** di trust.spaziogenesi.org e attestazione.spaziogenesi.org su Internet Archive — così che ciò che il servizio dichiarava, e con cosa, in una certa data resti dimostrabile anche a chi non si fida di questo registro. Software Heritage: `GET https://archive.softwareheritage.org/api/1/resolve/<SWHID>/` risolve ciascuno degli identificatori auto-verificanti (uno per repository) elencati in EVD-swh-archival-verify — nessun account richiesto. Lo SWHID è calcolato dal contenuto, non assegnato da un'autorità: chiunque può clonare il repository e verificare che il digest corrisponda. Zenodo: `GET https://doi.org/10.5281/zenodo.22031189` risolve al whitepaper v1.0 (stesso file, byte per byte, dell'impronta SHA-256 già attestata — l'impronta prova l'integrità, il DOI garantisce la reperibilità, distinzione esplicita in whitepaper.html §12); `GET https://doi.org/10.5281/zenodo.22031193` risolve ai bundle mensili del registro (2026-07, 2026-08 al momento del deposito — aggiornati annualmente, PRC-zenodo-archival-refresh). Nessuna delle due pagine contiene dati personali o impronte di opere attestate: solo hash di evidenze operative, verificato record per record prima del deposito (EVD-zenodo-deposit-verify). Internet Archive: le due catture elencate in EVD-ia-capture-verify sono pagine pubbliche ordinarie su web.archive.org, apribili da qualunque browser.

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.

  • CTL-secrets-escrow attivo Recupero possibile dei segreti critici senza dipendere dalla sola memoria del maintainer

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://attestazione.trust.spaziogenesi.org (il Trust Center dell'attestazione, dal 16 agosto 2026 su questo sottodominio — la radice trust.spaziogenesi.org ospita ora la pagina del framework), 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.

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

    Apri il pannello /admin (sezione Convenzioni), crea una convenzione di test (dominio, pool mensile, tetto individuale, date). Con un'email verificata OAuth su quel dominio, GET /developer/keys → chiave emessa con quota = tetto individuale ed expires_at = fine convenzione. Attestazioni da quella chiave rispondono con fascia:'convenzione' finché pool e tetto non sono esauriti; superata una delle due soglie, fascia degrada a 'base' con fascia_motivo esplicito (mai un errore/blocco) e nessuna riga aggiuntiva compare in convention_attestations. Il report per membro (/admin/api/conventions/<id>/report) è visibile solo nel pannello del gestore — verso l'ente va condiviso in forma aggregata. Verificato in produzione il 2026-07-12 con la prima convenzione reale (dominio spaziogenesi.org): login OAuth reale, chiave taggata correttamente, attestazione con fascia:'convenzione', riga di log corretta. Dal 2026-07-12 la STESSA contabilità (accountConventionUsage) serve anche il canale voucher dal sito (via='site', nessuna chiave coinvolta) — vedi CTL-site-voucher-auth per il meccanismo di autenticazione via voucher, non ancora verificato con un login reale in produzione a differenza di questo controllo.

  • CTL-site-voucher-auth attivo Voucher stateless firmato per l'accesso email dal sito ("attesta con la tua email")
    come verificare

    Da attestazione.spaziogenesi.org, scheda Attesta → "Hai una convenzione? Accedi con la tua email istituzionale" → login OAuth reale (Google, Microsoft o LinkedIn) → redirect a attestazione.spaziogenesi.org/#sgv=<voucher>. Verificare: il fragment sparisce subito dall'URL (history.replaceState), il voucher non compare in alcuna richiesta di rete verso il server (resta solo in sessionStorage), il banner mostra l'email e se una convenzione è riconosciuta. Attestare un file: nessun widget Turnstile richiesto, risposta di /api/hash con fascia:'convenzione' (o 'base' con fascia_motivo se pool/tetto sono esauriti). Un voucher manomesso o oltre le 8 ore restituisce 403 {error:"voucher_scaduto"}; una convenzione disattivata dopo l'emissione del voucher azzera il vantaggio al successivo uso (il voucher non è mai la fonte di verità sullo stato della convenzione). Il bypass riguarda SOLO il widget Turnstile: HMAC, timestamp server e rate-limit per-IP restano invariati, stesso principio già verificato per CTL-agent-access. ✅ Verificato in locale (wrangler dev, D1 reale, voucher emesso da uno script di test che replica la firma del server — EVD-site-voucher-local-test) e poi con un **login OAuth reale in produzione dal gestore** (2026-07-12, EVD-site-voucher-live-test): attestazione e certificato PDF generati dal sito e riconosciuti in convenzione. Durante quel primo uso reale sono emersi due difetti non coperti dai test automatizzati (bottoni provider invisibili, pannello /admin senza auto-refresh), corretti lo stesso giorno — vedi ADR-P25-site-voucher. Promosso da `draft` ad `active`, stesso criterio già applicato a CTL-cicd-pipeline e CTL-convention-accounting.

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.

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

    Apri il pannello /admin (sezione Convenzioni), crea una convenzione di test (dominio, pool mensile, tetto individuale, date). Con un'email verificata OAuth su quel dominio, GET /developer/keys → chiave emessa con quota = tetto individuale ed expires_at = fine convenzione. Attestazioni da quella chiave rispondono con fascia:'convenzione' finché pool e tetto non sono esauriti; superata una delle due soglie, fascia degrada a 'base' con fascia_motivo esplicito (mai un errore/blocco) e nessuna riga aggiuntiva compare in convention_attestations. Il report per membro (/admin/api/conventions/<id>/report) è visibile solo nel pannello del gestore — verso l'ente va condiviso in forma aggregata. Verificato in produzione il 2026-07-12 con la prima convenzione reale (dominio spaziogenesi.org): login OAuth reale, chiave taggata correttamente, attestazione con fascia:'convenzione', riga di log corretta. Dal 2026-07-12 la STESSA contabilità (accountConventionUsage) serve anche il canale voucher dal sito (via='site', nessuna chiave coinvolta) — vedi CTL-site-voucher-auth per il meccanismo di autenticazione via voucher, non ancora verificato con un login reale in produzione a differenza di questo controllo.

  • CTL-site-voucher-auth attivo Voucher stateless firmato per l'accesso email dal sito ("attesta con la tua email")
    come verificare

    Da attestazione.spaziogenesi.org, scheda Attesta → "Hai una convenzione? Accedi con la tua email istituzionale" → login OAuth reale (Google, Microsoft o LinkedIn) → redirect a attestazione.spaziogenesi.org/#sgv=<voucher>. Verificare: il fragment sparisce subito dall'URL (history.replaceState), il voucher non compare in alcuna richiesta di rete verso il server (resta solo in sessionStorage), il banner mostra l'email e se una convenzione è riconosciuta. Attestare un file: nessun widget Turnstile richiesto, risposta di /api/hash con fascia:'convenzione' (o 'base' con fascia_motivo se pool/tetto sono esauriti). Un voucher manomesso o oltre le 8 ore restituisce 403 {error:"voucher_scaduto"}; una convenzione disattivata dopo l'emissione del voucher azzera il vantaggio al successivo uso (il voucher non è mai la fonte di verità sullo stato della convenzione). Il bypass riguarda SOLO il widget Turnstile: HMAC, timestamp server e rate-limit per-IP restano invariati, stesso principio già verificato per CTL-agent-access. ✅ Verificato in locale (wrangler dev, D1 reale, voucher emesso da uno script di test che replica la firma del server — EVD-site-voucher-local-test) e poi con un **login OAuth reale in produzione dal gestore** (2026-07-12, EVD-site-voucher-live-test): attestazione e certificato PDF generati dal sito e riconosciuti in convenzione. Durante quel primo uso reale sono emersi due difetti non coperti dai test automatizzati (bottoni provider invisibili, pannello /admin senza auto-refresh), corretti lo stesso giorno — vedi ADR-P25-site-voucher. Promosso da `draft` ad `active`, stesso criterio già applicato a CTL-cicd-pipeline e CTL-convention-accounting.

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.

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

    Apri il pannello /admin (sezione Convenzioni), crea una convenzione di test (dominio, pool mensile, tetto individuale, date). Con un'email verificata OAuth su quel dominio, GET /developer/keys → chiave emessa con quota = tetto individuale ed expires_at = fine convenzione. Attestazioni da quella chiave rispondono con fascia:'convenzione' finché pool e tetto non sono esauriti; superata una delle due soglie, fascia degrada a 'base' con fascia_motivo esplicito (mai un errore/blocco) e nessuna riga aggiuntiva compare in convention_attestations. Il report per membro (/admin/api/conventions/<id>/report) è visibile solo nel pannello del gestore — verso l'ente va condiviso in forma aggregata. Verificato in produzione il 2026-07-12 con la prima convenzione reale (dominio spaziogenesi.org): login OAuth reale, chiave taggata correttamente, attestazione con fascia:'convenzione', riga di log corretta. Dal 2026-07-12 la STESSA contabilità (accountConventionUsage) serve anche il canale voucher dal sito (via='site', nessuna chiave coinvolta) — vedi CTL-site-voucher-auth per il meccanismo di autenticazione via voucher, non ancora verificato con un login reale in produzione a differenza di questo controllo.

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à: low impatto: medium

Dirottamento o falsificazione delle risposte DNS del dominio di verifica

Mitigato da

  • CTL-dnssec attivo DNSSEC attivo sulla zona spaziogenesi.org, con catena di fiducia chiusa fino al registro .org
  • CTL-edge-security-headers attivo Header di sicurezza HTTP (HSTS, CSP, Permissions-Policy) all'edge Cloudflare

RSK-dns-hijacking

probabilità: low impatto: high

Indisponibilità temporanea o permanente dell'unico maintainer tecnico

Mitigato da

  • CTL-maintainer-succession attivo 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

Scadenza del token di pubblicazione del Trust Center di tenant senza rinnovo

Mitigato da

  • nessuna mitigazione collegata ancora

RSK-tenant-publish-token-expiry

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

P55 — Evoluzione L0-L3: onestà del claim e riserva dei diritti registrata (F1 + F3)accepted

Specifica del committente (09/2026) per evolvere il servizio verso quattro livelli di garanzia crescente (L0 sigillo qualificato, L1 esistente, L2 contrassegno invisibile, L3 riserva dei diritti/persistenza). Verifiche tecniche preliminari (V1) hanno trovato che la marca temporale in uso (DigiCert AATL) non è qualificata eIDAS e che il fail-open del firmatario produceva, in silenzio, certificati più deboli di quanto il PDF lasciasse intendere — un rischio di overclaim più urgente di molto altro nella specifica. Il piano completo, con le verifiche e le migliorie proposte, è in `img-auth-hub/P55-DESIGN-evoluzione-L0-L3.md`.

Decisione: **F1 (onestà del claim, chiusa 16/09/2026)**: rimossa la formula "data certa" dove prometteva un effetto giuridico non ancora vero; il PDF e la pagina /c/<hash> dichiarano ora le garanzie realmente ottenute da QUELLA emissione (firma HMAC, PAdES, marca temporale con livello, ancoraggio Bitcoin) invece di una garanzia generica del servizio; il fail-open del firmatario resta come politica ma smette di essere muto. **F3 (riserva dei diritti registrata lato server, chiusa 16/09/2026)**: identificata come il differenziatore principale del servizio rispetto ai soli metadati editabili (IPTC, C2PA): un campo `riserva` facoltativo (vocabolario CAWG training-mining: notAllowed/constrained/allowed), vincolato alla stessa firma HMAC dei metadati dichiarati, verificabile pubblicamente da chiunque conosca l'impronta (`GET /api/rights`) anche quando il file che circola ha perso ogni metadato — è il caso che i metadati embedded non reggono mai. **Bivio architetturale risolto (M5 della specifica)**: L2/L3 della specifica del committente presupponevano che il servizio ricevesse i byte del file per scriverci un manifest o un watermark — in contraddizione diretta con l'invariante 4 (il file non lascia il dispositivo dal sito). Deciso: la parte server-side (F3, sopra) non richiede mai il file: basta l'impronta, già nota al servizio. La scrittura del contrassegno nel file stesso (F5, ridimensionata) si farà nel browser, mai sul server. Il contrassegno invisibile watermark (F6) è stato **sospeso**: non protegge da solo (serve monitoraggio continuo, fuori budget), il mercato è presidiato da specialisti con anni di vantaggio, e integrarne uno esterno richiederebbe di inviargli il file — la stessa contraddizione con l'invariante 4, spostata a un fornitore terzo invece che risolta.

Conseguenze: Contratto additivo su tutta la linea: `hmacMessage` senza `riserva` resta bit-identico a prima della 1.37.0 (test di non-regressione scritto prima del codice), i certificati emessi prima di F1/F3 restano verificabili senza eccezioni. Nessun costo ricorrente nuovo. In produzione dal 16/09/2026: authart 1.3.0 (invariato), imgauth 1.37.0, authweb 1.36.0 — collaudo automatico su staging e prova dal vivo del gestore. Un falso allarme nel collaudo (certificato senza la riga della riserva) è risultato essere cache del browser su una pagina aperta prima del deploy, non un difetto: annotato come trappola operativa (`img-auth-hub/docs/TRAPPOLE.md`), non un rischio di questo registro. Restano aperte, fuori da questa decisione: F2 (marca temporale qualificata, bloccata da una spesa da deliberare), F7/L0 (sigillo qualificato, blocca su V3/V4 esterne), F5 ridimensionata (riserva scritta in JPEG/PNG nel browser). F6 resta sospesa fino a un cambio di fatti esplicitamente scritto nel piano (domanda pagante ricorrente, un finanziamento che copra il monitoraggio, o un'integrazione che non trasmetta il file).

ADR-P55

P54 — seguito: badge OpenSSF ottenuto e reso visibile sul Trust Centeraccepted

`ADR-P54` aveva lasciato il badge OpenSSF Best Practices non sottomesso (richiedeva login GitHub del gestore). Compilato il modulo per `attest-mcp`: tre criteri erano stati classificati per errore come "SHOULD" quando sono "MUST" (`report_responses`, `vulnerability_report_response`, `warnings`/`test_policy` — questi ultimi due hanno richiesto un fix reale nel codice, non solo testuale). `attest-mcp` ha raggiunto il livello "passing" (`first_achieved_passing_at: 2026-08-20T08:21:35Z`).

Decisione: Terzo `CTL` della famiglia iniziata da `ADR-A3-parte4` (`CTL-best-practices-badge`), stesso schema: `verify_howto` senza credenziali via l'API pubblica di bestpractices.dev, collegato a `REQ-27001-inspiration-01`, con `EVD`/`IMP` dedicati. Promosso ad `active` lo stesso giorno su decisione esplicita del gestore. Verificando la visibilità pubblica del controllo, corretti due difetti preesistenti nel generatore del sito: un bug in `linkifyUrls` che includeva il backtick di chiusura negli `href` (5 link rotti sulla pagina) e 9 legami controllo↔requisito mai collegati (`satisfies` dichiarato nel controllo ma assente dal `satisfied_by` del requisito) — 6 controlli già `active`, mai comparsi pubblicamente per lo stesso motivo. Zero orfani residui dopo il fix.

Conseguenze: Il gap sulla pagina `/sicurezza/` di authweb (nessuna menzione di Scorecard/CodeQL/badge) resta segnalato ma non affrontato: lavoro più ampio su un secondo repo, rimandato dal gestore.

ADR-P54-badge

A5 — Conservazione permanente del codice e delle pagine pubbliche fuori dal perimetro dell'enteaccepted

`MET-conservazione` copriva solo backup dentro il perimetro di controllo dell'ente (R2 primario + backup offsite B2, ADR-GTF-013): entrambe le copie cessano di esistere se cessa l'ente. La fascia Professionale vende una garanzia di custodia a cinque anni la cui unica garanzia oggi era la continuità di un'organizzazione di volontari. `design/A5-conservazione-permanente.md` proponeva tre archivi indipendenti — Software Heritage (codice), Zenodo (whitepaper + snapshot del registro, DOI), Internet Archive (pagine pubbliche) — più un controllo e un processo ricorrente nel registro. Il documento assumeva, testualmente, l'archiviazione di "ciascun repository pubblico dell'organizzazione". L'organizzazione GitHub SPAZIO-GENESI conta però una quarantina di repository pubblici, la maggior parte estranei al sistema di attestazione: siti-portfolio personali di singoli artisti (repository nominati per nome e cognome), repository di test/demo, e un mirror generato (`trust-attestazione`, output pubblicato dal registro `gtf`, non codice sorgente). Archiviare su Software Heritage è un'operazione pubblica e di fatto permanente: farlo indiscriminatamente anche sui repository-portfolio di persone nominate è una decisione di merito, non di scope tecnico, e non spettava a questa sessione prenderla da sola (§8 di questo CLAUDE.md di sessione).

Decisione: Chiesto al gestore quale perimetro usare; risposta: solo gli otto repository del sistema di attestazione elencati nella tabella "I repository" di `img-auth-hub/CLAUDE.md` (imgauth, imgauthweb, autart-signer, gtf, attest-mcp, attest-mcp-remote, attest-action, attest-bot). Eseguite le Parti 1 e 3 del design doc, entrambe interamente senza credenziali: **Software Heritage**: richiesta di archiviazione (`POST .../origin/save/git/url/<url>/`) per tutti e otto, tutte concluse con `visit_status: full`; ogni SWHID risultante risolto singolarmente (`GET .../resolve/<SWHID>/`, HTTP 200) prima di essere citato, come richiesto esplicitamente dal design doc ("non citare uno SWHID che non hai risolto tu stesso") — dettaglio in EVD-swh-archival-verify. **Internet Archive**: cattura Save Page Now (`GET web.archive.org/save/<url>`) per trust.spaziogenesi.org e attestazione.spaziogenesi.org, entrambe risolte a HTTP 200 — dettaglio in EVD-ia-capture-verify, inclusa la sorpresa del redirect su `/en/` per il primo dominio (comportamento del crawler di Internet Archive, non del sito: verificato che il dominio risponde 200 senza reindirizzare, con o senza header Accept-Language). Registrato `CTL-permanent-archival` (**draft**) e un unico processo ricorrente `PRC-permanent-archival-refresh` (trimestrale, 90 giorni) che copre entrambe le riarchiviazioni — un solo PRC invece di due, perché sono due azioni-browser da pochi minuti nella stessa sessione trimestrale e moltiplicare i processi ricorrenti per azioni quasi identiche va contro PRN-10. Collegato a `REQ-27037-pres-01` (stesso requisito già usato da B3/CTL-maintainer-succession, coerente: entrambi coprono la conservazione della prova nel tempo, non l'integrità del singolo certificato). **Parte 2 (Zenodo), stesso giorno, seguito.** Inizialmente non eseguita: il design doc la descriveva come bloccata "per l'account umano"; il gestore aveva dichiarato in chat di aver aperto l'account, ma un deposito reale richiede un token di accesso API che l'apertura dell'account da sola non forniva. Chiarito il malinteso, il gestore ha generato un token personale (`deposit:write` + `deposit:actions`) e l'ha dato in chat: usato una sola volta, in un'unica sessione di comandi, mai scritto in un file né in un commit. Prima del deposito, verificato — non assunto — che il PDF scaricato da attestazione.trust.spaziogenesi.org/whitepaper-v1.0.pdf avesse lo stesso SHA-256 già attestato (ADR-P38) e che i due bundle mensili (`snapshots/anchors/2026-07-bundle.json`, `2026-08-bundle.json`) contenessero solo nomi di file ed hash di evidenze operative, nessun dato personale né impronta di opera attestata — vincolo esplicito e irreversibile del design doc ("verifica record per record prima di depositare"). Create due deposizioni in **bozza** via API (whitepaper: publication/technicalnote, licenza CC BY 4.0 già dichiarata nel documento §12; bundle: dataset, licenza MIT già dichiarata in `gtf/LICENSE`) — la pubblicazione, l'unico passo irreversibile, lasciata volutamente al gestore. Pubblicate dal gestore lo stesso giorno; DOI risolti dal vivo dopo la pubblicazione (`GET doi.org/<DOI>` → 302), checksum dei file confermati identici a quelli caricati — dettaglio in EVD-zenodo-deposit-verify. DOI stampato in `whitepaper.html` §12 accanto all'impronta SHA-256, con la distinzione esplicita richiesta dal design doc (l'impronta prova l'integrità, il DOI la reperibilità). Registrato `PRC-zenodo-archival-refresh` (annuale, separato dal PRC trimestrale di Software Heritage/Internet Archive perché la cadenza dichiarata dal design doc per Zenodo è diversa — "anche solo annuale"). `CTL-permanent-archival` aggiornato: ora copre tutti e tre gli archivi. **`MET-conservazione` non modificata**, nemmeno ora che la Parte 2 è completa. Letta la formula reale (`registry/metrics/MET-conservation.yaml`): media di freschezza degli snapshot ed esito dell'ultimo restore-drill — non ha oggi alcun termine per l'archiviazione permanente fuori perimetro. Integrarla resta un'azione futura proposta, non eseguita qui: emendare la formula è una decisione (precedente ADR-GTF-011), non una conseguenza automatica del completamento tecnico di un controllo ancora `draft`.

Conseguenze: `CTL-permanent-archival` nasce e resta `draft`: tutte e tre le sue parti (Software Heritage, Zenodo, Internet Archive) sono ora verificabili senza credenziali, ma la promozione ad `active` resta una decisione del gestore, non tecnica — non presa da questa sessione. Nessuna modifica a `MET-conservazione`: resta un'azione futura esplicitamente aperta, non un'omissione. Fuori scopo: i circa trenta repository pubblici dell'organizzazione non legati al sistema di attestazione — non archiviati, per decisione esplicita del gestore, non per dimenticanza. Se in futuro qualcuno dovrà decidere sulla loro archiviazione, è una decisione separata, non un'estensione automatica di questo controllo.

ADR-A5

P54 — CodeQL: copertura, triage del debito e rifiuto di GitHub Code Qualityaccepted

Valutazione di GitHub Code Quality (prodotto a pagamento, GA dal 20/07/2026, $10/committer/mese sui repo privati) ha portato alla luce che CodeQL era già attivo da A3 (13/08) su cinque repo con 74 alert mai triati, mentre `autart-signer` (il firmatario PKCS#7, unico componente Python del flusso probatorio), `gtf` e `attest-action` non erano coperti. `PRC-security-alert-triage` (cadenza settimanale) non aveva mai girato.

Decisione: **GitHub Code Quality rifiutato**: nessun riconoscimento pubblico (alert solo su tab Security ad accesso ristretto), costo su repo privati fuori dalla licenza Team nonprofit, valore concentrato in commenti di PR su repo dove si lavora quasi sempre a push diretto. **Triage dei 74 alert**: 1 fix reale (ReDoS su input Telegram non fidato in `attest-bot`, PR #4, verifica di equivalenza semantica su 7 casi), 13 dismiss motivati con verifica nel codice (falsi positivi su script diagnostici/build-time o dati mai raggiungibili dal sink segnalato), 60 alert Scorecard raggruppati per categoria. `FuzzingID` dismissato per sempre su decisione esplicita del gestore. **Copertura estesa**: CodeQL Python su `autart-signer` (prima analisi statica della sua storia, verde) e CodeQL+Scorecard su `gtf`/ `attest-action`/`attest-bot`/`attest-mcp-remote`. **`attest-mcp` messo in sicurezza** (Scorecard 3.5/10, il più basso del gruppo): 4 vulnerabilità transitive reali risolte con `npm audit fix` senza toccare i range dichiarati; pin per SHA su tutti i workflow; `upload-artifact`/`download-artifact` allineati a v7/v8 (necessario in coppia per l'hash enforcement di v8) e collaudati con un self-test reale prima di aprire la PR. **Candidato del badge OpenSSF Best Practices corretto**: `imgauth`, indicato inizialmente, non ha una suite di test automatica — criterio obbligatorio del badge. Sostituito con `attest-mcp` (11 test reali). **Branch protection**: estesa la protezione base (niente force-push, niente cancellazione branch) da tre a tutti e sei i repo del flusso, **senza** rendere obbligatoria la revisione via PR — coerente con la scelta già presa il 13/08 di mantenere il push diretto su main. Gli alert Scorecard `BranchProtectionID`/`CodeReviewID` restano quindi aperti per scelta dichiarata su tutti e sei i repo.

Conseguenze: Live: i dismiss di alert (stato mutato via API) e l'estensione della branch protection (verificata via API su tutti e sei i repo). Non ancora live: 7 PR aperte in attesa di merge del gestore (fra cui `autart-signer#11`, che innesca un deploy Azure reale al merge) — il merge non è stato eseguito da questa sessione, bloccato dal classificatore di sicurezza di Claude Code. `CTL-static-analysis`/ `CTL-scorecard-monitoring` non vengono estesi finché quella copertura non è in produzione (stesso principio di `ADR-A3-parte4`). Bozza del badge OpenSSF pronta in `img-auth-hub/p54-f7-badge-attest-mcp.md`, sottomissione non eseguita: richiede il login GitHub del gestore.

ADR-P54

P50 — Scopribilità per agenti AI: dichiarare quello che c'è, e non dichiarare quello che non c'èaccepted

Il servizio pubblica da mesi canali pensati per programmi e agenti AI (due server MCP, una CLI, una GitHub Action, eseguibili standalone, un contratto OpenAPI) ma non lo dichiarava nei luoghi convenzionali dove un agente guarda prima di leggere una pagina HTML: un rilevatore pubblico di "agent readiness" classificava il sito al livello più basso. La causa non era tecnica: mancavano i documenti di presentazione. Due vincoli reali misurati, indipendenti da questo lavoro: la zona è su un piano gratuito (nessuna conversione automatica in Markdown offerta dal provider) e il DNS non è autenticato — DNSSEC non attivo (`delegationSigned: false` sul registro `.org`). ⚠️ Corretta prima di agire una lettura iniziale sbagliata: i record DNSKEY presenti nelle risposte sono le chiavi condivise dell'infrastruttura del provider, restituite anche per zone non firmate — non provano che la zona sia firmata.

Decisione: Pubblicati come file statici i descrittori corrispondenti a capacità realmente esistenti: preferenze AI in `robots.txt`, un catalogo API (RFC 9727), una MCP Server Card, un indice di *agent skills* con due istruzioni operative, un documento sulle credenziali, una mappa dei link, tre strumenti **in sola lettura** via WebMCP (stato, verifica firma, ricerca impronta). **Deliberatamente non pubblicati** quattro descrittori che il rilevatore verifica, perché descriverebbero capacità inesistenti: metadati OAuth/OIDC di discovery (nessun authorization server reale); metadati di risorsa protetta OAuth (richiedono un elenco di issuer inesistente); una A2A Agent Card (qui ci sono strumenti che un agente usa, non un agente conversazionale); la directory Web Bot Auth (il servizio non fa crawling). Criterio applicato, valido oltre questo piano: **un 404 è preferibile a un metadato falso** — un descrittore assente dice "qui non c'è", uno che mente rompe chi vi si affida. Nessuno strumento WebMCP attesta: la challenge anti-bot davanti all'emissione esiste perché l'attestazione sia un gesto umano. Contro l'invecchiamento dei descrittori: l'indice delle skill dichiara lo SHA-256 di ogni istruzione, con guardia CI che fallisce se il testo cambia senza riallineare il digest, ed evidenza settimanale che ricalcola i digest dal vivo.

Conseguenze: Il livello del rilevatore esterno passa da 1 al massimo previsto dalla scala, 11 controlli su 15 soddisfatti. ⚠️ **Rettifica registrata perché istruttiva**: una prima analisi aveva scartato la negoziazione di contenuto Markdown per due motivi rivelatisi infondati — si credeva richiedesse un piano a pagamento (esiste invece una regola di riscrittura all'edge gratuita, attiva solo su `Accept: text/markdown`, che nessun browser invia) e si riteneva il beneficio marginale (vero nel merito, ma quel singolo controllo separava dal livello massimo). Lezione: una soluzione va scartata dopo aver verificato le alternative, non dopo averne immaginate due. Il gemello Markdown è generato dalla pagina con guardia CI che blocca la pubblicazione se diverge; nessun conteggio token dichiarato (sarebbe falso dal primo cambiamento di contenuto). Pubblicati record DNS-AID (SVCB sotto `_agents`): indice d'organizzazione, uno sul dominio del servizio, uno per il server MCP in esercizio. **Nessun record `_a2a`**, coerente con l'assenza dell'Agent Card — verificata attivamente dall'evidenza settimanale, non solo omessa. Scommessa dichiarata: il documento di riferimento è un Internet-Draft individuale non avallato IETF, in scadenza fine novembre 2026 (data annotata in ogni record). Nessun processo ricorrente creato: il rischio peggiore è che i record vengano ignorati. Nessun controllo/rischio/trattamento nuovo nel registro: sono documenti descrittivi, non meccanismi di sicurezza, non raccolgono nulla. Due questioni preesistenti restano aperte: attivare DNSSEC (record DS dal registrar — margine d'errore che renderebbe il dominio irrisolvibile, va eseguito con verifica immediata) e i caratteri tipografici caricati da un fornitore terzo (espone l'IP del visitatore, a differenza delle altre dipendenze terze già auto-ospitate).

ADR-P50

P49 — L'ancoraggio mensile diventa un pulsanteaccepted

`PRC-dogfooding-anchor` (§6.4) è l'unica delle sei manutenzioni ricorrenti del framework che un non tecnico non poteva iniziare, e proprio quella che torna ogni mese — la più frequente dopo il triage settimanale. Il comando che costruisce il pacchetto mensile (`generators/anchor-monthly.mjs`) è però deterministico e senza scelte: legge le fotografie settimanali già committate, le impacchetta, scrive un file, stampa un'impronta. Non decide niente — un lavoro così non ha bisogno di un umano al terminale, ha bisogno di un umano che decida quando farlo e che poi attesti il risultato dal sito, dove serve davvero una persona per la verifica anti-robot.

Decisione: Portata la parte automatica su un pulsante GitHub (`workflow_dispatch` soltanto, niente cron: un pacchetto creato da solo e mai attestato non proverebbe niente, e il segnale di scadenza esiste già nell'avviso settimanale di `CTL-cadence-monitoring`). Il riepilogo del run e un messaggio Telegram mostrano subito impronta, link per scaricare il file e link per attestarlo. `PRC-dogfooding-anchor` passa a `needs_technical: false` e diventa eseguibile da chiunque abbia accesso a GitHub e al servizio, non solo dal gestore. Chiusa prima la trappola più seria del piano: `anchor-monthly.mjs` riscriveva senza chiedere il pacchetto del mese corrente a ogni esecuzione, e ogni riscrittura cambia `generated_at` → cambia l'impronta del file. Con un pulsante alla portata di chiunque, una seconda pressione dopo l'attestazione avrebbe scollegato il certificato dal file (impronta attestata vecchia, pacchetto nel registro nuovo, verifica pubblica rotta) — il collettore settimanale se ne sarebbe accorto, ma solo dopo giorni. Il generatore ora rifiuta di sovrascrivere un pacchetto già esistente per lo stesso periodo (esce con codice 0, non un errore: rieseguire non è uno sbaglio, è cosa farà chi ha perso l'impronta e la vuole rivedere) e ammette una forzatura esplicita `--force`, con avviso, per il solo caso in cui il pacchetto vada rifatto prima di essere stato attestato. Non-obiettivo esplicito, non un limite tecnico: l'attestazione resta un gesto umano. È il punto stesso del controllo Turnstile — un'attestazione emessa da una macchina senza nessuno che la guardi non proverebbe la stessa cosa. Le altre tre manutenzioni tecniche ricorrenti (restore drill, revisione trimestrale, rinnovo di security.txt) restano `needs_technical: true` per lo stesso motivo che vale qui al contrario: richiedono giudizio umano, non solo esecuzione, e non sono candidate a un pulsante.

Conseguenze: Nessun controllo nuovo nel registro: `CTL-dogfooding-anchor` esiste già e non cambia natura, cambia lo strumento con cui il suo passo deterministico viene innescato — restano identici il modello di fiducia (attestazione umana + prova OpenTimestamps verificabile da chiunque) e i criteri con cui il collettore settimanale conferma che il bundle del mese è stato attestato. Nessun indicatore nuovo nello score, coerente con l'assenza di un controllo nuovo. Nessuna modifica a imgauth, imgauthweb o authart: solo repo `gtf`, nessun deploy Cloudflare, nessun segreto nuovo, nessun contratto API toccato. Fuori perimetro, per scelta e non per dimenticanza: l'aggiornamento automatico di `EVD-dogfooding-anchor` con impronta e link del certificato del mese (il collettore già ricalcola l'impronta del bundle più recente e verifica il badge — un secondo pulsante di conferma aggiungerebbe una cosa da ricordare in cambio di poco, da riprendere solo se si vorrà il link permanente di ogni certificato mensile dentro l'evidenza) e un cron che crei il pacchetto da solo (abituerebbe a un file che compare senza che nessuno lo attesti — l'effetto collaterale opposto a quello voluto).

ADR-P49

Un host per tenant, la radice al prodottoaccepted

Un solo host (trust.spaziogenesi.org) serviva due cose diverse: era, fino al 15 agosto 2026, il Trust Center del solo progetto attestazione. Con un secondo tenant in vista la radice non poteva restare identificata con uno solo di essi. GitHub Pages ammette un solo dominio custom per repository, quindi il sottodominio di un tenant non è ottenibile dal solo repo gtf, che deve continuare a servire la radice.

Decisione: La radice (trust.spaziogenesi.org) diventa la pagina del prodotto: cos'è il Genesis Trust Framework, come funziona, un riepilogo dei punteggi dei progetti che lo applicano — pubblicata direttamente da GitHub Pages del repo gtf. Ogni tenant ottiene un sottodominio proprio pubblicato da un repo Pages sottile dedicato, di sola pubblicazione (per l'attestazione, SPAZIO-GENESI/trust-attestazione → attestazione.trust.spaziogenesi.org), alimentato da un git push diretto con un PAT fine-grained scoped su quel solo repo. Il record DNS del sottodominio resta DNS-only, mai proxato su Cloudflare (quarto livello, fuori dalla copertura a un livello dell'Universal SSL). Un insieme di URL pubblici già consumati esternamente (badge.svg, whitepaper-v1.0.pdf, whitepaper.html, devops.html, changelog.html) resta fisicamente sulla radice per non rompere nulla: con GitHub Pages non esistono redirect server-side.

Conseguenze: Un secondo tenant costa un repo Pages sottile e un record DNS in più da mantenere, non una modifica al motore. devops.html e whitepaper.html restano un'incoerenza concettuale dichiarata (contenuto del tenant ospitato sul dominio del prodotto): sanabile in P47, quando l'astrazione sarà corretta dal secondo tenant. Nessun record del registro, nessuna formula, nessun controllo di sicurezza è cambiato: è una decisione di topologia di pubblicazione, il punteggio dell'attestazione resta 91/100 con 10/10 indicatori prima e dopo.

ADR-GTF-015

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 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 aveva emendato la formula di `MET-governance` perché il progetto ha un solo maintainer (la revisione tra pari via PR non si applica). Quella formula misura la robustezza del *processo* di rilascio, non la continuità della *persona* che lo gestisce — due domande diverse, nessuna copre l'altra, e il rischio mancava dal registro. Concreto, non astratto: la fascia Professionale (P27) vende una garanzia contrattuale di custodia a cinque anni sostenuta oggi dalla disponibilità continuativa di una sola persona.

Decisione: Aperti `RSK-maintainer-unavailability` (likelihood medium, impact high — nessun secondo referente tecnico individuato oggi) e `CTL-maintainer-succession` (nasce `draft`, referenzia `piano-2026/umano/procedura-successione.md`, repo privato). Collegato a `REQ-27037-pres-01` (conservazione delle prove nel tempo — la prova sopravvive all'ente — non `REQ-27043-ir-01`, gestione incidente). Ricostruito un inventario delle risorse operative (Cloudflare, Azure, organizzazione GitHub, npm, Stripe, backup offsite, bot Telegram, registrar) leggendo i repo: tre fatti confermati con verifiche dirette read-only (`gh auth status`/`gh api`, `wrangler whoami`, `az account show`), il resto marcato "da confermare col gestore" dove nessun file lo documentava (registrar, Stripe, BotFather). Verificate nel codice, non copiate dalla bozza, le due affermazioni tecniche del §6 della procedura: il file `.ots` prodotto da `imgauth/worker.js` è un `DetachedTimestampFile` standard sul digest reale dell'opera, verificabile con qualunque client OpenTimestamps indipendente; il certificato PDF porta una marca temporale di terza parte reale (`authart/signer_app/app.py`, DigiCert/AATL). Un terzo elemento dato per scontato dalla bozza — un verificatore offline dell'integrità del certificato — è risultato non ancora esistente (voce A4, `da fare`): la procedura lo dichiara esplicitamente come garanzia futura, non presente. Preparato ma non configurato un secondo destinatario dell'allarme Telegram di `CTL-cadence-monitoring` (parametro opzionale `TELEGRAM_CHAT_ID_SECONDARY`, nessun comportamento cambia finché il secret non esiste — chi sarà è decisione del gestore). `CTL-maintainer-succession` non referenzia oggi alcuna `EVD`, deliberatamente: non esiste ancora un'evidenza reale (nessun secondo referente, nessun collaudo del recupero segreti da terzi, procedura non pubblicata) e inventarne una avrebbe violato l'invariante contro i dati non verificati. Lo schema non richiede `evidenced_by` per un controllo `draft`.

Conseguenze: `CTL-maintainer-succession` nasce e resta `draft`: nessuno dei passaggi umani che chiudono la voce (secondo referente, collaudo dell'escrow da terzi, delibera, pubblicazione) è avvenuto. Azioni che restano solo al gestore (dettaglio in `STATO.md`, repo privato): individuare un secondo referente tecnico o dichiarare per iscritto che non esiste; far provare a una persona diversa da sé il recupero dei segreti senza aiuto e registrarne l'esito come `EVD`; compilare le voci "da confermare"; portare la procedura a delibera; pubblicarla; decidere il secondo destinatario dell'allarme. Fuori scopo: modifiche alla gestione reale dei segreti; configurazione del secondo destinatario; delibera; pubblicazione; 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`: dimostra che il file non è cambiato dopo la build, non che sia stato costruito da quel sorgente/commit/workflow. Complemento naturale di ADR-P37 (che aveva scartato Sigstore per le *opere* attestate, categoria sbagliata — qui il dominio è l'opposto: firma di un artefatto software, il caso d'uso proprio di quella tecnologia).

Decisione: Attivato **GitHub Artifact Attestations** (`actions/attest-build-provenance@v4`) sul job `release` di `release-binaries.yml`: firma con identità OIDC effimera i sei binari pubblicati (subject-path `dist/*`, prima della generazione di `SHA256SUMS.txt` così il checksum derivato non viene mai attestato), registrata in transparency log pubblico. Permessi `id-token: write` + `attestations: write` solo sul job `release`. Verificato sul primo tag reale (v0.4.2) e con `gh attestation verify` su un binario scaricato dalla Release pubblica, positivo sul file autentico e negativo su una copia alterata di un byte. **`gh attestation verify` richiede una sessione GitHub autenticata anche su repository pubblici** (exit 4 senza login, confermato). Il dato sottostante è pubblico (`GET api.github.com/.../attestations/<digest>` risponde 200 senza credenziali, verificato con `curl` puro), ma non esisteva una procedura a comando singolo senza `gh` autenticato — limite del client, non del dato, dichiarato esplicitamente nel `verify_howto`. Chiuso lo stesso giorno: `attest-mcp/scripts/verify-provenance.mjs` (PR #2) interroga l'endpoint REST pubblico e verifica il bundle Sigstore con la libreria npm `sigstore` (verificata a sua volta contro Rekor/ Fulcio/TUF pubblici — nessun account in nessun punto della catena). Scelta `sigstore` (libreria) invece di `cosign` (binario Go): progetto interamente Node/Bun, script auto-contenuto con solo `npm install`, stessa libreria con cui GitHub costruisce le attestazioni. Aggiunta come devDependency, non tocca `src/*` né le dipendenze del pacchetto pubblicato. Collaudato sui sei binari del tag v0.4.2: file autentico → verifica riuscita; rinominato → riuscita con avviso (il legame è sul digest in-toto, non sul nome); un byte alterato → fallita. **Nota di sicurezza per chi tocca questo script**: `sigstore.verify()` non lega da sé l'artefatto al subject per un bundle DSSE — accetta bytes estranei come `data` senza far fallire la verifica crittografica (il confronto interno è fra payload e firma, non fra artefatto e payload). Un'implementazione che si fidasse di quel parametro per il legame sarebbe silenziosamente insicura. Lo script fa il legame esplicitamente, confrontando il digest calcolato sul file locale con quello del subject nel predicato in-toto. **Per A2** (firma del codice, non ancora eseguita): l'ordine è vincolante — build → firma → attestazione di provenienza sul binario *firmato* → checksum → Release. Chi esegue A2 deve riposizionare lo step di attestazione esistente dopo la firma, non aggiungerne uno accanto, altrimenti l'attestazione risulterebbe orfana. Collegato a `REQ-27001-inspiration-01` (non `REQ-27037-pres-01`, proposto dal design doc: quel requisito copre la conservazione delle prove delle *opere* attestate, dominio diverso dalla provenienza di un *artefatto software*).

Conseguenze: `CTL-build-provenance` nasce e resta `draft`: la promozione ad `active` è una decisione del gestore, non tecnica. Nessun `RSK` creato per la manomissione della catena di distribuzione dei binari — lasciato come domanda aperta. Fuori scopo: attestazione del pacchetto npm (pubblicato a mano, non da CI); SLSA build level 3; modifiche 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. Rendere il servizio bilingue tocca 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 — l'artefatto probatorio.

Decisione: Scopo MVP: sito italiano, percorso inglese completo per chi arriva dal canale programmabile — sei pagine duplicate sotto `/en/`, le due pagine HTML servite dal Worker, e i messaggi d'errore di `/api/hash` e `/api/cert-pdf` via campo facoltativo `lang` (fallback `Accept-Language`). Fuori scopo: pagine di chi è già dentro (profilo, stato, sicurezza, vetrina, changelog) e lo storico. **Invariante che regge il piano: `lang` non entra mai in `hmacMessage`.** La lingua è resa, non un fatto sull'opera — legarla alla firma invaliderebbe la verifica di tutti i certificati già emessi. Verificato direttamente: la stessa coppia attestazione+HMAC produce il certificato in entrambe le lingue, `/api/verify` risponde `hmac_valido: true` in entrambi i casi, un certificato pre-P41 resta verificabile senza eccezioni. L'informativa privacy inglese è dichiarata **traduzione di cortesia** (l'italiana fa fede): tradurre un testo con effetti giuridici senza revisione legale non lo rende ufficiale, e dirlo è più onesto che lasciarlo intendere. Nessun rilevamento automatico della lingua né redirect: solo un selettore visibile. Guardia CI obbligatoria contro la divergenza fra le due versioni: registro delle coppie con l'impronta SHA-256 del file italiano al momento della traduzione, fallisce se l'italiano cambia senza che l'inglese sia aggiornato. Pagine EN statiche, nessuno step di build. Accettato per l'MVP: le etichette AcroForm del template PDF restano italiane anche nel certificato inglese (tradurle richiederebbe un secondo template da riverificare nelle coordinate) — il certificato EN è quindi misto. QR e URL di verifica invariati per lingua, la pagina si apre nella lingua di chi la visita, non di chi ha emesso.

Conseguenze: Contratto API puramente additivo: senza il campo `lang` ogni risposta, pagina e PDF restano bit-identici a prima — verificato, non dichiarato. Nessun endpoint/dato/trattamento nuovo, nessun rischio o controllo nuovo nel registro: l'informativa è tradotta, non estesa. Rilasciato in due tempi per ragione strutturale: il sito statico pubblica al push, il Worker ha un gate di produzione separato — le pagine inglesi sono andate live prima del motore bilingue, nessuna dipendenza fra le due parti. Deploy del Worker (imgauth 1.33.0) approvato dal gestore come ogni rilascio di produzione. Criterio per estendere lo scopo (altre pagine, altre lingue): il traffico reale su `/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. Aggiornamento 2026-08-16 (P45): trust.spaziogenesi.org non pubblica più il Trust Center di un progetto ma la pagina del framework (la radice); il Trust Center dell'attestazione è ora su attestazione.trust.spaziogenesi.org. Entrambi restano DNS-only per lo stesso motivo tecnico di questo controllo (quarto livello, Universal SSL copre un solo livello) e quindi entrambi restano fuori da questi header — la conclusione del controllo non cambia, cambia solo quale contenuto vive su quale host.

ADR-edge-security-headers

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

Le convenzioni (ADR-P25-conventions) erano fruibili solo via chiave API o client MCP, ma il pubblico reale (studenti, professionisti non tecnici) non usa strumenti da sviluppatore: deve attestare direttamente dal sito, come già avviene in forma anonima. Serviva riconoscere l'email istituzionale senza cookie, sessioni server o un secondo sistema di login — il percorso anonimo resta a zero dati personali.

Decisione: Stesso OAuth one-shot già usato per le chiavi API, ma l'esito non è persistito in D1: un **voucher stateless firmato** — `base64url(JSON{email,conv,exp}) + '.' + HMAC-SHA256(...)`, TTL 8 ore — consegnato nel fragment dell'URL (`#sgv=`, mai inviato a un server) e tenuto in `sessionStorage` (niente cookie, sparisce alla chiusura della scheda). Il campo `conv` è solo un hint: la fonte di verità resta una query fresca (`matchConvention`) a ogni uso su `/api/hash`, così una convenzione disattivata smette di dare vantaggi senza meccanismo di revoca. La contabilità pool/tetto riusa la stessa funzione condivisa delle chiavi API (`accountConventionUsage`): comportamento identico su entrambi i canali, mai un blocco.

Conseguenze: Nessuna riga D1 per il solo accesso (a differenza delle chiavi API). Testato in locale (D1 reale): voucher con/senza convenzione, tetto, pool esaurito, convenzione disattivata dopo l'emissione, voucher scaduto/manomesso (403). Rilasciato lo stesso giorno (imgauth 1.21.2, authweb 1.17.0), poi verificato con login OAuth reale in produzione — prima prova end-to-end del canale. `CTL-site-voucher-auth` promosso `draft`→`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 via gate P24, prima che la migrazione schema (`voucher.sql`) fosse applicata alla D1 remota — `/developer/keys` (P22, in uso reale) ha risposto 500 fino all'applicazione. Nessuna perdita di dati; classe di rischio già coperta da `RSK-unobserved-production-deploy`, l'incidente è avvenuto proprio perché il rilascio non è passato dal gate. Due difetti non rilevabili dai test locali, emersi al primo uso reale e corretti: i bottoni dei provider resi invisibili da una classe CSS riusata fuori contesto (i test verificavano il DOM, non la posizione visiva); 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" per la 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 design iniziale prevedeva una quota individuale per membro di convenzione. Corretto in revisione: un ente ragiona per monte collettivo mensile, non per persona — pool mensile condiviso con un tetto individuale come secondo argine anti-drenaggio, non come unità di misura primaria. Da decidere anche il comportamento a pool/tetto esauriti (scelto il degrado, mai il blocco) e quanto loggare per rendere possibile un report all'ente senza estendere il tracciamento su un sistema oggi a zero dati personali per chi attesta anonimo.

Decisione: Contabilità a **pool mensile dell'ente** (`conventions.monthly_quota`, `COUNT(*)` su `convention_attestations` per `(convention_id, ym)`, Europe/Rome) più **tetto individuale** anti-drenaggio (`member_cap`, default 50/mese, stessa soglia del self-service ordinario). Esaurito l'uno o l'altro: emissione degradata silenziosamente alla fascia Base (certificato comunque emesso, `fascia_motivo: 'pool_esaurito'` o `'tetto_individuale'`), mai un 429. Il log `convention_attestations` (convenzione→membro→impronta) si scrive solo per le emissioni taggate in convenzione — l'unica associazione identità→opere del sistema, giustificata caso per caso, non generalizzata. Varata `/condizioni/` (pagina pubblica delle fasce): pubblicata `noindex` l'11/7 in attesa di delibera assembleare, linkata e indicizzata il 12/7 senza attenderla — regole pubbliche prima del deploy del meccanismo sottostante, contenuti già scritti invariati.

Conseguenze: Rilasciato lo stesso giorno (imgauth 1.21.0, tag `v1.21.0`, pipeline P24 — schema `conventions.sql` su D1 staging e produzione prima della promozione). Verificato con login OAuth reale in produzione: prima convenzione creata (`spaziogenesi.org`), chiave emessa con `convention_id` corretto, riga di log corretta — confermato via query diretta su D1, non solo dalla risposta API. `CTL-convention-accounting` promosso `draft`→`active`. Risolto lungo il percorso un problema di configurazione preesistente non collegato a P25: URI di redirect OAuth Google con un carattere iniziale mancante (`redirect_uri_mismatch`). Approssimazione nota: il campo `tier` di `meta/cert/<hash>.json` riflette l'appartenenza statica della credenziale alla convenzione, non l'esito dinamico pool/tetto calcolato al momento di `/api/hash` — un certificato degradato a Base può comparire `tier:'convenzione'`. Accettato perché `tier` non è ancora consumato da logica di retention differenziata; da rivalutare quando lo sarà. `/condizioni/` non promette immutabilità dei numeri: le regole possono cambiare se/quando l'assemblea delibera, solo l'onestà di quanto già vero oggi.

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 delle 5-6 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). Il controllo copre imgauth e authweb.

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