Summary
Measuring nldesign's true gate-19 debt (root-commit-scoped, per #242) gives 190 findings. 34 of them are false positives in one specific branch: @e2e tag present but the test does not run: referenced only by a test that never runs (<file>).
Every file named in those 34 has passing tests in the same CI run. The real debt is 156.
This is the counting hazard in its concrete form: a finding count is not a defect count, and here the inflation is 18%.
Measured
$ run-hydra-gates.sh --scope-to-diff --base <root commit>
[hydra-gates] SCOPE-FILE-COUNT: 3611
[gate-19] e2e-coverage: FAIL — 190 scenario(s) missing @e2e
Split by reason (191 log lines, 191 distinct — no duplicate-emission inflation, so the 34 are the whole story):
| reason |
count |
verdict |
— missing @e2e |
156 |
genuine |
— @e2e tag present but the test does not run |
34 |
false positive |
Files named in the 34, against passing tests in nldesign CI job 93093896316:
| file |
flagged as "never runs" |
tests that PASSED in that run |
admin-settings.spec.ts |
14 |
17 |
token-editor-ui.spec.ts |
6 |
8 |
app-theming.spec.ts |
5 |
4 |
token-set-apply-dialog.spec.ts |
3 |
5 |
token-set-contrast-audit.spec.ts |
2 |
2 |
custom-token-set-upload.spec.ts |
2 |
4 |
token-set-apply-toggle-behavior.spec.ts |
1 |
3 |
token-import-export.spec.ts |
1 |
2 |
Not one of them is a file whose tests never run.
Five flagged anchors matched to passing tests, verbatim
The scenario names and the CI ✓ lines are the same tests:
admin-settings::settings-panel-appears-in-admin-area -> ✓ 10 admin-settings.spec.ts:24:6 › Settings panel appears in admin area (3.2s)
admin-settings::settings-panel-position-relative-to-nextcloud-theming -> ✓ 11 admin-settings.spec.ts:35:6 › Settings panel position relative to Nextcloud theming (2.6s)
admin-settings::dropdown-populated-with-token-sets -> ✓ 12 admin-settings.spec.ts:78:6 › Dropdown populated with token sets (2.6s)
admin-settings::dropdown-label-is-associated-with-select -> ✓ 13 admin-settings.spec.ts:95:6 › Dropdown label is associated with select (2.6s)
admin-settings::preview-box-renders-with-token-set-colors -> ✓ 15 admin-settings.spec.ts:133:6 › Preview box renders with token set colors (2.8s)
admin-settings.spec.ts contains test.describe('admin-settings', () => { — a plain describe, not .skip — 17 test( blocks, and zero test.skip / test.fixme / describe.skip anywhere in the file.
Likely mechanism
nldesign writes the tag inside the test( call, between the opening paren and the title:
test(
// @e2e openspec/specs/admin-settings/spec.md#settings-panel-appears-in-admin-area
'Settings panel appears in admin area',
async ({ page }) => {
await page.goto(THEMING_URL)
...
},
)
The helper's forward search from the tag looks for the test( that owns it — but here test( is on the line above the tag. The search runs forward, so it either finds the next test's declaration or runs off the end of the file, and the "no live declaration found" path reports "referenced only by a test that never runs".
That fits the distribution: 14 of 17 tagged tests in admin-settings.spec.ts are flagged, and the file's tag layout is uniform.
The helper already carries a comment about this class of bug — "never runs", about a file whose tests run fine — so the ancestor/describe.skip direction has been fixed. The declaration-above-the-tag layout looks like the remaining one.
Suggested fix
When resolving a tag to its owning test, search backwards as well as forwards for the nearest test( / it( declaration, and prefer the nearer of the two. Both layouts are in fleet use:
// @e2e ... | test(
test('title', ...) | // @e2e ...
| 'title',
Whatever the fix, the discriminator worth adding is: "I could not resolve this tag to a declaration" is not "the test never runs." They are different facts and only the second is a finding — the same distinction gate-5 already draws between "the attribute is absent" and "I could not open the class".
Why this was only visible at the root commit
A normal run scopes to the diff, so these never appear. A Scope: full repo run does not help either: gate-19 is unconditionally diff-scoped inside its helper, so an empty diff makes it call _pass (#242). It takes --scope-to-diff --base <root> to see any of it.
Related: #242 (gates 19/25/63 invisible to --require-full-coverage), #243 (set -e leak aborts the run mid-way).
Summary
Measuring nldesign's true gate-19 debt (root-commit-scoped, per #242) gives 190 findings. 34 of them are false positives in one specific branch:
@e2e tag present but the test does not run: referenced only by a test that never runs (<file>).Every file named in those 34 has passing tests in the same CI run. The real debt is 156.
This is the counting hazard in its concrete form: a finding count is not a defect count, and here the inflation is 18%.
Measured
Split by reason (191 log lines, 191 distinct — no duplicate-emission inflation, so the 34 are the whole story):
— missing @e2e— @e2e tag present but the test does not runFiles named in the 34, against passing tests in nldesign CI job
93093896316:admin-settings.spec.tstoken-editor-ui.spec.tsapp-theming.spec.tstoken-set-apply-dialog.spec.tstoken-set-contrast-audit.spec.tscustom-token-set-upload.spec.tstoken-set-apply-toggle-behavior.spec.tstoken-import-export.spec.tsNot one of them is a file whose tests never run.
Five flagged anchors matched to passing tests, verbatim
The scenario names and the CI
✓lines are the same tests:admin-settings.spec.tscontainstest.describe('admin-settings', () => {— a plain describe, not.skip— 17test(blocks, and zerotest.skip/test.fixme/describe.skipanywhere in the file.Likely mechanism
nldesign writes the tag inside the
test(call, between the opening paren and the title:The helper's forward search from the tag looks for the
test(that owns it — but heretest(is on the line above the tag. The search runs forward, so it either finds the next test's declaration or runs off the end of the file, and the "no live declaration found" path reports "referenced only by a test that never runs".That fits the distribution: 14 of 17 tagged tests in
admin-settings.spec.tsare flagged, and the file's tag layout is uniform.The helper already carries a comment about this class of bug — "never runs", about a file whose tests run fine — so the ancestor/
describe.skipdirection has been fixed. The declaration-above-the-tag layout looks like the remaining one.Suggested fix
When resolving a tag to its owning test, search backwards as well as forwards for the nearest
test(/it(declaration, and prefer the nearer of the two. Both layouts are in fleet use:Whatever the fix, the discriminator worth adding is: "I could not resolve this tag to a declaration" is not "the test never runs." They are different facts and only the second is a finding — the same distinction gate-5 already draws between "the attribute is absent" and "I could not open the class".
Why this was only visible at the root commit
A normal run scopes to the diff, so these never appear. A
Scope: full reporun does not help either: gate-19 is unconditionally diff-scoped inside its helper, so an empty diff makes it call_pass(#242). It takes--scope-to-diff --base <root>to see any of it.Related: #242 (gates 19/25/63 invisible to
--require-full-coverage), #243 (set -eleak aborts the run mid-way).