Skip to content

fix(module): coverage over nothing is not coverage - #61

Merged
rldyourmnd merged 6 commits into
mainfrom
fix/coverage-over-nothing
Aug 31, 2026
Merged

fix(module): coverage over nothing is not coverage#61
rldyourmnd merged 6 commits into
mainfrom
fix/coverage-over-nothing

Conversation

@rldyourmnd

Copy link
Copy Markdown
Contributor

gds module coverage reported success having compared no module against any gate.

The defect

An estate whose repositories are not declared as git-submodule-consumer relationships gets:

{"result": "succeeded", "exit_class": "success", "data": {"modules": []}}

Success over an empty set. That is indistinguishable from every module being covered, and the estates most likely to hit it are exactly the ones asking whether their gates are watched at all.

setup-systems found it: they ran it against eight repositories expecting a verdict on fourteen required contexts each, and got green over nothing. This command exists to catch a gate drifting away from its declaration — the same class of defect arriving inside the tool meant to find it.

There was already a guard for the narrower case: --module <name> that matches nothing returns GDS_MODULE_COVERAGE_SELECTION_UNKNOWN. The unselected empty case had none.

The change

Empty scope now emits GDS_MODULE_COVERAGE_SCOPE_NOT_PROVEN, carrying declared_relationships so the reason is legible rather than inferred:

{"result": "not-proven", "exit_code": 3,
 "findings": [{"code": "GDS_MODULE_COVERAGE_SCOPE_NOT_PROVEN",
               "evidence": {"declared_relationships": 0, "repository_id": "repo_…"}}]}

Not-proven rather than validation. An estate with no declared modules is not invalid — a standalone repository legitimately has none. But nothing about its gates was proven either, and that is what exit 3 says. The classifier keys NOT_PROVEN in the code name to ExitNotProven, so the name carries the classification.

Testing

There were no tests for CoverModules at all, which is part of why this shipped.

The new test runs the real command against this repository, which declares no relationships, and asserts the specific finding code rather than a non-zero exit. That distinction turned out to matter: with XDG_CONFIG_HOME redirected to a temp dir — the pattern the neighbouring tests use — the command fails earlier at GDS_POLICY_ESTATE_NOT_PROVEN and never reaches this path. A test asserting only exitCode != 0 would have passed while covering nothing, which is the defect this PR fixes, in the test for it.

Verified by mutation: replacing the guard condition with false makes the test fail; restoring it passes.

Projections

core/app is one of the source paths the bundle lock digests, so the fix changed the source tree identity. Regenerated through gds generate repository plan/apply; --check reports no findings and the full suite is clean.

`gds module coverage` reported success having compared no module against
any gate. An estate whose repositories are not declared as
git-submodule-consumer relationships gets `"modules": []` and
`result: succeeded`, which is indistinguishable from every module being
covered.

The estates most likely to hit it are exactly the ones asking whether
their gates are watched. setup-systems ran it against eight repositories
expecting a verdict on fourteen required contexts each, and got green over
an empty set. That is the same class of defect this command exists to
find, arriving inside the tool meant to find it.

Empty scope now emits GDS_MODULE_COVERAGE_SCOPE_NOT_PROVEN, carrying the
declared relationship count so the reason is legible. Not-proven rather
than validation: an estate with no modules is not invalid, but nothing
about its gates was proven either.

There were no tests for CoverModules at all, which is part of why this
shipped. The new test runs the real command against this repository, which
declares no relationships, and asserts the specific finding code rather
than a non-zero exit -- with `XDG_CONFIG_HOME` redirected the command
fails earlier at GDS_POLICY_ESTATE_NOT_PROVEN and never reaches this path,
so a test asserting only the exit code would have passed without covering
anything. Verified by mutation: replacing the guard condition with `false`
makes it fail.
`core/app` is one of the source paths the bundle lock digests, so the
coverage fix changed the source tree identity and left both projections
stale against it. Regenerated through `gds generate repository`
plan/apply; `--check` now reports no findings.
…hing

# Conflicts:
#	.gds/bundle.lock.yaml
#	.github/workflows/gds-ci.yml
The merge with main brought in the schema-digest change's own regenerated
projections; both are generated from the same source paths, so the merged
tree needs one regeneration rather than either side's. `--check` reports
no findings.
The first version of this test lived in core/cli and was green on my
machine for the wrong reason: a registered estate happened to be
configured there, so the command reached the guard. CI has none, so
`gds module coverage` failed earlier at GDS_POLICY_ESTATE_NOT_PROVEN and
the test never touched the path it claimed to cover.

That is the defect this pull request fixes, reproduced inside its own
test. It was only visible because the assertion named the finding code; an
assertion on a non-zero exit would have stayed green over the wrong
refusal, locally and in CI alike.

Moved to core/app against `appTestRepositoryRoot`, which builds an
external estate in a temporary directory and exports GDS_ESTATE_ROOT, so
the test carries its own preconditions. Verified twice: mutating the guard
to `false` makes it fail, and it passes with HOME, XDG_CONFIG_HOME,
XDG_STATE_HOME and GDS_ESTATE_ROOT all cleared, which is the CI shape.
@rldyourmnd

Copy link
Copy Markdown
Contributor Author

The test I shipped first was green on my machine and red in CI, for exactly the reason this PR exists.

core/cli had it running the real command against this repository. That reached the guard locally only because a registered estate happened to be configured here; CI has none, so gds module coverage failed earlier at GDS_POLICY_ESTATE_NOT_PROVEN and the test never touched the path it claimed to cover. A test asserting exitCode != 0 would have been green over the wrong refusal in both places — I only saw it because the assertion names the finding code.

So the defect reproduced itself inside its own test, one layer up. Moved to core/app against appTestRepositoryRoot, which builds an external estate in a temp directory and exports GDS_ESTATE_ROOT, so the test carries its own preconditions instead of borrowing the developer's.

Verified in both directions again on the new location: mutating the guard to false makes it fail, and it passes with HOME, XDG_CONFIG_HOME, XDG_STATE_HOME and GDS_ESTATE_ROOT all cleared — the CI shape.

`core/app` is a digested source path in whole, so a test-only change to it
moves the source tree identity as much as a change to the command does.
@rldyourmnd
rldyourmnd merged commit fe24b9b into main Aug 31, 2026
7 checks passed
@rldyourmnd
rldyourmnd deleted the fix/coverage-over-nothing branch August 31, 2026 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant