site: put the ADR projection in the build chain, and a contract outside the data tree

Two failures, one shape: something living in a tree whose consumers assume it holds
only something else.

adr-pages: gen-adr-pages.nu existed but no recipe called it, so the ADR projection
silently stopped tracking the spine — ADR-059..069 were written months ago and never
reached the site. A generator outside the dependency chain is a generator that does
not run. Wired into sync-full ahead of `content` (it emits the markdown that content
indexes), and it is idempotent: this commit carries no ADR diff because regenerating
reproduces the tree byte for byte.

graph: gen-content-graph nickel-exports every .ncl under site/content, and
expedientes/_schema/expediente.ncl was a CONTRACT, not an instance — export demanded
`id` and the recipe aborted. `just graph` had been dead since expedientes landed. The
generator was right and the file was in the wrong tree: site/content/ holds instances,
so a schema there is a category error. Moved to site/schemas/expediente.ncl; the one
sidecar that imports it still type-checks against the contract. graph now builds
(mini:154 related:222 ontology:15).

sync-full ordering: graph / adr-map / about-json ran AFTER content, but the leg that
copies the four nu artifacts into the serve tree lived INSIDE content — so a full sync
left site/r rendering the previous run's About and ADR map. Added sync-artifacts as the
final link. Both trees now agree on all four.

Gates
-----
adr-check  spine vs site: fails if the spine holds an ADR the site never published.
           Verified by deleting adr-069 — the gate caught it (69 vs 68, exit 1) and
           sync-full rebuilt it unattended.

This is the third gate (with templates-check and posts-check) and, like the others, it
runs before the damage, not after. None of them run on deploy yet: `just authoring
publish` still ships without asking. That is what keeps these gates a habit rather
than a law — and is the subject of the governed-delivery ADR still to be written.

Refs: ADR-066 (the check decides, never the reporter).

Versión corta, si la prefieres:

site: wire the ADR projection into the build chain; move a contract out of the data tree

gen-adr-pages existed but no recipe called it — eleven ADRs never reached the site. Now in
sync-full, with an adr-check gate that fails when the spine holds an ADR the site doesn't.
`just graph` had been dead since expedientes landed: a schema sat inside site/content/, which
gen-content-graph nickel-exports as if everything there were an instance. Moved to
site/schemas/. Also fixed sync-full ordering so the serve tree stops rendering the previous
run's About and ADR map.
This commit is contained in:
Jesús Pérez 2026-07-12 00:02:01 +01:00
parent 72a389f056
commit 56d3c10d9e
4 changed files with 53 additions and 3 deletions

1
site/.gitignore vendored
View file

@ -26,3 +26,4 @@ data/
logs_*.out
.wrks/
works-pv/
*.swp

View file

@ -105,6 +105,36 @@ diagram:
nu "{{ project_root }}/scripts/build/gen-diagram.nu" --root "{{ project_root }}/../.." --out "{{ project_root }}/site/public/images/ontoref-diagram.svg"
nu "{{ project_root }}/scripts/build/gen-graph-page.nu" --root "{{ project_root }}/../.." --out "{{ project_root }}/site/public/images/ontoref-graph.html"
# The ADR set moves with the spine, not with this repo, so the projection has to be in
# the build chain or it silently stops tracking it: ADR-059..069 sat unpublished for
# months because this generator existed but no recipe called it. A generator outside the
# dependency chain is a generator that does not run. Regenerating is safe and idempotent —
# it rewrites the whole adr/ kind from .ontoref/adrs, md + .ncl sidecar per ADR.
# Project .ontoref/adrs → adr content-kind markdown (site/content/adr/en/{accepted,proposed}).
[no-cd]
adr-pages:
ONTOREF_NICKEL_IMPORT_PATH="{{ project_root }}/../../code/ontology" \
nu "{{ project_root }}/scripts/build/gen-adr-pages.nu" \
--root "{{ project_root }}/../.." \
--mode content \
--content-out "{{ project_root }}/site/content/adr/en"
# Fails if the spine holds an ADR the site never published — the exact drift that hid
# eleven ADRs. Non-mutating: compares .ontoref/adrs against the adr content tree.
# Gate: assert every accepted/proposed ADR in the spine has a page.
[no-cd]
adr-check:
#!/usr/bin/env bash
set -e
spine=$(ls "{{ project_root }}/../../.ontoref/adrs"/adr-[0-9]*.ncl | wc -l | tr -d ' ')
pages=$(ls "{{ project_root }}"/site/content/adr/en/*/adr-[0-9]*.md | wc -l | tr -d ' ')
if [ "$spine" -eq "$pages" ]; then
echo "adr-check: $pages/$spine ADRs projected — spine and site agree"
else
echo "adr-check: DRIFT — spine has $spine ADRs, site publishes $pages. Run: just adr-pages"
exit 1
fi
# Makes /catalog (browse) + /catalog/{slug} serve as SSR content (runtime kind, no
# rebuild — registered in site/content/content-kinds.toml).
# Project .ontoref/catalog → catalog markdown, then build the content index.
@ -272,7 +302,26 @@ posts-check:
[no-cd]
sync: templates htmx-assets rotator-assets content
# Order is load-bearing, not cosmetic:
# adr-pages / catalog-content BEFORE content — they emit the markdown content indexes.
# graph / adr-map / about-json AFTER content — they read the freshly built indexes.
# sync-artifacts LAST — the nu generators write to site/public/r, and only `content`
# copies them back to the serve tree; without this final leg a sync-full leaves
# site/r rendering the previous run's About and ADR map.
# Requires ONTOREF_NICKEL_IMPORT_PATH in .env.
# Full resync including content_graph + ADR map + About view-model.
# Full resync: projections → content indexes → derived artifacts → serve tree.
[no-cd]
sync-full: templates htmx-assets rotator-assets content catalog-content graph adr-map about-json
sync-full: templates htmx-assets rotator-assets adr-pages catalog-content content graph adr-map about-json sync-artifacts
# The nu generators own these four files in site/public/r (the deploy tree); the serve
# tree needs the same copies. `content` does this too, so a standalone `just content`
# stays correct — this recipe exists for the chains that regenerate them AFTER content.
# Copy the four nu artifacts public/r → site/r so the dev server serves what was just built.
[no-cd]
sync-artifacts:
#!/usr/bin/env bash
set -e
for f in about.json adr-map.json taglines.json content_graph.json; do
src="{{ project_root }}/site/public/r/$f"
if [ -f "$src" ]; then cp -f "$src" "{{ project_root }}/site/r/$f"; fi
done

View file

@ -1,7 +1,7 @@
# Typed source for expediente "deriva-hardcoded-anti-pap".
# The sibling .md is the rendered prose; this record is the machine-parseable
# case. Regenerate/render via the `generate-expediente` mode.
let s = import "../../_schema/expediente.ncl" in
let s = import "../../../../schemas/expediente.ncl" in
s.make_expediente {
id = "deriva-hardcoded-anti-pap",