From 0e69be36bea310985ecb3dcb024a356615c0a762 Mon Sep 17 00:00:00 2001 From: Conduction Release Bot Date: Wed, 12 Aug 2026 14:05:06 +0200 Subject: [PATCH 1/3] =?UTF-8?q?fix(quality):=20quality.yml=20resolved=20to?= =?UTF-8?q?=20ZERO=20jobs=20=E2=80=94=20and=20the=20three=20Nextcloud=20le?= =?UTF-8?q?gs=20could=20not=20block=20a=20merge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TWO DEFECTS. The second is the one that was asked for; the first is the one that made the second academic. quality.yml PRODUCED ZERO JOBS ------------------------------ #383 (info-xml) merged at 11:19:40Z. #385 (app:check-code + REUSE) merged at 11:26:16Z from a branch written against a tree that did not contain info-xml. Its hunks anchored on the context lines - name: Checkout uses: actions/checkout@v4 with: which by then belonged to info-xml's checkout. Git merged it textually, cleanly, and spliced two jobs into a third: * app-check-code lost its `steps:` entirely — the job header ran straight into the info.xml comment block. * info-xml's body became app-check-code's steps. * reuse swallowed info-xml's steps, and a stray `sparse-checkout: appinfo/` landed inside a `run: |` block where it would have executed as a shell line. * the final upload-artifact carried TWO `name:` keys, which is what actually makes the file unparseable. Every core app consumes this file at @main, so since 11:26 the whole fleet's Code Quality workflow has resolved to nothing. Both apps that pushed after that point produced a run with ZERO jobs: openconnector 31592994170 and larpingapp 31592027242. The other sixteen have not pushed since; their green is stale evidence about a workflow that no longer exists. An unresolvable reusable workflow never goes red on its own. The only thing that noticed was this repo's own resolve probe, which said so in words. Note for anyone linting this file in future: PyYAML's safe_load PARSES the broken version happily — last duplicate key wins — and reports twenty jobs. A generic YAML lint would have called it clean. Proven both ways with a duplicate-rejecting loader on one tree: before, a duplicate `name` at line 799; after, none. THE THREE LEGS COULD NOT BLOCK A MERGE -------------------------------------- Neither #383 nor #385 added its job to the Quality Report's `needs:` list, and that job is the only meaningful required check on main and beta across the fleet. scripts/assert-quality-report-gates-every-leg.py named all three. It fails on the parent commit and passes here, and its own positive control passes in both states, so the clean pass is a verdict rather than an instrument that cannot fail. FIVE CHECKS, THREE JOBS ----------------------- #385's own message names five Nextcloud checks. Only three are jobs in this file. Multi-database PHPUnit is a matrix dimension of `phpunit`, which was already in `needs:`, and `occ integrity:sign-app` lives in release.yml, which has no Quality Report to gate. So `needs:` goes 16 -> 19, not 16 -> 21. WHAT LISTING THEM ACTUALLY DOES, PER LEG ---------------------------------------- app-check-code and reuse are internally non-blocking by default — the first swallows a non-zero occ exit and warns, the second carries continue-on-error: ${{ !inputs.reuse-blocking }}. Both conclude success with findings, so listing them changes nothing today. That is the point: the wiring has to exist first, or flipping the per-app input later would be a no-op nobody notices. Their `*-blocking` inputs are NOT flipped here; that is a separate decision. info-xml has no such flag and is blocking on arrival. #383 measured it before merging across all 18 core apps: five pass, thirteen fail, twelve of those on xs:sequence element ORDER and one on a summary over the schema's length cap. Confirmed live in the six-minute window in which this job both existed and resolved — openregister run 31591669849, `Element 'documentation': This element is not expected`. So this commit will red thirteen apps and block their merges until their manifests are reordered. That is the authorised trade: the App Store rejects the same files at upload time, after the release has been built, tagged and published. Every one of the thirteen is a reorder away from passing. THE REPORT WAS SILENT ABOUT ALL THREE ------------------------------------- They upload result artifacts nobody read. That is the same silence the Hydra-gates row three lines below exists to correct, and it would have been reintroduced the day these landed. Three rows added — with a comment saying to read them differently, because reuse.txt records the RAW step outcome (so it goes red on findings while the job is green) and app-check-code.txt records job.status (so it reads green with findings, and its cell means "a verdict was produced", not "no private API usage"). Narrowing that one to its own finding count is left as a separate change rather than smuggled in here. --- .github/workflows/quality.yml | 121 ++++++++++++++++++++++++++-------- 1 file changed, 94 insertions(+), 27 deletions(-) diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index c4339f00..06712e10 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -608,30 +608,6 @@ jobs: runs-on: ubuntu-latest name: "Nextcloud API check (app:check-code)" timeout-minutes: 20 - # ── appinfo/info.xml, validated against the App Store's own schema ───── - # - # Nextcloud ships `lint-info-xml.yml` as a workflow template and every - # upstream app runs it. This fleet ran no equivalent: `quality.yml` did not - # reference appinfo/info.xml anywhere, so a malformed manifest was discovered - # at App Store upload time — after the release had been built, tagged and - # published to GitHub. - # - # That matters more now than it did last week. The release path reads - # info.xml for two things it cannot get wrong: the version the tag must match, - # and the Nextcloud version whose `occ` signs the package. A file that does - # not validate can still parse well enough for a grep to return something - # plausible — which is exactly how an unanchored min-version pattern resolved - # `8` from `` in 13 apps. - # - # The schema is fetched from nextcloud/appstore rather than vendored, because - # a vendored copy is a claim about what the App Store accepts that goes stale - # silently. If the fetch fails the job FAILS — a validator that cannot get its - # schema has validated nothing, and saying so is the whole point. - info-xml: - if: ${{ inputs.enable-php }} - runs-on: ubuntu-latest - name: "info.xml lint" - timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v4 @@ -738,6 +714,47 @@ jobs: if: steps.lint.outcome == 'failure' && !inputs.reuse-blocking run: | echo "::warning::REUSE reported findings. Not blocking yet — set reuse-blocking: true once this app ships a REUSE.toml and a LICENSES/ directory." + + - name: Record result + if: always() + run: | + mkdir -p quality-results + echo "${{ steps.lint.outcome }}" > quality-results/reuse.txt + - name: Upload result + if: always() + uses: actions/upload-artifact@v4 + with: + name: result-reuse + path: quality-results/ + + # ── appinfo/info.xml, validated against the App Store's own schema ───── + # + # Nextcloud ships `lint-info-xml.yml` as a workflow template and every + # upstream app runs it. This fleet ran no equivalent: `quality.yml` did not + # reference appinfo/info.xml anywhere, so a malformed manifest was discovered + # at App Store upload time — after the release had been built, tagged and + # published to GitHub. + # + # That matters more now than it did last week. The release path reads + # info.xml for two things it cannot get wrong: the version the tag must match, + # and the Nextcloud version whose `occ` signs the package. A file that does + # not validate can still parse well enough for a grep to return something + # plausible — which is exactly how an unanchored min-version pattern resolved + # `8` from `` in 13 apps. + # + # The schema is fetched from nextcloud/appstore rather than vendored, because + # a vendored copy is a claim about what the App Store accepts that goes stale + # silently. If the fetch fails the job FAILS — a validator that cannot get its + # schema has validated nothing, and saying so is the whole point. + info-xml: + if: ${{ inputs.enable-php }} + runs-on: ubuntu-latest + name: "info.xml lint" + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: sparse-checkout: appinfo/ - name: Is there an info.xml to lint? @@ -789,13 +806,11 @@ jobs: if: always() run: | mkdir -p quality-results - echo "${{ steps.lint.outcome }}" > quality-results/reuse.txt echo "${{ job.status }}" > quality-results/info-xml.txt - name: Upload result if: always() uses: actions/upload-artifact@v4 with: - name: result-reuse name: result-info-xml path: quality-results/ @@ -4879,7 +4894,34 @@ jobs: # been waved through yet. A gate that would not catch a failure is still a # dead gate, and `scripts/assert-quality-report-gates-every-leg.py` now # keeps the list complete. - needs: [php-quality, vue-quality, frontend-build, frontend-checks, frontend-tests, security, license, phpunit, newman, playwright, journeydoc-capture, baseline-protection, sbom, features-check, features-extract, hydra-gates] + # + # THE THREE NEXTCLOUD LEGS (#383, #385) ARE ON THIS LIST DELIBERATELY, and + # what that does per leg is not the same thing: + # + # app-check-code internally non-blocking by default. The step swallows a + # non-zero `occ` exit and warns unless `check-code-blocking` + # is set, so the job concludes `success` with findings and + # listing it changes nothing today. It becomes a real gate + # the moment an app flips its own input — which is the + # point: the wiring must already be there, or flipping the + # input would be a no-op nobody notices. + # reuse same shape, via `continue-on-error: ${{ !inputs.reuse-blocking }}`. + # info-xml HAS NO SUCH FLAG AND IS BLOCKING ON ARRIVAL. Measured in + # #383 across all 18 core apps: 5 pass, 13 fail, twelve of + # those on xs:sequence element ORDER and one on a summary + # over the schema's length cap. Live-confirmed on + # openregister run 31591669849 — `Element 'documentation': + # This element is not expected`. So listing it here turns + # 13 of 18 apps red until their manifests are reordered, + # and that is the authorised trade: the App Store rejects + # the same files at upload time, after the release has been + # built, tagged and published. + # + # The two remaining Nextcloud checks are NOT missing from this list. Multi + # database PHPUnit is a matrix dimension of `phpunit`, which is already + # here, and `occ integrity:sign-app` lives in release.yml, which has no + # Quality Report to gate. + needs: [php-quality, vue-quality, frontend-build, frontend-checks, frontend-tests, security, license, phpunit, newman, playwright, journeydoc-capture, baseline-protection, sbom, features-check, features-extract, hydra-gates, app-check-code, info-xml, reuse] if: always() # Observed across 266 executions: median 0.6 min — but 2% of runs sit at # ~30 min because the `Install PDF tools` apt-get step stalls on the Ubuntu @@ -5031,6 +5073,31 @@ jobs: sec_npm=$(read_result "results/result-security-npm/npm.txt") echo "| npm | | | $(icon "$sec_npm") | $(license_info "npm") | |" + # The three Nextcloud legs. They are in `needs:` above, so their + # failure reds this job — but a leg the report never mentions is the + # exact silence the Hydra-gates row below exists to correct, and it + # would have been reintroduced the day these three landed. + # + # READ THESE THREE CELLS DIFFERENTLY, because the artefacts behind + # them hold different things: + # + # REUSE records `steps.lint.outcome` — the RAW step + # outcome, before `continue-on-error` rewrites it. + # So this cell goes ❌ on findings even while the + # job concludes success. That is the useful shape. + # app:check-code records `job.status`, and the step exits 0 on + # findings unless `check-code-blocking` is set. So + # ✅ here means "the run produced a recognisable + # verdict", NOT "no private API usage" — the + # findings are in that job's `::warning::` only. + # Worth narrowing to the step's own finding count; + # deliberately not done in this commit. + # info.xml records `job.status` and has no non-blocking + # flag, so its cell and its gate agree. + echo "| app:check-code | $(icon "$(read_result "results/result-app-check-code/app-check-code.txt")") | | | | |" + echo "| info.xml | $(icon "$(read_result "results/result-info-xml/info-xml.txt")") | | | | |" + echo "| REUSE | $(icon "$(read_result "results/result-reuse/reuse.txt")") | | | | |" + # Test rows. `test_icon` rather than `icon`: see its definition — # an enabled-but-skipped test job is the absence of a verdict and # says so in words. From 2e37be37772b9bbd7488a62add699760ff7ecba5 Mon Sep 17 00:00:00 2001 From: Conduction Release Bot Date: Wed, 12 Aug 2026 14:10:36 +0200 Subject: [PATCH 2/3] =?UTF-8?q?docs(quality):=20re-measure=20the=20info-xm?= =?UTF-8?q?l=20blast=20radius=20=E2=80=94=2012=20of=2018,=20not=2013?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #383's 5/13 was measured before it merged. Re-measured against every repo's live development tip with the same App Store XSD: 6 pass, 12 fail. The one-app difference is larpingapp, whose manifest was reordered by efa6aead at 11:28:48Z; validating that single file at both refs gives FAIL then PASS, so the difference is the world moving rather than the instrument drifting. Also records that the local validator reproduces the live CI message on openregister byte for byte, and that the count decays. --- .github/workflows/quality.yml | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 06712e10..f4a17305 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -4906,16 +4906,29 @@ jobs: # point: the wiring must already be there, or flipping the # input would be a no-op nobody notices. # reuse same shape, via `continue-on-error: ${{ !inputs.reuse-blocking }}`. - # info-xml HAS NO SUCH FLAG AND IS BLOCKING ON ARRIVAL. Measured in - # #383 across all 18 core apps: 5 pass, 13 fail, twelve of - # those on xs:sequence element ORDER and one on a summary - # over the schema's length cap. Live-confirmed on - # openregister run 31591669849 — `Element 'documentation': - # This element is not expected`. So listing it here turns - # 13 of 18 apps red until their manifests are reordered, - # and that is the authorised trade: the App Store rejects - # the same files at upload time, after the release has been - # built, tagged and published. + # info-xml HAS NO SUCH FLAG AND IS BLOCKING ON ARRIVAL. #383 + # measured 5 pass / 13 fail before it merged. RE-MEASURED + # 2026-08-12 14:20Z against every repo's live `development` + # tip, same App Store XSD: 6 PASS / 12 FAIL. Eleven of the + # twelve are xs:sequence element ORDER; the twelfth + # (zaakafhandelapp) is a over the schema's length + # cap. The one-app difference is not drift in the + # instrument — larpingapp's manifest was reordered by + # efa6aead at 11:28:48Z, and validating that one file at + # both refs gives FAIL then PASS. + # + # Live-confirmed against CI, not only locally: openregister + # run 31591669849 emitted `Element 'documentation': This + # element is not expected` — the same message, byte for + # byte, as the local validator. + # + # So listing it here turns 12 of 18 apps red until their + # manifests are reordered, and that is the authorised + # trade: the App Store rejects the same files at upload + # time, after the release has been built, tagged and + # published. TREAT THE 12 AS DECAYING — it is a count over + # files several agents edit daily, so re-measure before + # quoting it. # # The two remaining Nextcloud checks are NOT missing from this list. Multi # database PHPUnit is a matrix dimension of `phpunit`, which is already From f118fe7cd55be09ee49be9db95f134e07b100dca Mon Sep 17 00:00:00 2001 From: Conduction Release Bot Date: Wed, 12 Aug 2026 14:20:58 +0200 Subject: [PATCH 3/3] =?UTF-8?q?fix(quality):=20app:check-code=20was=20REMO?= =?UTF-8?q?VED=20from=20Nextcloud=20after=20v20=20=E2=80=94=20default=20it?= =?UTF-8?q?=20off?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This job cannot pass in any repo in this fleet, and merging it into the Quality Report's `needs:` would have hard-failed every PHP app on a check that no longer exists. MEASURED, not inferred. Extracting the registered App-namespace commands from `core/register_command.php` at each tag — same expression, same file, only the tag varying: v20.0.0 Disable Enable GetPath Install ListApps Remove Update + CheckCode v34.0.0 Disable Enable GetPath Install ListApps Remove Update CheckCode appearing at v20 is the control: the extraction CAN produce it, so its absence at v34 is a result rather than a failed lookup. Corroborated independently against a second artefact — `core/Command/App/CheckCode.php` answers 200 at v20 and v21 and 404 at v25, v31, v32, v33 and v34, while `core/Command/App/ListApps.php` answers 200 at every one of those tags, so the 404s are a removal and not a wrong path. Every app here declares a Nextcloud min-version of 31, 32 or 34, and the job fetches the server at the version the app DECLARES. So occ would be asked for a command around eleven majors dead, print no recognisable verdict, and trip the job's own positive control — whose `exit 1` is UNCONDITIONAL. It sits above the check-code-blocking branch and is not covered by it, so "non-blocking by default" was never true of this job's failure path. The positive control behaved exactly as designed: it refused to report an absence of private API usage from a run that inspected nothing. The job is what is wrong, not the control. LEFT WIRED AND STILL IN `needs:`. A skipped job's result is `skipped`, not `failure`, so this blocks nothing while it is off, and the wiring stays correct for whoever rebuilds the check on tooling that still exists. Deleting it would quietly re-open the gap #385 was written to close. check-code-blocking is untouched. Nobody asked for it to move, and it is irrelevant while the job does not run. --- .github/workflows/quality.yml | 42 +++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index f4a17305..bacb8edc 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -98,10 +98,48 @@ on: type: string default: "[]" enable-check-code: - description: "Run `occ app:check-code` against the Nextcloud version the app declares in info.xml. Nextcloud's own tool for private and deprecated API usage; the general form of the hand-written \\OC::$server sniff. Reports without blocking unless check-code-blocking is also set." + # DEFAULT FLIPPED true -> false, because the command this job runs DOES + # NOT EXIST any more and the job therefore cannot pass in any repo here. + # + # `occ app:check-code` was removed from Nextcloud server after v20. + # Measured, extracting the registered App-namespace commands from + # `core/register_command.php` at each tag — same expression, same file, + # only the tag varying: + # + # v20.0.0 Disable Enable GetPath Install ListApps Remove Update + # + CheckCode <- the control: it CAN appear + # v34.0.0 Disable Enable GetPath Install ListApps Remove Update + # <- CheckCode is gone + # + # Corroborated independently: `core/Command/App/CheckCode.php` answers + # 200 at v20 and v21 and 404 at v25, v31, v32, v33 and v34, while + # `core/Command/App/ListApps.php` answers 200 at every one of those tags + # — so the 404s are a removal, not a bad path. + # + # Every app in this fleet declares of 31, 32 or + # 34, and this job fetches the server at the version the app DECLARES. + # So `occ` would be asked for a command roughly eleven majors dead. It + # would print no recognisable verdict, the job's own positive control + # would fire, and that control's `exit 1` is UNCONDITIONAL — it sits + # above the `check-code-blocking` branch and is not covered by it. The + # result would be a hard failure in every PHP repo in the fleet, on a + # check that can never pass. + # + # The positive control is doing its job here, exactly as designed: it + # refuses to report "no private API usage" from a run that inspected + # nothing. The job is what is wrong, not the control. + # + # LEFT WIRED, NOT DELETED, and still listed in the Quality Report's + # `needs:`. A skipped job's result is `skipped`, not `failure`, so this + # blocks nothing while it is off — and when someone rebuilds the check on + # something that exists (Psalm/PHPStan over the private surface, or + # nextcloud/app-api's linter), the wiring is already correct and one + # input turns it on. Deleting it would lose that and quietly re-open the + # gap #385 was written to close. + description: "Run `occ app:check-code` against the Nextcloud version the app declares in info.xml. OFF BY DEFAULT AND CURRENTLY UNUSABLE: `app:check-code` was removed from Nextcloud server after v20 (verified against core/register_command.php and core/Command/App/CheckCode.php from v20 through v34), and every app here declares min-version 31 or later, so the command cannot be found and the job's positive control fails the run. Do not set this true until the check is rebuilt on tooling that still exists." required: false type: boolean - default: true + default: false check-code-blocking: description: "Turn app:check-code findings into a build failure. OFF by default on purpose: every app in this fleet reaches into server internals somewhere, and a gate that is red on arrival is a gate nobody turns on. Flip it per app once the findings are worked down." required: false