Delivery dei contenuti pubblici
ADR-030 supersede ADR-029. Il public site target usa Astro su Vercel con rendering server/on-demand e cache ISR bounded. Publish e unpublish non provocano una build frontend.
Responsabilità
| Confine | Owner |
|---|---|
| API, Editorial MCP, D1 e lifecycle editoriale | architecture-coffee su Cloudflare |
| Astro, rendering, ISR, cache, SEO, ricerca e analytics | architecture-coffee-public-site su Vercel |
| Contenuto pubblico | REST bounded autenticata server-to-server |
Il backend non renderizza pagine applicative e non orchestra build frontend. Il frontend non scrive stato editoriale e non accede a D1. I secret Access restano esclusivamente nel runtime server-side Vercel e non raggiungono browser o HTML.
Flusso target
flowchart LR
E[Publish o unpublish] --> D[(D1 Cloudflare)]
D --> A[REST bounded per modello]
U[Richiesta lettore] --> V[Astro su Vercel]
V -->|Access Service Auth, sola lettura| A
V --> I[Cache ISR bounded]
I --> U
Il deployment del codice frontend e l'aggiornamento dei contenuti sono
lifecycle distinti. I contenuti diventano visibili tramite lettura API e
revalidation della cache; una transizione editoriale non seleziona branch, SHA,
tag, build, Worker o target. Dopo la prova DEV di #47, #382 ha rimosso la route
family /public/v1/* e il relativo catalogo/snapshot globale.
Contratto REST bounded
D1 è l'autorità editoriale runtime. Access autentica il consumer server-side e ammette soltanto i path ADR-032; l'API impone visibilità, shape, bound e metodi.
| Modello | Collection/detail | Visibilità pubblica |
|---|---|---|
| Issue | /issues, /issues/{slug} |
PUBLISHED con data pubblicazione |
| Topic | /topics, /topics/{slug} |
accepted |
| Knowledge Asset | /knowledge-assets, /knowledge-assets/{slug} |
accepted |
| Glossary Term | /glossary, /glossary/{slug} |
accepted |
Le collection accettano limit 1..100 (default 50) e offset 0..10.000
(default 0). D1 legge al massimo limit + 1 righe del solo modello richiesto;
il detail seleziona una sola riga visibile tramite slug canonico. Le response
omettono revision, workflow editoriale, campi privati di planning/reference e
relazioni cross-model non risolte. I metodi mutativi non hanno route.
Filtri tipizzati delle collection
Ogni collection possiede una specifica chiusa. Parametri diversi si combinano
con AND; ripetere un filtro array richiede che il record contenga tutti i
valori, mentre ripetere un filtro scalare produce 400. La stringa letterale
null seleziona uno scalare pubblico opzionale assente. Valori vuoti,
enumerazioni e numeri non validi, dot path non dichiarati e parametri sconosciuti
producono 400 senza fallback permissivi.
Ogni campo della matrice può inoltre usare il solo suffisso dichiarato .any.
Valori ripetuti di field.any si combinano con OR locale a quel campo; gruppi
di campi differenti continuano a combinarsi con AND. Per uno scalare equivale
a una equality multi-value; per un array richiede la membership di almeno uno
dei valori. La forma base e .any dello stesso campo non possono comparire
insieme e producono 400.
| Collection | Filtri scalari | Filtri array ripetibili | .any |
|---|---|---|---|
/admin/editorial/taxonomies |
kind, canonicalKey, visibility, publicLabel, publicDescription, revision, updatedAt |
aliases |
tutti i campi elencati |
/topics |
id, slug, title, titleIt, track, category, learningObjective, intendedAudienceValue, difficulty.level, difficulty.rationale |
tags |
tutti i campi elencati |
/knowledge-assets |
id, slug, title, type, summary |
tags |
tutti i campi elencati |
/issues |
slug, title, subtitle, issueNumber, category, readingTimeMinutes, publishedAt |
tags |
tutti i campi elencati |
Non esistono altri suffissi o operatori: .or, .all, .in, confronti,
parentesi, filtri JSON e OR tra campi arbitrari sono parametri sconosciuti e
producono 400.
I repository D1 traducono soltanto questi attributi in colonne materializzate o
path JSON statici, con tutti i valori passati tramite binding. I predicati sono
applicati prima di ordinamento, limit, offset e calcolo di hasMore; restano
invarianti le selezioni accepted per Topic e Knowledge Asset e
PUBLISHED con publishedAt per Issue. Il repository statico delle tassonomie
applica la stessa semantica. I gruppi .any scalari usano IN bound; quelli
array usano una sola membership existential con valori bound. Colonne, path,
operatori e predicati restano allowlist statiche possedute dai repository.
relatedTopics resta intenzionalmente escluso dai filtri di
/knowledge-assets: è una relazione interna presente nel modello canonico ma
non nella projection pubblica della collection. Sono esclusi allo stesso modo i
campi interni Topic di planning/reference e i campi Issue disponibili soltanto
nel detail. Aggiungerli richiede prima una modifica esplicita della projection
pubblica, non un accesso arbitrario al payload JSON.
GET /topics/{slug}/knowledge-assets espone la projection inversa dei
Knowledge Asset accepted il cui relatedTopics canonico contiene il Topic
accepted risolto dallo slug. La membership è esatta sull'array persistito; la
response riusa i campi card già validi per /knowledge-assets e non restituisce
ID interni relatedTopics. Non persiste righe di relazione, grafi o autorità
derivata.
La consistenza tra collection è intenzionalmente eventuale.
PublicContentCatalog e il relativo snapshot/hash globale non fanno più parte
dell'architettura runtime; una richiesta di modello non scansiona collection
estranee.
Superfici rimosse dal target
Il target non include:
- Queue Public Content usata per provocare rebuild;
- Rebuild Coordinator o Durable Object di rebuild;
- Workers Builds, Deploy Hook, readback o callback;
- Event Subscription Queue e correlazione
build_uuid; - debounce, successore build o credenziali di orchestrazione;
- sei Worker applicativi;
- rebuild dopo publish/unpublish.
Il repository Coordinator e le PR storiche restano evidenza, ma non hanno una responsabilità runtime target.
Contratto Vercel
La Story public-site #39 definisce e verifica @astrojs/vercel, output
server/on-demand, strategia ibrida prerender/dinamica, TTL ISR code-owned e
bounded, homepage, dettaglio, archivi, ricerca, sitemap, feed, 404 e comportamento
durante indisponibilità temporanea del backend o di Access. DEV e PROD sono
separati tramite configurazione Vercel.
Progetto, domini e variabili Vercel richiedono target, verifica e rollback espliciti nell'Issue. Dopo il merge Fra, Codex esegue gli stage provider già revisionati senza un secondo go/no-go ordinario.
Esito della cleanup backend
La cleanup backend #380, eseguita dopo la prova end-to-end del flusso bounded,
ha rimosso dalle superfici applicative public_content.changed, producer Queue,
enqueue post-commit, recovery amministrativa, repository outbox e
PublicContentEventResolver. Publish e unpublish modificano soltanto il
lifecycle editoriale canonico.
L'inventario ha escluso consumer indipendenti: in development il producer era del solo Worker backend e non esisteva un consumer; in production producer e unico consumer appartenevano allo stesso Worker backend; nessuna delle due Queue aveva Event Subscription. D1 non aveva righe pending, ma soltanto evidenza storica terminale.
La tabella D1 storica e la colonna di revisione restano inerti e non sono referenziate dal runtime. Rimuoverle non è necessario per il disaccoppiamento e sarebbe distruttivo prima dell'attivazione del nuovo codice nell'ordine canonico migrazione-prima-del-deploy. Detach/delete delle Queue provider e un'eventuale contrazione D1 fisica richiedono una successiva Issue operativa con inventario e rollback espliciti. Mailjet resta fuori scope.
Governance
- Project #1 pianifica soltanto backend, API, MCP, D1 e lifecycle editoriale.
- Project #2 pianifica soltanto Astro, Vercel, SSR/on-demand, ISR, SEO, ricerca e analytics.
- Project #3 è archiviato, conserva evidenza storica e non riceve nuovi item.
La chiusura nativa della Issue rappresenta il completamento. Project Status
governa selezione e priorità, non CI o deploy. Non si usano label status:* come
seconda fonte di workflow.
Ordine
architecture-coffee#383
→ architecture-coffee#382 (nuove REST, legacy mantenuta)
→ architecture-coffee-public-site#47
→ architecture-coffee#382 (rimozione legacy dopo evidenza DEV)
→ architecture-coffee-public-site#44 (evidenza di convergenza Production)
→ architecture-coffee#380 (cleanup applicativa delivery-only)
La chiusura di una dipendenza non promuove automaticamente la Story successiva.