diff --git a/site/.gitignore b/site/.gitignore index 5edfe54..fbca28c 100644 --- a/site/.gitignore +++ b/site/.gitignore @@ -17,10 +17,11 @@ site/public/r/ provisioning/.content-packs/ # ── Generated: runtime + build outputs ─────────────────────────────────────── -# htmx-templates/ and htmx-assets/ are ASSEMBLED (`just templates` rm -rf's them). -# They stay tracked until templates-overlay/ is the declared source for every -# hand-authored template and `just templates-check` proves regeneration is a -# no-op — ignoring a generated tree that still holds unique work loses it. +# htmx-templates/ and htmx-assets/ are ASSEMBLED (`just templates` / `just htmx-assets` +# rm -rf them). They stay tracked until site/_htmx/ is the declared source for every +# hand-authored template and asset, and `just templates-check` + `just assets-check` +# prove regeneration is a no-op — ignoring a generated tree that still holds unique +# work loses it. cache/ data/ logs_*.out diff --git a/site/justfile b/site/justfile index 01dac09..d66c2cc 100644 --- a/site/justfile +++ b/site/justfile @@ -152,12 +152,18 @@ deck-new source id title_es="" slug_es="" title_en="" slug_en="" source_en="": # Gate (on disk): the deck's CSS bundle cannot reach out of its wrapper, the site's CSS cannot # reach in — except the glossary, which is entitled to and which is the whole point. +# +# The ids come from lib/variants.mjs, the one authority on the deck→artifact key. This recipe used +# to re-derive them with `jq -r '.decks[].id'`, from before a bilingual deck became one source (and +# one asset dir) PER LANGUAGE: it asked for `ai-infra`, which holds thumbnails and no bundle, while +# the pages load `ai-infra-es`. It crashed on the deck built after the split and passed on the one +# whose pre-split bundle still sat on disk — gating a file no page loads. Ask the authority. [no-cd] decks-check: #!/usr/bin/env bash set -e cd "{{ project_root }}/../presentation" - for id in $(nickel export --format json ../scripts/decks/decks.ncl | jq -r '.decks[].id'); do + for id in $(node ../scripts/decks/lib/variants.mjs --ids); do node ../scripts/decks/gates/artifact.mjs "$id" done diff --git a/site/site/public/docs/case-files/protocol.html b/site/site/public/docs/case-files/protocol.html index 2c239cb..27e571c 100644 --- a/site/site/public/docs/case-files/protocol.html +++ b/site/site/public/docs/case-files/protocol.html @@ -132,7 +132,7 @@
Session protocol · Case-file series

The session protocol

This protocol was not written: it is projected from the case files' replays — the session replayed with protocol that every case leaves written and typed. Change a case and this page changes with it. Editing it by hand is impossible by construction, because a hand-kept checklist is the next drift.

-
Replays 4Cases 11
+
Replays 4Cases 12
@@ -262,6 +262,7 @@ the one you deduce by reading the file. 404-PAP-bis⊘ The relapse had a cause, and it was not memory: the first fix had never gone back to the template, so it was not a relapse but reinfection from the source. It has gone back now — the backport is in both templates and the hand-written list is forbidden by a typed constraint. What remains is the usual: nothing runs it. Rustelo's justfile calls no validator, so the gate that would prevent the next reinfection exists and is silent. A just recipe in its chain would settle it. Case 8/0 tells why the fix took months to come home. 1/1852⊘ The frontier stays open where data becomes text: whatever is written past it is published on someone's word. A contract over the EMITTED artifact — a schema for the generated resource — would settle it, not one over its source alone. 8/0⊘ Nothing runs the new constraint: the recipe that runs all 23 and reports per constraint is unwritten — and without it, this rots again exactly the same way. A just recipe in rustelo's chain would settle it, kept out of check-strict until the 11 real hits are triaged. And four remain ownerless: the third list in htmx_pages.rs (3 hits), the 11 real hits of no-hardcoded-route-paths, templates/ unversioned — the gravest, and no ADR says whether it is policy or oversight — and an instance ↔ template drift-check, which has neither mechanism nor owner. +8/4⊘ «--artifacts» declared list<string> and unreachable from the CLI wrapper — the five historical runs carry an empty column because of it; settled by accepting a comma-separated string or a repeatable option. «verify» in zero of the mode's eleven steps; adding it to the three with an oracle is approved (assemble_case: the .ncl exports; render: the .md reproduces; publish: expedientes-check). The howto's step list is not checked against the DAG; settled by a gate comparing its ids against mode show --fmt json. And the underlying one, with no gate possible: a sentence is not an artifact — the only oracle of the human↔agent channel is the human.
diff --git a/site/site/public/docs/expedientes/protocolo.html b/site/site/public/docs/expedientes/protocolo.html index b396625..6c52763 100644 --- a/site/site/public/docs/expedientes/protocolo.html +++ b/site/site/public/docs/expedientes/protocolo.html @@ -132,7 +132,7 @@
Protocolo de sesión · Serie Expedientes

