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
The a11y gate family scans src/**/*.vue only — a PHP-template app (nldesign) gets a green over an EMPTY SCOPE, and the PASS is indistinguishable from a measured zero #225
gate-40 (form-label-association) and its neighbours in the accessibility family select their inputs with find src -name '*.vue'. nldesign has src/ but zero .vue files — it is a settings-only app whose entire UI is templates/settings/admin.php plus vanilla js/admin.js. The gate arm runs, the in-scope set is empty, and it reports:
[gate-40] form-label-association: PASS
That PASS describes nothing. It is not wrong at the gate level — the code even documents the choice, correctly, so the applicability table does not drift. But in the repo that owns the fleet's WCAG AA posture, the accessibility gates measure zero bytes, and nothing in the output says so.
Positive control
A green over an empty scope and a green over a clean scope are the same string, so I planted a defect:
directly into templates/settings/admin.php — an <input> with no aria-label, no <label for>, no wrapping <label>, and a type outside the exempt set. Textbook gate-40 true positive.
Unmoved. The gate cannot see this repo's markup, and there is no signal that distinguishes that from "this repo is clean".
What the unscanned markup actually contains
Audited by hand, since nothing else does. templates/settings/admin.php, 18 form controls:
15 exposed controls, all correctly associated via <label for> — nldesign-token-set-select, nldesign-hide-slogan, nldesign-show-menu-labels, nldesign-custom-css-enabled, nldesign-email-footer-*, nldesign-marianne-enabled, nldesign-upstream-freshness-toggle, and the rest.
3 unlabelled, all <input type="file" ... style="display:none"> (nldesign-upload-input, nldesign-font-input, nldesign-config-bundle-input). Each is triggered programmatically by a visible button. display:none removes an element from the accessibility tree entirely, so there is no accessible name to get wrong and no keyboard or AT path to it. SC 1.3.1 / 3.3.2 do not apply. Genuine non-findings.
So the repo is clean. That is a good outcome and it was not what the green told anyone — the green would have read identically over 18 unlabelled controls.
Why this matters beyond one repo
The fleet-wide claim "gate-40 measured 58% false positives" is computed over repos the gate can see. A repo it cannot see contributes a PASS to the same denominator. Any fleet a11y roll-up that counts nldesign as green is counting a repo that was never measured.
Suggested fix
Two parts, and the first matters more than the second:
Say when the scope is empty. Something like PASS (0 files in scope) on the [gate-N] line, for every [ -d src ] gate whose in-scope set is empty. This does not change any verdict or the applicability table — it changes an unfalsifiable string into a falsifiable one, and it is the same principle already applied in fix(gates): 16 gates reported PASS when their helper never ran — 2 are authorization gates #147 (a missing helper must not report PASS) and in gate-22/gate-53 (refuse to run fail-open rather than pass weakly).
Extend the input set to server-rendered markup — templates/**/*.php at minimum, for the label-association and semantic-controls gates. check_form_labels.py already parses HTML-ish markup and already excludes <script>/<style>; PHP templates need the same treatment plus <?php ?> stripping. nldesign is the only fleet app that is template-only today, but every app ships templates/settings/admin.php, and none of them is scanned.
Part 1 alone would have made this discoverable in seconds. Part 2 is what makes the gate true.
Repro
git clone https://github.com/ConductionNL/nldesign &&cd nldesign && git checkout development
find src -name '*.vue'| wc -l # 0
bash /path/to/hydra-gates/scripts/run-hydra-gates.sh # full repo, NOT --scope-to-diff# [gate-40] form-label-association: PASS# ... now plant an unlabelled <input> in templates/settings/admin.php and run again: still PASS
Related: #214 (gate-38 scopes templates/settings/*.php and gets it wrong in the other direction — it does look at that file, and reports a skip link that Nextcloud core already emits).
Summary
gate-40 (form-label-association) and its neighbours in the accessibility family select their inputs with
find src -name '*.vue'. nldesign hassrc/but zero.vuefiles — it is a settings-only app whose entire UI istemplates/settings/admin.phpplus vanillajs/admin.js. The gate arm runs, the in-scope set is empty, and it reports:That PASS describes nothing. It is not wrong at the gate level — the code even documents the choice, correctly, so the applicability table does not drift. But in the repo that owns the fleet's WCAG AA posture, the accessibility gates measure zero bytes, and nothing in the output says so.
Positive control
A green over an empty scope and a green over a clean scope are the same string, so I planted a defect:
directly into
templates/settings/admin.php— an<input>with noaria-label, no<label for>, no wrapping<label>, and atypeoutside the exempt set. Textbook gate-40 true positive.Unmoved. The gate cannot see this repo's markup, and there is no signal that distinguishes that from "this repo is clean".
What the unscanned markup actually contains
Audited by hand, since nothing else does.
templates/settings/admin.php, 18 form controls:<label for>—nldesign-token-set-select,nldesign-hide-slogan,nldesign-show-menu-labels,nldesign-custom-css-enabled,nldesign-email-footer-*,nldesign-marianne-enabled,nldesign-upstream-freshness-toggle, and the rest.<input type="file" ... style="display:none">(nldesign-upload-input,nldesign-font-input,nldesign-config-bundle-input). Each is triggered programmatically by a visible button.display:noneremoves an element from the accessibility tree entirely, so there is no accessible name to get wrong and no keyboard or AT path to it. SC 1.3.1 / 3.3.2 do not apply. Genuine non-findings.So the repo is clean. That is a good outcome and it was not what the green told anyone — the green would have read identically over 18 unlabelled controls.
Why this matters beyond one repo
The fleet-wide claim "gate-40 measured 58% false positives" is computed over repos the gate can see. A repo it cannot see contributes a PASS to the same denominator. Any fleet a11y roll-up that counts nldesign as green is counting a repo that was never measured.
Suggested fix
Two parts, and the first matters more than the second:
Say when the scope is empty. Something like
PASS (0 files in scope)on the[gate-N]line, for every[ -d src ]gate whose in-scope set is empty. This does not change any verdict or the applicability table — it changes an unfalsifiable string into a falsifiable one, and it is the same principle already applied in fix(gates): 16 gates reported PASS when their helper never ran — 2 are authorization gates #147 (a missing helper must not report PASS) and in gate-22/gate-53 (refuse to run fail-open rather than pass weakly).Extend the input set to server-rendered markup —
templates/**/*.phpat minimum, for the label-association and semantic-controls gates.check_form_labels.pyalready parses HTML-ish markup and already excludes<script>/<style>; PHP templates need the same treatment plus<?php ?>stripping. nldesign is the only fleet app that is template-only today, but every app shipstemplates/settings/admin.php, and none of them is scanned.Part 1 alone would have made this discoverable in seconds. Part 2 is what makes the gate true.
Repro
Related: #214 (gate-38 scopes
templates/settings/*.phpand gets it wrong in the other direction — it does look at that file, and reports a skip link that Nextcloud core already emits).