fix(gate-28): an empty diff scope is not a structural gap — it is ADR-020 working - #182
Merged
Merged
Conversation
…-020 working
gate-28 turned RED on every PR in the fleet whose diff does not touch
lib/**/*.php, which is most of them. It reported the DIFF's shape as the
REPOSITORY's defect.
`_lt_files` empty had two causes, and the final `else` stated them as one:
a) the repo has lib/**/*.php but this diff touches none of them — ADR-020
diff-scoping working, the same state gate-4 reports NOT APPLICABLE for
the same diff;
b) lib/ exists but holds no tracked .php at all.
Neither is (c) "files WERE in scope and none carried a declaration", the
genuine structural gap that branch describes. All three printed (c), and for
(a) that message is FALSE: with zero files in scope, "0 in-scope file carried
a declaration" is true only vacuously.
Not cosmetic. hydra-gates-require-full-coverage defaults to TRUE (#164) and a
structural gap fails the run with exit 98, so this was a fleet-wide falsely-RED
gate — the mirror of the falsely-GREEN shape #172 built this taxonomy to kill.
It surfaced the moment a repo unpinned to main, because gate-28 does not exist
at v1.3.0.
Measured on nldesign against the real runner, three arms, before and after:
diff = .github/workflows/code-quality.yml before SKIPPED(structural) -> exit 98
after NOT APPLICABLE
diff = 3 files under lib/ before PASS after PASS
diff = one lib/*.php declaring AGPL while
composer.json declares EUPL-1.2 after FAIL — 1 file(s)
The third arm is the one that matters: turning a red into a not-applicable is
exactly how a gate gets muted, so the mismatch it exists to catch was injected
and confirmed still caught.
The (c) message now also states the in-scope count, so a reader can tell it
apart from the case it used to be conflated with.
rubenvdlinde
pushed a commit
to ConductionNL/nldesign
that referenced
this pull request
Aug 6, 2026
…it was cut development moved from v1.3.0 to v1.5.0 while this branch was open, which fixes the symptom that opened it: v1.5.0 satisfies #177's capability probe, so Hydra Gates no longer fails before a gate runs. Unpinning entirely still matters, and the reason is now sharper than when this branch started. v1.5.0 carries a fleet-wide falsely-RED gate. gate-28 license-triangle reported a `structural` coverage gap whenever the diff scope was empty — that is, on every PR that does not touch lib/**/*.php, which is most of them — because "no file in scope" and "files in scope, none carried a licence declaration" reached the same branch. With hydra-gates-require-full-coverage defaulting TRUE, that is exit 98. It is fixed in ConductionNL/.github#182, on main. A repo pinned to v1.5.0 cannot see that fix, and the tag will not move. Measured here against the real runner, three arms: diff = .github/workflows/code-quality.yml before SKIPPED(structural) after NOT APPLICABLE diff = 3 files under lib/ PASS, both diff = a lib/*.php declaring AGPL while composer.json declares EUPL-1.2 FAIL — 1 file(s) That is the whole argument for #177's contract in one branch: this repo was pinned at v1.3.0, bumped to v1.5.0 to clear one breakage, and would have needed a third commit to clear the next one. A pin is a silent expiry date on every upstream fix. Verified on this PR's own run after the unpin: Hydra Gates PASS in 20s, 58 applicable gates green, 5 correctly NOT APPLICABLE. Rollback for this repo alone stays available: set hydra-gates-ref explicitly. An escape hatch, not a resting state.
rubenvdlinde
pushed a commit
to ConductionNL/nldesign
that referenced
this pull request
Aug 6, 2026
…it was cut development moved from v1.3.0 to v1.5.0 while this branch was open, which fixes the symptom that opened it: v1.5.0 satisfies #177's capability probe, so Hydra Gates no longer fails before a gate runs. Unpinning entirely still matters, and the reason is now sharper than when this branch started. v1.5.0 carries a fleet-wide falsely-RED gate. gate-28 license-triangle reported a `structural` coverage gap whenever the diff scope was empty — that is, on every PR that does not touch lib/**/*.php, which is most of them — because "no file in scope" and "files in scope, none carried a licence declaration" reached the same branch. With hydra-gates-require-full-coverage defaulting TRUE, that is exit 98. It is fixed in ConductionNL/.github#182, on main. A repo pinned to v1.5.0 cannot see that fix, and the tag will not move. Measured here against the real runner, three arms: diff = .github/workflows/code-quality.yml before SKIPPED(structural) after NOT APPLICABLE diff = 3 files under lib/ PASS, both diff = a lib/*.php declaring AGPL while composer.json declares EUPL-1.2 FAIL — 1 file(s) That is the whole argument for #177's contract in one branch: this repo was pinned at v1.3.0, bumped to v1.5.0 to clear one breakage, and would have needed a third commit to clear the next one. A pin is a silent expiry date on every upstream fix. Verified on this PR's own run after the unpin: Hydra Gates PASS in 20s, 58 applicable gates green, 5 correctly NOT APPLICABLE. Rollback for this repo alone stays available: set hydra-gates-ref explicitly. An escape hatch, not a resting state.
rubenvdlinde
added a commit
to ConductionNL/zaakafhandelapp
that referenced
this pull request
Aug 6, 2026
…it was cut (#331) This repo has been bumped twice already (v1.0.1 -> v1.3.0 -> v1.5.0), each time to clear a breakage the pin itself caused, and a third was queued. v1.5.0 ships gate-28 reporting a `structural` coverage gap whenever the diff scope is empty — i.e. on every PR that does not touch lib/**/*.php, which is most of them. With hydra-gates-require-full-coverage defaulting TRUE that is exit 98. It is fixed in ConductionNL/.github#182, on main, and a pinned repo cannot see that fix because the tag will not move. Tracking main is the fleet contract as of ConductionNL/.github#177, which also added the guard that makes it safe: an unresolvable reusable workflow produces no red check at all, just zero jobs, so quality-resolve-probe.yml counts the jobs a caller materialises and asserts > 0. Supersedes #329, which set v1.4.0 — a DOWNGRADE from what development now carries, and to a ref that fails #177's capability probe outright (v1.4.0 ships none of check_spec_anchors.py, check_form_labels.py or check_license_triangle.py). Rollback for this repo alone stays available: set hydra-gates-ref explicitly. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
rubenvdlinde
added a commit
that referenced
this pull request
Aug 8, 2026
…s missing (#248) #178 (gate-28 fails every PHP-free PR in a PHP repo) was already FIXED by #182 on 2026-08-06, released in v1.5.2 and v1.6.0. The issue was simply never closed. Measured, not assumed — all three states behave correctly on main today. This commit adds no runtime change; it adds the acceptance evidence and closes three real gaps in what the suites actually asserted. WHAT WAS NOT BEING ASSERTED 1. The suite never ran --require-full-coverage, so it proved "gate-28 prints NOT APPLICABLE" and INFERRED "the run is not failed". Those are different claims and have come apart before (#164 defaulted the flag TRUE in the workflow while every local suite ran without it). 2. Nothing exercised the _fail 28 branch end to end. A licence MISMATCH is the gate's actual purpose; only the weaker "no file carried a tag" control existed. The helper->log->wc -l->_fail wiring was untested. 3. check_license_triangle.py's declared_file_count() had NO test in either direction, despite being the single number the runner's PASS-vs-structural decision rests on, and nothing pinned the stdout/stderr split it travels over. A TRAP FOUND WHILE WRITING THIS The coverage-failure branch lives inside `if [ "${_FAILED}" -eq 0 ]`. Any other failing gate suppresses the sentence entirely, so "the coverage sentence is absent" passes automatically on a fixture that trips an unrelated gate — including on a fully reverted runner. The first draft wrote its Vue file to src/App.vue, which gate-38 fails; both runs exited 1, not 98, and both new assertions were measuring gate-38. Fixtures are now otherwise green (src/components/Widget.vue), the pass case reads the POSITIVE "ALL N APPLICABLE GATES GREEN" sentence, and the control uses a composer.json with no license field rather than untagged PHP (which also fails gate-1, making the coverage branch unreachable). MUTATION-CHECKED — every new assertion shown capable of failing: M1 empty diff scope -> structural (pre-#182) -> 2 new shell assertions RED, status 98 M2 structural -> na (gate stops failing) -> 2 new shell assertions RED M3 _fail 28 disabled -> 2 new shell assertions RED (drift became PASS) M4 declared_file_count -> len(files) -> 3 new python tests RED M5 count printed to stdout -> 4 new python tests RED; clean repo turns FAIL M6 empty scope invents a finding + a count -> 3 new python tests RED M7 declared_file_count -> 0 -> 5 new python tests RED; PASS became structural Counts: shell 5 -> 11 assertions, python 19 -> 29 tests. Both appended to, never overwritten. Full helper suite: 27 passed, 0 failed, 2 quarantined. No runtime file is touched: the diff is the two test files only. Closes #178 Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
gate-28 goes RED on every PR whose diff does not touch
lib/**/*.php— which is most of them. It reports the diff's shape as the repository's defect.Found while unpinning nldesign to
mainper #177: with the pin removed both capability probes pass, all 58 applicable gates go green, and the only failure is gate-28 not running. It does not exist atv1.3.0, so unpinning is what surfaced it — and it would have turned the whole fleet red as the sweep landed.The conflation
_lt_filesempty had two causes, and the finalelsestated them as one:lib/**/*.phpbut this diff touches none — ADR-020 diff-scoping working, the same state gate-4 reports NOT APPLICABLE for the same diff;lib/exists but holds no tracked.phpat all.Neither is (c) "files were in scope and none carried a declaration" — the genuine structural gap that branch describes. All three printed (c), and for (a) that message is simply false: with zero files in scope, "0 in-scope file carried a declaration" is true only vacuously.
Not cosmetic:
hydra-gates-require-full-coveragedefaults TRUE (#164) and a structural gap exits 98. This is a fleet-wide falsely-RED gate — the mirror of the falsely-GREEN shape #172 built this taxonomy to kill.Three arms, measured on nldesign against the real runner
.github/workflows/code-quality.ymllib/lib/*.phpdeclaring AGPL while composer.json declares EUPL-1.2The third arm is the one that matters. Turning a red into a not-applicable is exactly how a gate gets muted, so the mismatch gate-28 exists to catch was injected on a throwaway branch and confirmed still caught.
The (c) message now also reports the in-scope file count, so a reader can tell it apart from the case it used to be conflated with.