Skip to content

RQ3 — CUJ/CLI coverage matrix #1282

Description

@njhensley

Parent: #1265 · Labels: enhancement, area/docs (auto, docs/**), area/ci (auto, .github/**); adds tools/coverage/** to .github/labeler.yml + CODEOWNERS (no glob exists for that root) · Size: M–L · Depends on: none hard for the generator skeleton; soft on the dynamic-clusters epic (DC3 CUJ2 wiring, DC4 CLI-boundary coverage) for the data the matrix reports to be non-trivial. RQ3 can ship the generator + an honest "exercised: no / planned" matrix first, then fill in as the dynamic-clusters epic lands. Listed first deliberately: it de-risks the epic by shipping value independent of #1224 and the TestGrid epic.

Goal. Promote CUJs and CLI-verb coverage from demo+chainsaw-only into a versioned, generated, public coverage matrix that runs on main at a cadence (never a merge gate), cross-linked to the other two surfaces.

Scope.

  • Define the coverage data model: for each CUJ (CUJ1 training/kubeflow, CUJ2 inference/dynamo) and each CLI verb (snapshot, recipe, query, bundle, verify, validate, evidence{digest,publish,verify}, diff, mirror, trust, skill — the actual pkg/cli/*.go command set), record which is exercised, by what (chainsaw / KWOK / UAT / GPU-nightly), on what hardware class, at what cadence. Honestly mark the verbs no CUJ touches today (query, diff, mirror, verify, trust, skill) as not-yet-covered until the dynamic-clusters epic's DC4 brings them under test.
  • Account for the AICR-version axis (consistent with the dynamic-clusters epic's version matrix, DC5). The data model also records which AICR versions — main + the previous N stable releases — are exercised per recipe on real hardware, and at what cadence, mirroring the dynamic-clusters epic's nightly version-parameterized UAT matrix ({aicr version} × {intent} × {cloud}); the matrix surfaces this axis rather than collapsing all versions into a single "covered" cell, so a reader can tell whether an older stable AICR is still exercised against a recipe. The per-version live posture itself stays a link into TestGrid (which carries aicr_version as a column facet), never embedded here.
  • Distinguish from Epic: Recipe health tracking (ADR-009 V1) — enumeration, structural soundness, public matrix #1224's Coverage column (cross-link, don't overlap). ADR-009 V1 already ships a per-recipe Coverage column in docs/user/recipe-health.md derived from declared_coverage (named checks per phase, e.g. R:2 D:4 P:1 C:10; 009-recipe-health-tracking.md:259-262). RQ3's coverage-matrix.md is a different axis: it answers "which CUJs / CLI verbs are exercised, where, at what cadence," not "how many checks per phase does this recipe declare." State this distinction in the matrix header and cross-link the two pages so a reader does not mistake one for the other.
  • Source the matrix from durable, in-repo signals where possible: the chainsaw CLI trees (tests/chainsaw/cli/*, e.g. cuj1-training/, validate-phases/, bundle-*), the UAT CUJ trees (tests/uat/{aws,gcp,azure}/tests/cuj{1,2}-*/), and demos/cuj{1,2}-*.md. Caveat to call out: tests/uat/azure/tests/cuj{1,2}-*/ exist but no workflow references them (Azure UAT is stubbed — the dynamic-clusters epic's DC6 owns the revive-or-retire decision); the matrix must reflect Azure as stubbed/not-running, not as covered, and link to DC6.
  • New tools/coverage Go generator (cloning tools/bom / the planned tools/health shape): walks the in-repo signals, emits a deterministic Markdown table spliced into docs/user/coverage-matrix.md, with -deterministic -no-title flags mirroring tools/bom. MDX gate (load-bearing). make lint (inside make qualify) runs tools/check-docs-mdx, which fails any file under docs/user/ that has HTML comments (<!-- … -->) outside fenced code blocks (Check 3, tools/check-docs-mdx:78-79). The only reason container-images.md (the BOM output this clones) passes is a hard-coded single-file exclusion EXCLUDE="container-images.md" (tools/check-docs-mdx:30). Prefer option (b): use a splice marker that is NOT an HTML comment outside a code fence (an MDX-safe sentinel, e.g. inside a fenced block or an MDX comment {/* … */}), so coverage-matrix.md stays inside the MDX gate. Option (a) — refactoring the single EXCLUDE scalar into a multi-file allowlist and adding coverage-matrix.md — is acceptable only as a fallback, because each allowlisted file silently loses MDX/Fern-render validation and the allowlist will accrete every future generated page; if (a) is taken, add a test asserting allowlisted files still render under Fern so the carve-out is not an unguarded blind spot. Without one of these, the page trips the MDX gate and Epic AC fix: use workflow_run for PR coverage comments on fork PRs #5 ("pass make qualify") fails. make coverage-docs regenerates; make coverage-check is an opt-in staleness check paralleling bom-check (explicitly not wired into make qualify/lint/merge gate).
  • New .github/workflows/coverage-matrix-refresh.yaml, weekly clone of the shipped bom-refresh.yaml (cron, peter-evans/create-pull-request on drift, built-in GITHUB_TOKEN, chore/coverage-refresh branch, delete-branch: true, explicit documentation label; area labels (area/docs, area/ci) auto-assign from changed paths via labeler.yml). It refreshes the structural matrix (which tests exist) weekly; the live "did it pass on hardware" answer is a link into TestGrid, not embedded here (same link-only rule as RQ1).
  • Docs same PR: register docs/user/coverage-matrix.md in docs/index.yml under User Guide (mirrors ADR-009's registration of recipe-health.md, 009-recipe-health-tracking.md:409); cross-link from docs/user/recipe-health.md (RQ1) and docs/user/cli-reference.md. fern-docs-ci.yaml lychee + MDX-safety run on the new docs/** page on its introducing PR (hence the splice-marker requirement above).
  • labeler.yml + CODEOWNERS: add tools/coverage/** (CODEOWNERS precedent: ADR-009's planned tools/health/** entry, 009-recipe-health-tracking.md:416).

Out of scope. Running the CUJs (the dynamic-clusters epic owns execution; RQ3 reports). A merge gate. Embedding live pass/fail (link to TestGrid). Per-verb unit-coverage % (that's make test-coverage, a different gate). #1224's per-recipe declared_coverage column (orthogonal; cross-link only).

Key files. New tools/coverage/ (+ labeler.yml/CODEOWNERS entries); new docs/user/coverage-matrix.md; docs/index.yml; docs/user/cli-reference.md; tools/check-docs-mdx (splice-marker-safe rendering, or the EXCLUDE allowlist fallback); Makefile (coverage-docs/coverage-check targets paralleling bom-docs / bom-check). Signal sources: tests/chainsaw/cli/*, tests/uat/{aws,gcp,azure}/tests/cuj{1,2}-*/, demos/cuj{1,2}-*.md, pkg/cli/*.go. Pattern source: .github/workflows/bom-refresh.yaml, tools/bom.

Tests. Generator unit tests: deterministic table output across two runs (byte-compare, not struct DeepEqual); a fixture set of chainsaw/UAT/demo trees produces the expected coverage rows; the verb list is derived from the actual pkg/cli command registry (so a new verb without coverage shows up as a not-yet-covered row rather than being silently dropped); Azure rows render as stubbed; make coverage-check flags a deliberately-stale doc. Clears the 75% floor on tools/coverage measured with pkg=tools/coverage, and clears the baseline-delta gate (no > 0.5% per-package regression; no new exported func at 0%).

Acceptance. docs/user/coverage-matrix.md is generated, versioned, registered, and cross-linked (including the explicit distinction from #1224's declared_coverage column); runs on main weekly via a clone of the BOM loop; passes the MDX gate (via an MDX-safe splice marker, or the guarded check-docs-mdx allowlist fallback); never gates merges; honestly reflects covered / not-yet-covered / stubbed; links to TestGrid for live posture rather than embedding it.


Child of #1265 · staged from docs/design/012-recipe-quality-surfaces.md (RQ3).

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions