website-htmx-rustelo/.ontoref/reflection/modes/publish-content.ncl
Jesús Pérez 3ccd15a14b init repo
2026-07-10 03:33:46 +01:00

98 lines
6.8 KiB
Text

let d = import "../defaults.ncl" in
# Consumption contract for publishing content INTO a Rustelo HTMX-SSR site.
#
# This mode exists because the site's self-description validates content QUALITY
# (validate-content: bilingual parity; plan-publication: coverage/SEO/state) but
# never declared the MECHANICS of getting a page to render. Those mechanics live
# scattered in adr-001 (build-time codegen), adr-006 (htmx profile), and adr-007
# (templates+generator). A consumer that assumes "drop a markdown file and it is
# visible" is wrong by design: routes and content-kinds are build-time codegen.
#
# The build/runtime boundary — VERIFIED against the binary, not assumed:
# - Content-TYPE discovery is RUNTIME. content_processor reads
# site/content/content-kinds.toml ([[content_kinds]] name/enabled); if that
# file is absent it falls back to [blog, recipes] (rustelo_server bin: "no
# hardcoded types"). So enabling a new kind (projects, …) is a TOML edit →
# NO recompile. A kind that is a silent no-op is simply missing from that TOML.
# - Markdown WITHIN an enabled kind is indexed at runtime by content_processor
# (→ site/r/<kind>/<lang>/) → NO recompile.
# - Only ROUTES are build-time codegen (config/routes/*.ncl, adr-001): the server
# must already carry the kind's <Handler> route. Adding a NEW route REQUIRES
# `cargo build`. Reusing an existing routed kind (e.g. projects) does not.
# - Serving path: the server reads processed content from public/r; if the site
# uses it, sync site/r → site/public/r (just sync) after reindexing.
#
# Content layout per kind shape (from the living model):
# - index-style (projects): content/<kind>/<lang>/<slug>/index.md → /<kind>/<slug>
# - category-style (blog): content/<kind>/<lang>/<category>/<slug>.md → /<kind>/<category>/<slug>
#
# Site identity (logo/name/footer/nav) is NOT content — it lives in
# site/config/site.ncl + templates/build-tools/partials/{header,footer}.tera.
d.make_mode String {
id = "publish-content",
trigger = "Publish content into a Rustelo HTMX-SSR site: place markdown in the content-kind's on-disk layout, reindex with content_processor (no recompile for an existing kind), and verify the route serves — honoring the build-time/runtime boundary (adr-001) and the bilingual-parity axiom (validate-content).",
preconditions = [
"site/content/ exists",
"content_processor is on PATH",
"the target content-kind is listed in site/content/content-kinds.toml (runtime registry — add it if missing, no recompile)",
"the target kind already has a <Handler> route in config/routes (build-time, adr-001 — a NEW route needs `cargo build`; reusing an existing routed kind like projects does not)",
],
steps = [
{
id = "classify-target",
action = "Confirm content_processor recognises the kind. Discovery is RUNTIME from site/content/content-kinds.toml — NOT compiled in. Oracle: run content_processor on the kind; 'Processing <kind>' means it is enabled. If it is a silent no-op, the kind is missing from content-kinds.toml — add a [[content_kinds]] entry (name=<kind>, enabled=true). This is a TOML edit, NO recompile. Only check the route separately: the kind needs a <Handler> route in config/routes, which is build-time (reusing an already-routed kind like projects avoids any build).",
cmd = "content_processor --content-type \"$KIND\" 2>&1 | grep -qi \"processing $KIND\" && echo \"kind enabled (runtime)\" || echo \"kind missing from content-kinds.toml — add a [[content_kinds]] entry (no recompile)\"",
actor = 'Both,
on_error = { strategy = 'Continue },
},
{
id = "place-content",
action = "Write the markdown at the kind's layout: index-style kinds (e.g. projects) use content/<kind>/<lang>/<slug>/index.md and serve at /<kind>/<slug>; category-style kinds (e.g. blog) use content/<kind>/<lang>/<category>/<slug>.md. Author BOTH languages (en and es) — the bilingual-parity axiom makes a route present in one language only a hard error. Frontmatter must carry at least id, title, slug, subtitle, excerpt, author, date, published, category, tags.",
actor = 'Agent,
depends_on = [{ step = "classify-target" }],
on_error = { strategy = 'Stop },
},
{
id = "enforce-bilingual-parity",
action = "Run the validate-content mode as a sub-check: EN/ES route symmetry, content-file count parity, FTL key parity. Any route present in one language but not the other is a blocking error against the bilingual-parity gate; resolve before reindexing.",
actor = 'Both,
depends_on = [{ step = "place-content" }],
on_error = { strategy = 'Stop },
},
{
action = "Regenerate the runtime content index with content_processor (writes site/r/<kind>/<lang>/ + filter-index.json + category indexes). With the kind enabled in content-kinds.toml this is the SINGLE activation command — no recompile. Then ensure the server's serving path is current: if the site serves from public/r, sync site/r → site/public/r (just sync) so the new index is visible.",
id = "reindex",
cmd = "content_processor --content-type $KIND",
actor = 'Both,
depends_on = [{ step = "enforce-bilingual-parity" }],
on_error = { strategy = 'Stop },
},
{
id = "verify-oracle",
action = "Start the server (run.sh / rustelo-htmx-server) and GET each published route in both languages — /<kind>/<slug> and its localized path — asserting the page renders the authored title/body, not a 404 or the index fallback. This observed render is the oracle; publication is not done until it passes.",
cmd = "for u in \"/$KIND/$SLUG\" \"/$KIND_ES/$SLUG\"; do printf '%s -> ' \"$u\"; curl -sfI \"http://localhost:3000$u\" >/dev/null && echo ok || echo FAIL; done",
actor = 'Both,
depends_on = [{ step = "reindex" }],
on_error = { strategy = 'Continue },
},
{
id = "review",
action = "Human confirms the rendered pages match intent in both languages. Only then is the content published. A failing oracle is a blocker, never bridged by assumption.",
actor = 'Human,
depends_on = [{ step = "verify-oracle" }],
on_error = { strategy = 'Stop },
},
],
postconditions = [
"Content exists at the kind's layout in BOTH languages (bilingual-parity satisfied)",
"The kind is enabled in content-kinds.toml (runtime registry) — a new TYPE costs a TOML edit, never a recompile",
"content_processor index (site/r/<kind>) regenerated and synced to the server's serving path (public/r if used)",
"Each published route returns the authored page in both languages (oracle passed)",
"Only a NEW ROUTE is flagged as build-time (cargo build); enabling a content-type is never silently treated as needing a rebuild",
],
}