Skip to content
The CNA BibleCNA 0.1.0-alpha.1 Edition

Chapter 72 Discipline as a Test

Many CNA regressions would compile, render a plausible frame, and pass ordinary API tests while violating the architecture or historical era a renderer is meant to preserve. The project turns those negative constraints into executable checks: forbidden-token scans, link-closure probes, identity-table comparisons, document-to-code validators, log parsers, and mutation tests. In this layer the gate itself is the principal artifact.

72.1 DirectX eras as executable boundaries

Eight shell gates scan the source and public headers of DirectX 1, 2, 3, 5, 6, 7, 8, and 10. They strip comments, search for forbidden interface names or call shapes, and fail if the source directory is missing. Each forbidden list encodes an architectural statement:

  • DirectX 1 permits only first-generation DirectDraw and no Direct3D;

  • DirectX 2 forbids execute buffers, while DirectX 3 differs by allowing IDirectDraw2;

  • DirectX 5 and 6 require version-4 surfaces and reject the bare legacy surface name;

  • DirectX 7 forbids viewport objects, texture-handle indirection, and a texture-blend state empirically rejected under Wine;

  • DirectX 8 removes DirectDraw and requires modern state names;

  • DirectX 10 rejects both the FVF model and D3D9-style render-state calls.

The later scripts use a line-preserving Perl pass to remove both line and block comments. That upgrade became necessary when Doxygen prose began matching forbidden tokens. Preserving line numbers keeps violations actionable. The lesson is not that grep replaces compilation; it is that a focused lexical invariant can reject historically impossible shortcuts that a compiler would happily accept.

72.2 Link closures make module boundaries observable

Fourteen minimal consumers link public module aliases. A companion Python gate reads each generated link.txt, rejects forbidden libraries, and can require expected ones. Math, for example, must not acquire a graphics, SDL, ENet, or renderer dependency merely because a transitive umbrella target makes the program link.

The gate returns skip 77 when a generator does not produce link.txt. That is honest but weakens the evidence, so reports need generator and skip data. The broader pattern remains valuable: compile proves usability; link inspection proves dependency direction.

Renderer identities receive a similar structural check. An order-sensitive canonical table is compared with the C++ enum and set-wise with the CMake cache choices. This detects missing, stale, reordered, or unselectable identities before a user discovers the mismatch at configure time.

72.3 Documents can be machine-checked contracts

Six Skia validators connect ledgers and decision records to implementation. They require every renderer method and capability to have a classified parity row; classify every test-matrix entry; hold the accepted 2D-only decision to 37 enumerated 3D requirements; check release-gate row contiguity and Ganesh guards; require successor-contract routes to cover an exact numeric range; and compare a 27-row surface-format table with the live enum and size functions.

These checks turn documentation from commentary into schema-bearing input. They also reveal the maintenance cost. At the audited revision, three registered Skia validators still read paths removed by modularization, and the native HLSL verifier has the same kind of path rot. A gate that aborts before reaching its invariant is not evidence, even if its design is sound.

Every validator should therefore test its own assumptions: required files exist, anchors occur exactly once, parsed sets are non-empty, and fixtures include both accepted and rejected cases. Treating a missing input as success would be the worst possible failure mode.

72.4 Can the green test go red?

Direct2D’s mutation checker applies seven pinned edits to the renderer. Every anchor must occur exactly once. In full --run mode it rebuilds and runs the named test; if the test still passes, the mutation survived and the checker fails. Source is restored in a finally path.

Mutation testing asks a question ordinary execution cannot answer: is the oracle sensitive to the defect it claims to prevent? CI currently performs only the dry-run anchor validation, not the seven rebuild-and-test mutations. That distinction belongs in any claim about mutation coverage.

The Direct2D debug-log verifier applies the same self-suspicion differently. It fails if diagnostics never engaged, the debug layer was off, the report was truncated, severe messages appeared, or unexpected live objects remained. Six checked-in fixtures exercise the parser’s pass and failure paths through --self-test. A log parser without hostile fixtures is only an untested second program.

72.5 Automation is narrower than apparatus

The tag’s script layer is reached through CTest registrations, direct workflow commands and source-only generated-record checks. CTest wiring still matters only when the corresponding configuration is built and run; a Python checker in a workflow is a different population from a renderer executable registered conditionally.

The 21 workflows tell an uneven but broader story. Nineteen have push or pull-request triggers and Direct3D/GDI are manual-only. Multi-renderer, platform/audio, Apple, glTF, TinyGL, NanoVG and C API records now have dedicated routes. The Windows D3D workflow’s unrestricted default build includes CnaTests, despite a stale header comment saying otherwise. Its renderer-label selectors do not run the general discovered corpus, although the Direct2D leg does run the filtered Direct2D_Unit entry through that binary. The GDI workflow instead names 17 standalone targets and never builds CnaTests. Metal has a macOS workflow, HTML-DOM has a browser workflow, and Devices has a UBSan path. Conversely, the general job and two Input legs still use the invalid EASYGL value and never reach compilation; more files do not imply broader executed coverage.

Automation records should therefore distinguish four states: script exists, script is a CTest, workflow references it, and workflow triggers for the change in question. Collapsing them into “covered by CI” hides exactly the gaps the gates were built to expose.

72.6 A gate-quality checklist

A structural gate is trustworthy when it:

  1. 1.

    states one invariant in language a reviewer can compare with the code;

  2. 2.

    proves that its input and engagement conditions were present;

  3. 3.

    fails closed on missing or empty inputs, or reports an explicit skip whose consequence is retained;

  4. 4.

    carries positive and negative fixtures, mutation, or another self-test showing that the verdict can change;

  5. 5.

    records its configuration and is actually wired into the intended automation path;

  6. 6.

    is updated atomically when modularization moves its inputs.

The unifying principle is that a green result that was never produced is worse than a red one. Wine log tokens, compile-must-fail tests, link closures, mutation inversion, diagnostics-engaged checks, and browser verdict channels are independent expressions of that idea. Discipline becomes a test when the project can prove both the rule and the fact that the rule was applied.

Type at least three characters. Results are ranked by how often and where the words occur.