You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
gate-19: one requirement-level @e2e exclude silently exempts every sibling scenario — 471 of openbuild's 509 exclusions share a reason (42 reasons, one covers 32) #356
A single @e2e exclude <reason> written at requirement level exempts every scenario under that requirement — including scenarios the stated reason does not describe. Combined with #345 (an exclude is read as positive coverage), one sentence can turn up to 32 scenarios green.
Measured on ConductionNL/openbuild@development, using the gate's own parser:
scenarios 767 excluded 509
distinct exclusion reasons: 80
reasons covering MORE THAN ONE scenario: 42
scenarios excluded by a SHARED reason: 471 <- 92.5% of all exclusions
Top fan-outs — one reason, N scenarios:
N
spec
reason (truncated)
32
application-versions
pure-backend schema/lifecycle/CRUD spec — all scenarios are OR REST contracts…
pure-backend schema/data spec — all scenarios are OR REST + repair-step contracts…
Note how many begin with "mixed spec — …". The author is explicitly saying some of these scenarios are backend-only and some are not, and the tag then exempts all of them uniformly.
A worked example where the reason is provably wrong for the scenario it exempts
### Requirement: REQ-SAT-001 "Save as template" action and dialog on the application-detail surface
@e2e exclude pure-logic capture contract — verifying the captured record carries schema
definitions only and no object rows is a property of the pure `captureTemplate` function,
covered by Vitest; no Playwright-observable UI surface for record internals.
...
#### Scenario: Viewer cannot save a template
- **GIVEN** a user with only viewer rights on the Application
The reason is about the internals of a captured record. The scenario it silently exempts is about whether a viewer sees a button — as Playwright-observable as anything in the suite, and writable today (tests/e2e/global-setup.ts mints .auth/rbac-viewer.json).
Same shape at REQ-SAT-005, whose reason is about the canManage Vitest tests and swallows Scenario: Seeded cards remain read-only.
Why this is hard to see from outside
Report mode prints excluded: 509 as a single number, so a reason doing the work of 32 looks exactly like 32 reasons doing the work of one.
coverage_pct is computed as covered / (scenarios − excluded) — openbuild reports 27.9%, which sounds like a coverage problem in the 258 remaining scenarios and says nothing about the 509 removed from the denominator. The unqualified figure is 72/767 = 9.4%.
I have not implemented this, on purpose: any change to exclusion accounting reddens many repos at once, and #347 is already being held for that reason. Landing it needs to be sequenced.
Two changes, neither of which fails a build:
Report mode: emit per-reason fan-out — {reason, spec, requirement, scenario_count, scenarios[]} — so a reason covering 32 scenarios is visible as one line. Also emit the unqualified covered / scenarios alongside the exclusion-adjusted percentage.
Gate mode: print an advisory (not a failure) when one reason exempts more than N scenarios, naming the requirement — [gate-19] NOTE: 1 exclusion reason exempts 32 scenarios in application-versions.
A stricter follow-up worth discussing separately: require a requirement-level exclusion to be repeated per scenario once it would exempt more than one, forcing the author to re-read the reason against each scenario. That one would redden repos, so it should be staged behind a flag.
The general lesson
⚠️An exemption's reason is a testable claim, and it is only tested against the scenario the author had in mind while writing it. Every sibling scenario inherits a justification nobody ever checked against it. In this repo that is 471 scenarios carrying a reason written for something else.
Related: #343 (file-level @e2e tag credited without checking the test body), #345 (an @e2e exclude read as POSITIVE coverage).
Concrete instance in an app repo: ConductionNL/openbuild#178.
Summary
A single
@e2e exclude <reason>written at requirement level exempts every scenario under that requirement — including scenarios the stated reason does not describe. Combined with #345 (an exclude is read as positive coverage), one sentence can turn up to 32 scenarios green.Measured on
ConductionNL/openbuild@development, using the gate's own parser:Top fan-outs — one reason, N scenarios:
application-versionsopenbuild-runtimeversion-promotionversion-routingopenbuild-exporteropenbuild-application-registerNote how many begin with "mixed spec — …". The author is explicitly saying some of these scenarios are backend-only and some are not, and the tag then exempts all of them uniformly.
A worked example where the reason is provably wrong for the scenario it exempts
openspec/specs/save-as-template/spec.md, REQ-SAT-001:The reason is about the internals of a captured record. The scenario it silently exempts is about whether a viewer sees a button — as Playwright-observable as anything in the suite, and writable today (
tests/e2e/global-setup.tsmints.auth/rbac-viewer.json).Same shape at REQ-SAT-005, whose reason is about the
canManageVitest tests and swallowsScenario: Seeded cards remain read-only.Why this is hard to see from outside
excluded: 509as a single number, so a reason doing the work of 32 looks exactly like 32 reasons doing the work of one.coverage_pctis computed ascovered / (scenarios − excluded)— openbuild reports 27.9%, which sounds like a coverage problem in the 258 remaining scenarios and says nothing about the 509 removed from the denominator. The unqualified figure is72/767= 9.4%.Suggested fix — deliberately non-reddening
I have not implemented this, on purpose: any change to exclusion accounting reddens many repos at once, and #347 is already being held for that reason. Landing it needs to be sequenced.
Two changes, neither of which fails a build:
{reason, spec, requirement, scenario_count, scenarios[]}— so a reason covering 32 scenarios is visible as one line. Also emit the unqualifiedcovered / scenariosalongside the exclusion-adjusted percentage.[gate-19] NOTE: 1 exclusion reason exempts 32 scenarios in application-versions.A stricter follow-up worth discussing separately: require a requirement-level exclusion to be repeated per scenario once it would exempt more than one, forcing the author to re-read the reason against each scenario. That one would redden repos, so it should be staged behind a flag.
The general lesson
Related: #343 (file-level
@e2etag credited without checking the test body), #345 (an@e2e excluderead as POSITIVE coverage).Concrete instance in an app repo: ConductionNL/openbuild#178.