CNA-VGAP-019: The HEADLESS native-SDK-free link-closure gates run in no CI job
Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 3b43bd86; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
The link-closure check reads a Ninja tree's link line, so general-tests-ci.yml evaluates its OPENGLES3 tree's gates; the NativeSdkFree_* and GraphicsNativeSdkFree gates exist only in single-renderer HEADLESS trees, which no CI job tests through ctest.
- Identifier
CNA-VGAP-019- Category
- Verification gap
- Subsystem
- Testing & evidence
- Status
- Narrowed (partially fixed; describes only what survives)
- Verified against
- CNA
3b43bd86(3b43bd8687ed46523d873f9bec4b32bd8481dcb5) - Evidence basis
- Source-verified: read at TARGET, not executed
- Tests touching this area
- Yes: see Current tests
- Affected contract
- The ModuleLinkClosure_NativeSdkFree_* and ModuleLinkClosure_GraphicsNativeSdkFree CTests registered by cmake/Tests/ModuleProbes.cmake
Expected behaviour
ModuleProbes.cmake describes each probe and its ModuleLinkClosure_* partner as "turning every module's real dependency closure into a permanent contract", and check_module_link_closure.py as "a permanent, mechanically checked contract": for example probe_math may link nothing but the math archive and Sharp Runtime, and the HEADLESS tree must show that renderer-neutral graphics needs no native graphics SDK. A contract of that kind is evidence only if some routine run evaluates it.
Actual behaviour at TARGET
check_module_link_closure.py reads link.txt or, in a single-config Ninja tree, the target's final command from ninja -t commands -s, and returns 77 only when neither exists, so the unfiltered ctest of general-tests-ci.yml (Ninja, OPENGLES3) evaluates every closure gate that tree registers. ModuleProbes.cmake registers the native-SDK-free gates (ModuleLinkClosure_NativeSdkFree_*, ModuleLinkClosure_GraphicsNativeSdkFree) only when CNA_GRAPHICS_RENDERER is HEADLESS without multi-renderer. The CI jobs that configure a HEADLESS tree leave these gates out: gltf-renderer-stride-ci.yml runs ctest -L gltf-conformance, platform-ci.yml only the CnaPlatform* suites, and multi-renderer-ci.yml's single-renderer HEADLESS control builds only CnaTests and runs the binary directly.
Source locations
scripts/check_module_link_closure.py— main and ninja_link_line: link.txt, else the Ninja final command, else exit 77cmake/Tests/ModuleProbes.cmake— the HEADLESS-only NativeSdkFree_*/GraphicsNativeSdkFree registrations.github/workflows/general-tests-ci.yml— -G Ninja OPENGLES3 configure and the unfiltered ctest run.github/workflows/gltf-renderer-stride-ci.yml— the HEADLESS cell runs ctest -L gltf-conformance only.github/workflows/multi-renderer-ci.yml— single-renderer HEADLESS control: builds CnaTests and runs it directly
Evidence
Checked by reading check_module_link_closure.py, ModuleProbes.cmake and every workflow under .github/workflows for its renderer, generator and ctest selection at 3b43bd86; not executed.
Narrowed at c1c316b9: 12ab7471b (AM4-167) added ninja_link_line, so the gates no longer report SKIP in a single-config Ninja tree; CNA records 28 of 28 gates measured and passing on its Mac (plans/plan_apple_m4.md, AM4-167). A multi-config generator still gets exit 77.
Re-verified at c6dec91f: the second gate this entry tracked belonged to a renderer retired on 2026-10-13 (fe5e25994) and is gone with it; the HEADLESS gates are still selected by no CI job.
Focused reproduction
No focused reproduction is known. Nothing has been invented here; the evidence above is what exists.
Current tests
The gates are the tests. The OPENGLES3 tree's gates (ModuleLinkClosure_probe_math, _probe_storage and the rest) run in general-tests-ci.yml; the HEADLESS gates run only where someone runs ctest by hand in such a tree.
Regression test
Add -R '^ModuleLinkClosure_' to a single-renderer HEADLESS cell (for example in gltf-renderer-stride-ci.yml), or give the gates a label one of those jobs already selects.
Blast radius
Evidence only: a native graphics SDK leaking into the renderer-neutral HEADLESS closure would not be caught by any automated run. No shipped behaviour is affected.
Workaround
Evaluate the gates in a single-renderer HEADLESS tree configured with CNA_BUILD_TESTS=ON: ctest -R '^ModuleLinkClosure_'.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- Maintainer workflow
- Conventions that are enforced
- Reference
- Test targets
- Known issues
- Verification gap index