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

111 lines
6.1 KiB
Text

let d = import "../defaults.ncl" in
d.make_mode String {
id = "validate-purpose-alignment",
trigger = "Validate that purpose.ncl criteria are met before production publication: bilingual completeness, SEO criteria (hreflang, JSON-LD, meta descriptions, Core Web Vitals), audience relevance, and rendering/hydration stability. A single Hard failure blocks publication.",
preconditions = [
".ontoref/ontology/purpose.ncl exists",
".ontoref/ontology/state.ncl exists",
".ontoref/ontology/gate.ncl exists",
"nickel binary available on PATH",
"code/site/config/ exists",
],
steps = [
{
id = "export-purpose",
action = "Export .ontoref/ontology/purpose.ncl via nickel. Validates the PurposeFile schema and all purpose-constraints checks. Fail immediately on any structural violation.",
cmd = "nickel export .ontoref/ontology/purpose.ncl",
actor = 'Both,
on_error = { strategy = 'Stop },
},
{
id = "check-rendering-state",
action = "Export state.ncl and read the rendering-mode dimension. The Hard criterion 'no-publish-during-hydration-instability' requires that no hydration-opted route regresses below the htmx-ssr baseline. Run validate-hydration; any empty-HashMap hit in a *Client component is a Hard FAIL that blocks publication.",
cmd = "nickel export .ontoref/ontology/state.ncl",
actor = 'Both,
depends_on = [{ step = "export-purpose" }],
on_error = { strategy = 'Stop },
},
{
id = "check-purpose-gate-membrane",
action = "Export gate.ncl and verify the 'purpose-gate' membrane exists, is active=true, and protocol='Challenge. A weakened or inactive purpose-gate is itself a Hard violation.",
cmd = "nickel export .ontoref/ontology/gate.ncl",
actor = 'Both,
depends_on = [{ step = "export-purpose" }],
on_error = { strategy = 'Stop },
},
{
id = "check-bilingual-completeness",
action = "For each menu-visible content section (blog, projects): diff EN vs ES content file lists. Flag any section where ES trails EN. This is the Hard criterion 'bilingual-completeness'.",
cmd = "diff <(find code/site/content/en -name '*.md' 2>/dev/null | sed 's#/en/#/#' | sort) <(find code/site/content/es -name '*.md' 2>/dev/null | sed 's#/es/#/#' | sort) | head -40 || echo 'content dirs not found at code/site/content/'",
actor = 'Both,
depends_on = [{ step = "export-purpose" }],
on_error = { strategy = 'Continue },
},
{
id = "check-hreflang",
action = "Audit generated HTML for the bilingual-canonical-urls criterion: verify <link rel=\"alternate\" hreflang=\"es\"> and hreflang=\"en\" are present in the <head> of blog and project pages, and that sitemap.xml includes both variants. Hard SEO criterion.",
actor = 'Both,
depends_on = [{ step = "export-purpose" }],
note = "Requires a built or staged site. If none available, report UNVERIFIED (not PASS).",
on_error = { strategy = 'Continue },
},
{
id = "check-structured-data",
action = "Audit generated HTML for the structured-data-articles criterion: verify <script type=\"application/ld+json\"> with @type Article or TechArticle is present in blog post pages. Hard SEO criterion.",
actor = 'Both,
depends_on = [{ step = "export-purpose" }],
note = "Requires a built or staged site. If none available, report UNVERIFIED.",
on_error = { strategy = 'Continue },
},
{
id = "check-meta-descriptions",
action = "Audit generated HTML for the meta-description-purpose-aligned criterion: verify every published page has a non-empty <meta name=\"description\"> matching the real content (no clickbait, no keyword stuffing). Spot-check 5 pages minimum. Hard SEO criterion.",
actor = 'Both,
depends_on = [{ step = "export-purpose" }],
note = "Requires a built or staged site. Human judgment required for content accuracy.",
on_error = { strategy = 'Continue },
},
{
id = "check-core-web-vitals",
action = "Run Lighthouse (mobile) on /, /blog, /projects. Report the performance score; flag any score < 90. Soft SEO criterion 'core-web-vitals-threshold' (advisory, not blocking).",
actor = 'Both,
depends_on = [{ step = "export-purpose" }],
note = "Requires a running site: npx lighthouse <url> --only-categories=performance --form-factor=mobile --output=json",
on_error = { strategy = 'Continue },
},
{
id = "validate-hard-criteria",
action = "Aggregate results from check-rendering-state, check-bilingual-completeness, check-hreflang, check-structured-data, check-meta-descriptions. For each Hard criterion in purpose.ncl validation_criteria and seo_criteria, report PASS / FAIL / UNVERIFIED. A single Hard FAIL means BLOCKED.",
actor = 'Both,
depends_on = [
{ step = "check-rendering-state" },
{ step = "check-purpose-gate-membrane" },
{ step = "check-bilingual-completeness" },
{ step = "check-hreflang" },
{ step = "check-structured-data" },
{ step = "check-meta-descriptions" },
{ step = "check-core-web-vitals" },
],
on_error = { strategy = 'Stop },
},
{
id = "report",
action = "Generate the purpose-alignment report: (1) per-criterion PASS/FAIL/UNVERIFIED table, Hard first; (2) Soft criteria status; (3) overall verdict READY FOR PUBLICATION or BLOCKED; (4) if BLOCKED, ordered list of blocking issues with the Hard criterion id and fix hint.",
actor = 'Both,
depends_on = [{ step = "validate-hard-criteria" }],
on_error = { strategy = 'Stop },
},
],
postconditions = [
"nickel export .ontoref/ontology/purpose.ncl succeeds",
"rendering-mode hydration-opted routes confirmed at htmx-ssr baseline or better (validate-hydration clean)",
"purpose-gate membrane is active with protocol='Challenge",
"All Hard ValidationCriteria PASS or explicitly UNVERIFIED with justification",
"All Hard SeoCriterion PASS or explicitly UNVERIFIED with justification",
"Report generated with explicit READY or BLOCKED verdict and criterion table",
],
}