111 lines
6.1 KiB
Text
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",
|
|
],
|
|
}
|