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///) → NO recompile. # - Only ROUTES are build-time codegen (config/routes/*.ncl, adr-001): the server # must already carry the kind's 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////index.md → // # - category-style (blog): content////.md → /// # # 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 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 ' 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=, enabled=true). This is a TOML edit, NO recompile. Only check the route separately: the kind needs a 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////index.md and serve at //; category-style kinds (e.g. blog) use content////.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/// + 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 — // 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/) 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", ], }