El protocolo de sesión

Este protocolo no se redactó: se proyecta de los replays de los expedientes — la sesión repetida con protocolo que cada caso deja escrita y tipada. Cambia un caso y esta página cambia con él. Editarla a mano es imposible por construcción, porque una checklist mantenida a mano es la siguiente deriva.

-
Replays 4Casos 11
+
Replays 4Casos 12
@@ -262,6 +262,7 @@ deduce leyendo el fichero. 404-PAP-bis⊘ La recaída tenía causa, y no era la memoria: el primer arreglo nunca había vuelto a la plantilla, así que no era recaída sino reinfección desde la fuente. Ya volvió — el backport está en las dos plantillas y la lista a mano está prohibida por una restricción tipada. Lo que queda es lo de siempre: nada la ejecuta. El justfile de rustelo no llama a ningún validador, así que la puerta que impediría la próxima reinfección existe y está callada. La saldaría una receta just en su cadena. El expediente 8/0 cuenta por qué el arreglo tardó meses en volver a casa. 1/1852⊘ La frontera sigue abierta donde el dato se vuelve texto: lo que se escribe tras ella se publica bajo palabra. Lo saldaría un contrato sobre el artefacto EMITIDO —un esquema del recurso generado—, no sólo sobre su fuente. 8/0⊘ Nada ejecuta la restricción nueva: la receta que corra las 23 y reporte por restricción está sin escribir — y sin ella, esto se vuelve a pudrir exactamente igual. La saldaría una receta just en la cadena de rustelo, fuera de check-strict hasta que los 11 hits reales estén triados. Y quedan cuatro sin dueño: la tercera lista de htmx_pages.rs (3 hits), los 11 hits reales de no-hardcoded-route-paths, templates/ sin versionar —la más grave, y ningún ADR dice si es política o descuido—, y un drift-check instancia ↔ plantillas, que no tiene ni mecanismo ni dueño. +8/4⊘ «--artifacts» declarado list<string> e inalcanzable desde el envoltorio del CLI — los cinco runs históricos llevan la columna vacía por eso; lo salda aceptar cadena separada por comas o una opción repetible. «verify» en cero de los once pasos del modo; aprobado añadirlo a los tres con oráculo (assemble_case: el .ncl exporta; render: el .md reproduce; publish: expedientes-check). La lista de pasos del howto no se coteja contra el DAG; lo salda una puerta que compare sus ids contra mode show --fmt json. Y la de fondo, sin puerta posible: una frase no es un artefacto — el único oráculo del canal humano↔agente es el humano.
diff --git a/site/site/rbac.ncl b/site/site/rbac.ncl index 316d1dd..1384a2d 100644 --- a/site/site/rbac.ncl +++ b/site/site/rbac.ncl @@ -257,7 +257,18 @@ let E = { # Per-post engagement (views/ratings/messages) — public, anonymous by design. { resource = "endpoint", pattern = "/api/htmx/post/*", outcome = "allow", methods = ["GET", "POST"], effects = [] }, # Contact form — public submission, no auth required. + # Superseded by the declarative endpoint below; kept while the old handler still exists. { resource = "endpoint", pattern = "/api/htmx/contact", outcome = "allow", methods = ["POST"], effects = [] }, + # Declarative forms — one endpoint, one policy. A form is DECLARED in + # site/config/forms/.ncl and served by POST /api/htmx/form/: no Rust, no rebuild. + # The handler refuses an id that is not [a-z0-9-] and refuses a posted field the spec does + # not declare, so opening the pattern does not open a hole — the spec is the boundary. + { resource = "endpoint", pattern = "/api/htmx/form/*", outcome = "allow", methods = ["POST"], effects = [] }, + # Newsletter subscription — public, anonymous by design. The handler RELAYS to the provider in + # newsletter.ncl and answers with a fragment, so the reader stays on the site and learns + # whether it worked. The list is read from that config, never from the request: opening this + # pattern lets a visitor subscribe, not choose which list they land on. + { resource = "endpoint", pattern = "/api/htmx/newsletter", outcome = "allow", methods = ["POST"], effects = [] }, # Leptos server functions for OTP auth flow — hash suffix varies per build, # glob * matches the numeric hash without requiring a path separator. { resource = "endpoint", pattern = "/api/server_request_otp*", outcome = "allow", methods = ["POST"], effects = [] },