VW-DL
Viroway Docs Lint
1. Purpose
In the ZAYAZ pipeline, specifications are not documentation about the system — they are machine-read inputs to the system: Docusaurus frontmatter feeds ticket generation, datasheet generation, and implementation by AI agents (Viktor). A drifted spec is therefore not a stale page; it is a defect injected upstream of code, where it is cheapest to create and most expensive to discover.
The evidence is already on record. A single review pass across the TrustGate corpus surfaced: a pipeline-ordering conflict between the MIB and an engine spec (resolved by CTO ruling, 2026-07); duplicate file generations (*-specification-2, *-specification-3); one engine whose identity table was embedded inside its output-CSI table; and four distinct heading conventions for the same CSI sections. Each was cheap to fix when found and would have been expensive to discover in a sprint.
This specification makes that review pass permanent, automatic, and blocking.
2. Rule Classes
2.1. Class A — Frontmatter Schema (per doc_type)
Every .mdx file SHALL declare doc_type, and each doc_type has a required frontmatter schema:
| doc_type | Required fields |
|---|---|
| engine-spec | id, semantic_id, short_id, canonical_name, version, status, hub, omr_module_ids, consumes, produces, replay_profile (per TG-RP §3) |
| normative-spec | id, semantic_id, short_id, canonical_name, version, status, governed_by |
| catalog | id, semantic_id, version, status |
| mib | id, semantic_id, version, status |
Field-level checks: semantic_id matches grammar zar.component.<hub>.<kind>.<name...>; version is semver; status ∈ {draft, review, approved, deprecated}; short_id unique across the corpus.
2.2. Class B — Cross-Document Invariants
These are the rules that catch what humans reliably miss: statements that are individually correct and mutually contradictory.
DL-101 · Canonical pipeline order (ERROR). Any document that enumerates the TrustGate runtime pipeline SHALL present it in the canonical order:
Parser → Structure Validation → Normalizer → Enrichment →
Rule Evaluation → Policy Resolution → Trust Scoring → Decision
The linter extracts ordered engine-name sequences from pipeline diagrams, tables, and position sections, normalizes aliases (per each spec's aliases frontmatter), and fails on any sequence that contradicts the canonical order. The historic MIB/engine-spec conflict becomes a build failure.
DL-102 · Decision exits (ERROR). Any enumeration of Decision Engine outcomes SHALL contain exactly the five exits: ACCEPT, ROUTE, ESCALATE, QUARANTINE, REJECT. Four-exit enumerations (pre-correction documents) fail.
DL-103 · Identity layer separation (ERROR). EID, MEID, and CMI SHALL never be presented as interchangeable; a document that assigns one identifier to two layers fails.
DL-104 · Replay profile completeness (ERROR). Every output CSI declared in produces SHALL appear in replay_profile.outputs or be covered by replay_profile.default (cross-reference: TG-RP §6.1).
DL-105 · No ungoverned scoring parameters (ERROR). Numeric weights, thresholds, modifier values, or ε values appearing outside the weight-bundle specification or example blocks explicitly marked <!-- example --> fail (cross-reference: TG-SG §5.1).
2.3. Class C — Structural Rules
| Rule | Severity | Check |
|---|---|---|
| DL-201 | ERROR | Canonical Identity table exists under a Canonical Identity heading and nowhere else (the TG-ENRICH failure mode) |
| DL-202 | ERROR | Input/Output CSI sections use one heading convention: ### Input CSIs / ### Output CSIs |
| DL-203 | WARN | CSI table rows follow the grammar comp.<SYS>.<DIR>.<NAME>.vN_N (legacy vera.* / siss.* prefixes flagged for migration) |
| DL-204 | WARN | Every table has a header row and separator row |
2.4. Class D — Hygiene Rules
| Rule | Severity | Check |
|---|---|---|
| DL-301 | ERROR | No duplicate-generation filenames (*-2.mdx, *-3.mdx, *-corrected.mdx) on main — corrections replace, never accumulate |
| DL-302 | ERROR | No two documents share a semantic_id or short_id |
| DL-303 | WARN | Orphaned CSIs: referenced in some document's consumes but defined in no document's produces (and vice versa) |
| DL-304 | WARN | last_updated older than version change in git history |
| DL-305 | INFO | Dead internal links / unresolved slugs |
3. Severity Semantics
- ERROR — CI build fails; the PR cannot merge. Reserved for rules whose violation would propagate a defect into tickets or code.
- WARN — CI annotates the PR; merge allowed; warnings accumulate in a weekly digest owned by the docs owner.
- INFO — Reported in lint output only.
Severity assignments are themselves governed: changing a rule's severity is a ZRR ruleset change, not a config edit.
4. CI Integration
Implementation lives in viroway-tools/docs-lint/ (Python; no runtime dependencies beyond PyYAML). It runs:
- On every PR touching
**/*.mdx— full corpus lint (cross-document invariants require the whole corpus, not just the diff). - Nightly on main — full lint + orphan/link analysis + digest.
# .github/workflows/docs-lint.yml
name: docs-lint
on:
pull_request:
paths: ['**/*.mdx']
schedule:
- cron: '0 4 * * *'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install pyyaml
- run: python viroway-tools/docs-lint/lint.py --config docs-lint.yml --fail-on error
# docs-lint.yml (excerpt)
canonical_pipeline:
- TG-PARSE
- TG-VALIDATE-STRUCTURE
- TG-NORM
- TG-ENRICH
- TG-RULE
- TG-POLICY
- TG-SCORE
- TG-DECISION
decision_exits: [ACCEPT, ROUTE, ESCALATE, QUARANTINE, REJECT]
csi_grammar: '^comp\.[A-Z]+\.(INPUT|OUTPUT|EVENT|SCHEMA)\.[A-Z0-9-]+\.v\d+_\d+$'
legacy_csi_prefixes: [vera., siss.]
The ticket generator SHALL consume only lint-clean corpora: it runs the linter as its first step and refuses drifted input.
The same rule applies to any other offline renderer of the corpus — for example, a future script that regenerates the Figma engine datasheets from spec frontmatter. Such renderers are documentation tooling in the same category as Docusaurus: one-way projections of the source of truth (docs → output, never back). They are not ZAYAZ components, and nothing in the platform depends on them; the rule exists only so that no rendering of the specs — page, diagram, or ticket — can be produced from a drifted corpus. Figma itself is a visualization surface, not part of the system.
5. Normative Rules
- Every
.mdxdocument in governed paths SHALL declare adoc_typeand satisfy its frontmatter schema. - Class B invariants are constitutional: adding or amending one SHALL be a ZRR ruleset change with CTO approval, DAL-anchored like any other governed artifact.
- A correction to a specification SHALL replace the specification in place (same filename, version bump, changelog entry). Suffix-file corrections (
-corrected,-2) SHALL NOT exist on main. - New cross-document contradictions discovered by any means (human review, AI session, audit) SHALL be encoded as a Class B rule within one release cycle — every found conflict permanently increases the corpus's immune system.
6. ZRR Ruleset Entry
ruleset: ZRR-DOCS-001
invariant: spec-corpus-is-lint-clean-on-main
scope: all .mdx under key-zayaz-features/
enforcement: CI (blocking on ERROR class) + nightly digest
amendments: Class B rule changes require CTO approval, DAL-anchored
A specification corpus that feeds code generation is code. This document gives it a compiler. Source of truth: Docusaurus.