This page defines how coverage-matrix turns one doc-source.json and one ui-tests.json into
coverage-matrix.json: entities and rows, summaries, view, envelope, warnings and lineage. Developers who change the
command, and readers of its output, read it.
Read before: normalize-tests · Next: Action reference
- Modules
- What a run does
- Entities and rows
- Summaries
- View and gate
- The envelope
- Warnings
- Lineage
- Tests
living-doc-service-coverage-matrix turns one doc-source.json and one ui-tests.json, as a collector wrote them,
into coverage-matrix.json, the file a generator reads.
| Module | What it holds |
|---|---|
living_doc_service_coverage_matrix.builder |
build_coverage_matrix(documentation, tests, *, view, generated_at): the inputs' contract models to the output's. It reads and writes no file. |
living_doc_service_coverage_matrix.lineage |
LINEAGE_TABLE, the command's lineage table over both inputs |
living_doc_service_coverage_matrix.service |
coverage_matrix(doc_inputs, tests_input, output_path, *, view), the command, and check_no_field_loss(result) |
- The three files are
living-doc-utilitiescontracts,doc-source-v1.0.0,ui-tests-v1.0.0andcoverage-matrix-v1.0.0; this package keeps no copy of any of them - The CLI registers it as
living-doc coverage-matrix, with the--fail-undergate, and the action runs it ascommand: coverage-matrix→ CLI, Commands - Coverage is counted per declared aspect; a scenario link not counted as written is a warning in the file → Entities and rows, Warnings
A run takes these steps; nothing is written when one fails:
- Both inputs go through the shared input validation,
validate_inputs("coverage-matrix", …), and nothing else reads them (Shared input validation). A failing step stops the run before anything is built; a second documentation file, or a repeated entity or criterion id, fails there. - The builder builds the matrix from the validated models, and the command sets its
metadata.runto the CI run. check_no_field_losscompares the lineage table's paths against the documentation input (Lineage); a lost field stops the run.write_artifactvalidates the matrix, then writes it.
The builder gives each User Story and Functionality an entry and a row per counted criterion:
- There is one
entities[]entry per User Story and Functionality, whatever its state and even with no counted criterion. Itstype,titleandstateare the entity's own - There is no Feature entity
- An entity has a row per
activeordeprecatedcriterion, with that criterion'sstate; aplannedor anin_reviewcriterion has no row - A link matches a criterion by its
id, whatever repository the scenario is in - A criterion that declares aspects, with
Aspect:or a keyword, gets anaspects[]entry per declared value, in declared order- An aspect lists the
scenario_idof every scenario whose link names that aspect. A bare@AC:<id>link, with no aspect, lists its scenario under every aspect. - An aspect is
coveredwith at least one scenario andnot_coveredwith none. The row'sstatusfollows its aspects,covered,partially_coveredornot_covered, and its ownscenario_idsstays empty. - A keyword criterion's row carries
keyword, the criterion's oneplaceholder_valueskey, as stored (rule); anAspect:row carries none. - The keyword's name in a scenario tag is not stored, so it is not checked.
- A link to an aspect the criterion does not declare counts nowhere (Warnings).
- An aspect lists the
- A criterion without aspects gets a plain row: it lists the
scenario_idof every linking scenario, and iscoveredwith at least one andnot_coveredwith none. It carries no aspect and no keyword- A link with an aspect covers it too, as a bare one does, and is a warning: the scenario names the criterion, and the criterion has one thing to cover.
- A scenario is listed once per aspect, and once per plain row, however many of its links reach it
- Order: every User Story, then every Functionality, each in input order. Rows follow the entity's own criteria, aspects the criterion's declared values, and the scenarios the tests input. Nothing is sorted
- Which criteria are counted, and what each status means, are the rendering page's § Coverage → rendering
- How a keyword declares its values is the AC grammar page's § Variants → AC grammar
- Each entity's
summarycomes fromCoverageSummary.from_rowsover its own rows, and the rootsummaryover every row of every entity- An entity without a row counts
0criteria and has no percentage,null.
- An entity without a row counts
planned_summarycounts theplannedcriteria of User Stories and Functionalities- One with a
versiongoes underby_target_version, in the order first met, and one without inbacklog;totalis their sum. - An
in_reviewcriterion is in neither.
- One with a
- The rules are the rendering page's § Coverage summary and § Planned work summary → rendering
--viewsetsdocument.view,innerby default, and nothing else: both views have the same rows and the same counts. What a view shows is the generator's--fail-underis the CLI's gate on the rootcoverage_pct, run after the file is written; the service has no gate → CLI
The fields coverage-matrix fills its own way; every command fills the rest the same way, as the core's Shared envelope says:
| Field | Value |
|---|---|
metadata.source |
the documentation input's, with each of the tests input's systems it does not name added once |
metadata.stats |
from the matrix's own entities |
metadata.source_inputs[] |
the documentation input's entry, then the tests input's, each with its schema_version, producer, run, source, stats and selected_stats |
document.view |
--view |
metadata.sourcemerges the tests input'ssystemsinto the documentation input's- Each input's own
runstays in itssource_inputs[]entry - The documentation entry's
selected_statsare computed over the records the matrix counts: the User Stories and Functionalities, each with only itsactiveanddeprecatedcriteria, and no Feature- Its
statsagainst itsselected_statsshows what the matrix leaves out.
- Its
- The tests entry's
selected_statsare computed over all its scenarios, so they equal its occupancy
Every input warning reaches the output through the core's forwarded: the documentation input's first, then the tests input's, each in input order, its context starting with source_input='doc-source-v1.0.0' or source_input='ui-tests-v1.0.0'.
After them, the command adds a warning for each scenario link it does not count as written:
| Code | The link names | The link |
|---|---|---|
STALE_AC_REF |
a criterion no User Story or Functionality in the documentation input has | counts nowhere |
STALE_AC_REF |
an aspect its criterion does not declare | counts nowhere; on a criterion without aspects, covers it |
PLANNED_AC_HAS_TESTS |
a planned criterion, whatever the aspect |
counts nowhere; the criterion has no row |
IN_REVIEW_AC_HAS_TESTS |
an in_review criterion, whatever the aspect |
counts nowhere; the criterion has no row |
ac_idis the link's criterion, andentity_idthat id without its last-<n>.line_no,pathandsource_urlstay unset: a collector fills themmessagenames the scenario, the criterion and an undeclared aspect.contextholds them asscenario_id='<id>', then a space andaspect='<value>'for an undeclared aspect- There is one warning per scenario and criterion, or per scenario, criterion and undeclared aspect. They follow scenario order, then link order
- The run still succeeds, and no command logs a warning: the warnings are the file's
- A scenario on a
plannedorin_reviewcriterion keeps its link inui-test-catalog→ normalize-tests - The codes and their fields are the errors page's § Codes and § How codes are reported → errors
LINEAGE_TABLE is one table over both inputs: the 162 doc-source-v1.0.0 paths and the 12 ui-tests-v1.0.0 paths.
| Input path | Output path |
|---|---|
user_stories[] and functionalities[]: entity_id, type, title, state |
entities[].entity_id, .type, .title, .state |
user_stories[] and functionalities[]: acceptance_criteria[].id |
entities[].acceptance_criteria[].ac_id |
user_stories[] and functionalities[]: acceptance_criteria[].state |
entities[].acceptance_criteria[].state |
user_stories[] and functionalities[]: acceptance_criteria[].aspect[] |
entities[].acceptance_criteria[].aspects[].aspect |
user_stories[] and functionalities[]: acceptance_criteria[].placeholder_values |
entities[].acceptance_criteria[].keyword |
scenarios[].scenario_id |
entities[].acceptance_criteria[].scenario_ids[] |
- It is declared here, and
assert_complete(LINEAGE_TABLE, "doc-source-v1.0.0")andassert_complete(LINEAGE_TABLE, "ui-tests-v1.0.0")run in this package's tests only - Every other path is
Dropped, with its reason- Every
features[]path, as the matrix has no Feature entity. - The other entity and criterion fields, as the matrix carries coverage, not content.
- Each link's
idandaspect, as they decide which row, and which aspect of it, lists the scenario. - The other
scenarios[]paths, as a row names its scenarios by id.
- Every
Before write_artifact, check_no_field_loss computes the matrix's stats as write_artifact will, and calls
check_field_loss once, with the documentation entry's selected_stats; the tests entry is never compared.
- It compares against the documentation input only, as the component checks' § R11 says: the one documentation input makes the comparison well defined → component checks
- Why not the tests input: a tests file linking no counted criterion would falsely fail with
FIELD_LOSSonscenarios[].scenario_id.
- Why not the tests input: a tests file linking no counted criterion would falsely fail with
- It compares against
selected_statsover the counted records, not over the whole input- Why: over the input's own stats, an all-
plannedfile writes no row and fails withFIELD_LOSSonacceptance_criteria[].idand.state. - So a new project with only planned work would get no matrix. Over the counted records, that file gets its matrix, and a builder that empties every entity's rows still fails.
- The artifact rules' R7 defines
selected_statsby the view filter. This command's view filters nothing, and its count rule plays that part; the contract pages do not state that reading yet → artifact rules
- Why: over the input's own stats, an all-
- It checks every mapped path, the criteria's declared aspects and keywords included: a builder dropping every row's aspects fails on
acceptance_criteria[].aspect[]and.placeholder_values, and one dropping only keywords on.placeholder_values user_stories[]andfunctionalities[]map to the same output paths. A criterion, aspect or keyword lost from one list while the other fills its path is a partial drop; R11 compares aggregate occupancy and does not detect itFIELD_LOSSstays a hard error when the counted records have data in a mapped path and the matrix has none
- The fixture pair's matrix is compared with the committed golden, masking only
metadata.generated_at; the release view differs from it only indocument.view - Built inputs cover:
- counted and uncounted states, and a
deprecatedrow; - rows with every, some and no aspect covered;
- a bare link on a criterion with aspects, and a scenario linking a criterion twice;
- a keyword criterion beside an
Aspect:one; - each link warning with its fields and order;
- the planned-work counts and the merged
source; - both
source_inputs[]entries, and forwarding from both inputs.
- counted and uncounted states, and a
full_sample("doc-source-v1.0.0")andfull_sample("ui-tests-v1.0.0")are written withwrite_artifactand run through the command in both views, shared input validation included- Their entities, with every row, aspect, keyword and summary, their root
summaryand theirplanned_summaryequalfull_sample("coverage-matrix-v1.0.0")'s. They raise no warning. - The entities are compared by id: that sample lists them in another order.
- Their entities, with every row, aspect, keyword and summary, their root
- A builder dropping a mapped field, the rows, titles, aspects or keywords fails with
FIELD_LOSSand writes no file