From 06fa7827437c8a50f7926e4804966052d055165e Mon Sep 17 00:00:00 2001 From: Conduction Release Bot Date: Tue, 11 Aug 2026 14:28:53 +0200 Subject: [PATCH 1/2] fix(gate-24): the JS probe knew one of two registration APIs, so it cleared repos that have leaves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gate-24 (integration-parity) decides applicability from its own subject matter: does this repo register integration leaves at all? The PHP half looks for `new LeafDescriptor(` in lib/. The JS half looked only for `registerIntegration(` in src/. There are TWO canonical JS registration forms, and the gate knew one. `registerIntegration(descriptor)` is the convenience wrapper exported from @conduction/nextcloud-vue; the registry object it wraps is called directly as `OCA.OpenRegister.integrations.register(descriptor)`, which is equally canonical — it is how nextcloud-vue registers its OWN built-in leaves (src/integrations/builtin/files.js). So an app using the direct form was told, verbatim, "this repo registers no integration leaves at all". That sentence is not cosmetic: it routes the gate to `na`, which does NOT count against coverage, instead of `structural`, which does. The one gate that exists to correlate the server and JS halves of a leaf therefore switched itself off precisely in repos that have halves to correlate, and did so while printing a confident absence claim. MEASURED, `--full`, on ConductionNL/decidesk at e03f5932: [gate-24] integration-parity: NOT APPLICABLE — ... this repo registers no integration leaves at all — no `new LeafDescriptor(` in lib/ and no `registerIntegration(` in src/. decidesk registers exactly one leaf, `decidesk-decisions`, at src/integrations/registerDecisionsLeaf.js:175 via `target.OCA.OpenRegister.integrations.register(decisionsLeafDescriptor)` — and that id was present in the LIVE JS registry in the same repo's Playwright run, read out of `window.OCA.OpenRegister.integrations.list()`. The gate said the set was empty while the runtime enumerated a member of it. POSITIVE CONTROL, old probe vs new, over the fleet checkout: decidesk old=0 new=1 <- false negative, fixed openregister old=0 new=1 <- false negative, fixed openconnector old=1 new=1 <- no regression procest old=1 new=1 <- no regression hermiq old=1 new=1 <- no regression nldesign old=0 new=0 <- correctly negative docudesk old=0 new=0 <- correctly negative and on decidesk the verdict moves from a false `na` to `SKIPPED (structural)`, which moves the coverage line from "58 of 58 applicable gates ran" to "58 of 59" — the gap becomes visible instead of being absorbed. `integrations\.register` cannot collide with `unregister(`, because the qualifier is part of the match, nor with `registerIntegrationIcons(`, because `\(` anchors the end. Why this matters beyond the count: the drift gate-24 exists to catch is live right now. openregister advertises `kvk` and `opencorporates` over OCS capabilities while @conduction/nextcloud-vue never registered either in JS (ConductionNL/nextcloud-vue#630), and it went unnoticed until an app's e2e test compared the two lists by hand. A parity gate that cannot tell it has leaves is a parity gate that cannot find that. NOT FOR IMMEDIATE MERGE. This turns a silent `na` into a counted `structural` gap in at least two repos, so it can redden a `--require-full-coverage` run mid-wave — the same reason #347 is being held. Land it once the wave settles. --- hydra-gates/scripts/run-hydra-gates.sh | 33 +++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/hydra-gates/scripts/run-hydra-gates.sh b/hydra-gates/scripts/run-hydra-gates.sh index 5d315bdd..02fb9dec 100755 --- a/hydra-gates/scripts/run-hydra-gates.sh +++ b/hydra-gates/scripts/run-hydra-gates.sh @@ -3877,12 +3877,39 @@ _parity_has_js=0 if [ -d lib ] && grep -rqE 'new[[:space:]]+LeafDescriptor[[:space:]]*\(' lib/ --include='*.php' 2>/dev/null; then _parity_has_php=1 fi -if [ -d src ] && grep -rqE '\bregisterIntegration[[:space:]]*\(' src/ 2>/dev/null; then +# THE JS PROBE MUST MATCH BOTH SUPPORTED REGISTRATION APIs, NOT ONE. +# +# `registerIntegration(descriptor)` is the convenience wrapper exported from +# @conduction/nextcloud-vue (src/integrations/registry.js). The registry object +# it wraps is equally canonical and is called directly as +# `OCA.OpenRegister.integrations.register(descriptor)` — including by +# nextcloud-vue's OWN built-in leaves (src/integrations/builtin/files.js). +# +# Probing for only the wrapper made this gate produce a FALSE ABSENCE CLAIM, and +# the claim was load-bearing: an app registering leaves via the direct form was +# told "this repo registers no integration leaves at all", which routes it to +# `na` (not counted as a gap) instead of `structural` (counted). So the one gate +# that exists to correlate the server and JS halves of a leaf switched itself +# off, silently, in exactly the repos that have leaves to correlate. +# +# MEASURED across the fleet checkout, both forms are live: +# registerIntegration( -> hermiq/src/integration-leaf.js, +# openconnector/src/integration.js, +# procest/src/main.js +# integrations.register( -> decidesk/src/integrations/registerDecisionsLeaf.js, +# openregister/src/main.js +# decidesk registers `decidesk-decisions` through the second form and was +# reported by this gate as having no leaves at all, on a --full run, while that +# very id was present in the live JS registry. +# +# `integrations\.register` cannot collide with `unregister(` (the qualifier is +# part of the match) nor with `registerIntegrationIcons(` (the `\(` anchors it). +if [ -d src ] && grep -rqE '\b(registerIntegration|integrations\.register)[[:space:]]*\(' src/ 2>/dev/null; then _parity_has_js=1 fi if [ ! -f scripts/check-integration-parity.sh ]; then if [ "${_parity_has_php}" = "0" ] && [ "${_parity_has_js}" = "0" ]; then - _skip 24 "integration-parity" na "no scripts/check-integration-parity.sh, and this repo registers no integration leaves at all — no \`new LeafDescriptor(\` in lib/ and no \`registerIntegration(\` in src/. There is no server↔JS pair for parity to correlate." + _skip 24 "integration-parity" na "no scripts/check-integration-parity.sh, and this repo registers no integration leaves at all — no \`new LeafDescriptor(\` in lib/ and neither \`registerIntegration(\` nor \`integrations.register(\` in src/. There is no server↔JS pair for parity to correlate." else _skip 24 "integration-parity" structural "no scripts/check-integration-parity.sh, but this repo DOES register integration leaves (lib/ LeafDescriptor: ${_parity_has_php}, src/ registerIntegration: ${_parity_has_js}). server↔JS leaf parity (ADR-066 Decisions 4/7: phantom render surfaces, orphan JS registrations, renderMode mismatch) is UNVERIFIED — a leaf whose other half never registered is invisible to this run." fi @@ -3911,7 +3938,7 @@ if [ -f scripts/check-integration-parity.sh ]; then if grep -qiE 'skipping|not found locally|could not be located' "${_parity_log}" 2>/dev/null \ && ! grep -qE '^(✓|✗)' "${_parity_log}" 2>/dev/null; then if [ "${_parity_has_php}" = "0" ] && [ "${_parity_has_js}" = "0" ]; then - _skip 24 "integration-parity" na "scripts/check-integration-parity.sh ran but SKIPPED (it could not locate the canonical JS check), and this repo registers no integration leaves at all — no \`new LeafDescriptor(\` in lib/ and no \`registerIntegration(\` in src/. There is no server↔JS pair for parity to correlate. See ${_parity_log}." + _skip 24 "integration-parity" na "scripts/check-integration-parity.sh ran but SKIPPED (it could not locate the canonical JS check), and this repo registers no integration leaves at all — no \`new LeafDescriptor(\` in lib/ and neither \`registerIntegration(\` nor \`integrations.register(\` in src/. There is no server↔JS pair for parity to correlate. See ${_parity_log}." else _skip 24 "integration-parity" structural "scripts/check-integration-parity.sh ran but SKIPPED — it could not locate the canonical JS check, so NOTHING was correlated — while this repo DOES register integration leaves (lib/ LeafDescriptor: ${_parity_has_php}, src/ registerIntegration: ${_parity_has_js}). server↔JS leaf parity (ADR-066 Decisions 4/7: phantom render surfaces, orphan JS registrations, renderMode mismatch) is UNVERIFIED by this run — this is NOT a pass. See ${_parity_log}." fi From bd696df2f883e459a62e0692273519fd9b860d03 Mon Sep 17 00:00:00 2001 From: Conduction Release Bot Date: Tue, 11 Aug 2026 20:50:17 +0200 Subject: [PATCH 2/2] test(gate-24): a fixture for #349, and it caught its own dead first draft MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A paired arm on top of the existing `clean/` fixture: one overlaid file that registers a leaf through the DIRECT form, `OCA.OpenRegister.integrations. register(...)`. `clean/` alone stays NOT APPLICABLE (asserted, unchanged); `clean/` + that one file must be SKIPPED (structural). Observed red, then green, against the same suite: runner @765d0817 (pre-#349) FAIL x3 -- "gate-24 still asserts 'registers no integration leaves at all' over a tree that registers one" runner @06fa782 (post) PASS x3, and the clean arm still na THE FIRST DRAFT OF THE FIXTURE WAS A DEAD TEST AND WENT GREEN AGAINST THE PRE-#349 RUNNER. Its explanatory comment named the wrapper function followed by an open paren, and gate-24's probe is a plain grep over file TEXT -- so the PROSE describing the defect satisfied the gate being described. That is .github#358's shape (gates 19 and 26 parse prose) met while fixturing a different gate, and the warning is now written into the fixture itself so the next author does not re-introduce it. BLAST RADIUS, MEASURED rather than argued -- the runner run over each fleet checkout, old runner vs new, nothing else changed: decidesk NOT APPLICABLE -> SKIPPED (structural) <- the only change openregister PASS -> PASS openconnector PASS -> PASS hermiq PASS -> PASS ⚠️ THIS CORRECTS THE PR BODY. #349 says the verdict moves "in at least two repos" and names openregister. It does not: openregister ships scripts/check-integration-parity.sh, so gate-24 takes the wrapper path and passes, before and after. Exactly ONE repo changes. decidesk's new verdict is honest -- it registers `decidesk-decisions` and ships no parity check -- but `structural` COUNTS against coverage, and decidesk sets enable-hydra-gates: true while inheriting hydra-gates-require-full-coverage: true. So its Code Quality run will exit 98 until it adds the parity wrapper that openregister, openconnector, hermiq and procest already carry. The shared workflow's own input description already records hermiq and openconnector as having exactly this gap, so decidesk is joining a known and closable shape rather than meeting a new one. --- ...st_gates_23_33_never_green_over_nothing.sh | 43 +++++++++++++++++++ .../src/integration-leaf-direct.js | 35 +++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 hydra-gates/scripts/test-fixtures/gates-23-33/direct-registry-overlay/src/integration-leaf-direct.js diff --git a/hydra-gates/scripts/lib/test_gates_23_33_never_green_over_nothing.sh b/hydra-gates/scripts/lib/test_gates_23_33_never_green_over_nothing.sh index 45223c56..5800432f 100644 --- a/hydra-gates/scripts/lib/test_gates_23_33_never_green_over_nothing.sh +++ b/hydra-gates/scripts/lib/test_gates_23_33_never_green_over_nothing.sh @@ -217,6 +217,49 @@ if _run "${FIXTURES}/clean"; then _expect 24 "NOT APPLICABLE" "gate-24 with no wrapper and no leaves is na, not PASS" fi +# =========================================================================== +echo +echo "== gate-24: BOTH registration APIs count as having leaves (.github#349) ==" +# =========================================================================== +# `clean/` has no parity wrapper AND no leaf, so its NOT APPLICABLE above is +# correct. Drop ONE file on top of it — a leaf registered through +# `OCA.OpenRegister.integrations.register(...)`, the direct form — and the +# verdict must become SKIPPED (structural): the repo now has a server↔JS pair +# to correlate and nothing correlated it. +# +# The distinction is not cosmetic. `na` does NOT count against coverage; +# `structural` does. A gate that misreads its own subject matter as absent +# switches itself off silently, and prints a confident absence claim while +# doing it — measured on decidesk, whose registered leaf id was enumerable from +# the live JS registry in the same repo's Playwright run. +# +# THE PAIRED ARM MATTERS: `clean/` untouched must STAY `na`. A fix that made +# gate-24 structural everywhere would pass a one-armed version of this test and +# would be a false gap in every repo in the fleet. +_DIRECT="$(mktemp -d "${TMPDIR:-/tmp}/hydra-gate-24-direct.XXXXXXXX")" +cp -r "${FIXTURES}/clean/." "${_DIRECT}/" +cp "${FIXTURES}/direct-registry-overlay/src/integration-leaf-direct.js" "${_DIRECT}/src/" +if [ ! -f "${_DIRECT}/src/integration-leaf-direct.js" ]; then + _bad "the direct-registry overlay did not land — every assertion in this arm would be vacuous" +elif grep -rq 'scripts/check-integration-parity.sh' "${_DIRECT}" 2>/dev/null \ + || [ -f "${_DIRECT}/scripts/check-integration-parity.sh" ]; then + _bad "the direct-registry arm inherited a parity wrapper from clean/, so it exercises the wrapper path and not the leaf-detection path this arm is about" +elif _run "${_DIRECT}"; then + _expect 24 "SKIPPED (structural)" \ + "gate-24 recognises a leaf registered via integrations.register( — structural, not na" + _expect 24 "DOES register integration leaves" \ + "gate-24 says WHY it is structural" + # And the reason must not still claim the repo has no leaves. + _l24="$(_verdict 24)" + case "${_l24}" in + *"registers no integration leaves at all"*) + _bad "gate-24 still asserts 'registers no integration leaves at all' over a tree that registers one — .github#349 is live: ${_l24:0:160}" + ;; + *) _ok "gate-24 no longer claims the leaf set is empty" ;; + esac +fi +rm -rf "${_DIRECT}" + # =========================================================================== echo echo "== gate-23 in WARN mode: a finding must still be VISIBLE ==" diff --git a/hydra-gates/scripts/test-fixtures/gates-23-33/direct-registry-overlay/src/integration-leaf-direct.js b/hydra-gates/scripts/test-fixtures/gates-23-33/direct-registry-overlay/src/integration-leaf-direct.js new file mode 100644 index 00000000..b1845fed --- /dev/null +++ b/hydra-gates/scripts/test-fixtures/gates-23-33/direct-registry-overlay/src/integration-leaf-direct.js @@ -0,0 +1,35 @@ +/** + * THE SECOND CANONICAL REGISTRATION FORM — `.github#349`. + * + * The convenience wrapper exported from @conduction/nextcloud-vue is the form + * gate-24 already knew. The registry object it wraps is equally canonical and + * is called directly, as below — including by nextcloud-vue's OWN built-in + * leaves (src/integrations/builtin/files.js). + * + * gate-24's JS probe knew only the wrapper. An app registering this way was + * told, verbatim, "this repo registers no integration leaves at all", which + * routes the gate to `na` — NOT counted against coverage — instead of + * `structural`, which is. So the one gate that exists to correlate the server + * and JS halves of a leaf switched itself off in exactly the repos that have + * halves to correlate. Measured live on decidesk, whose `decidesk-decisions` + * id was in `window.OCA.OpenRegister.integrations.list()` in the same repo's + * Playwright run while gate-24 reported the set empty. + * + * ⚠️ THIS COMMENT MUST NOT NAME THE WRAPPER FUNCTION FOLLOWED BY AN OPEN + * PAREN. gate-24's probes are plain greps over file TEXT, comments included, + * so an explanatory sentence spelling the wrapper call out is indistinguishable + * from a registration. The first draft of this file did exactly that, and the + * arm below went GREEN against the PRE-#349 runner — a dead test that looked + * like coverage. Same family as `.github#358` (gates 19 and 26 parse prose), + * encountered while writing the fixture for a different gate. + * + * This file is an OVERLAY dropped on top of `clean/`, which has neither a + * parity wrapper nor any leaf. `clean/` alone is legitimately NOT APPLICABLE; + * `clean/` + this file must be SKIPPED (structural). + */ +;(function (target) { + target.OCA.OpenRegister.integrations.register({ + id: 'gateplant-direct-agent', + renderMode: 'mount', + }) +})(window)