Skip to content

fix(quality): quality.yml resolved to ZERO jobs — and the three Nextcloud legs could not block a merge - #389

Merged
rubenvdlinde merged 3 commits into
mainfrom
fix/quality-nextcloud-jobs-needs
Aug 12, 2026
Merged

fix(quality): quality.yml resolved to ZERO jobs — and the three Nextcloud legs could not block a merge#389
rubenvdlinde merged 3 commits into
mainfrom
fix/quality-nextcloud-jobs-needs

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Two defects. The second is what was asked for; the first is what makes the second matter.

1. quality.yml has produced ZERO jobs since 11:26Z, fleet-wide

#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 yet contain info-xml. Its hunks anchored on - name: Checkout / uses: actions/checkout@v4 / with: — context lines that by then belonged to info-xml's checkout. Git merged it textually and 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 command.
  • the final upload-artifact ended up with two name: keys, which is what actually makes the file unparseable.

Every core app consumes this file at @main. Both apps that pushed after 11:26 produced a run with zero jobs — openconnector 31592994170 and larpingapp 31592027242. The other sixteen have not pushed since; their green describes a workflow that no longer exists. The next push in any of them gets zero jobs.

An unresolvable reusable workflow never goes red on its own. The only thing that noticed was this repo's own Quality resolve probe, which said so in words.

Worth recording for whoever lints this file next: 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.

2. The three Nextcloud legs could not block a merge in any of the 18 apps

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 on this branch, 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 message names five Nextcloud checks. Only three of them are jobs in this file:

Nextcloud check job id governed by added to needs:
info.xml vs App Store XSD info-xml enable-phpno dedicated flag yes
occ app:check-code app-check-code enable-php && enable-check-code yes
REUSE / SPDX reuse enable-reuse yes
multi-database PHPUnit not a job — a matrix dimension of phpunit database-test-matrix already covered via phpunit
occ integrity:sign-app not in this filerelease.yml cannot be; no Quality Report there

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 deliberately 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 on xs:sequence element order, 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, complaining that a documentation element is not expected in that position).

So merging this reds thirteen apps and blocks 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, and none of them needs a code change.

The report was silent about all three

They upload result artifacts nobody reads — the same silence the Hydra-gates row three lines below exists to correct, reintroduced the day these landed. Three rows added, with a comment saying to read them differently: reuse.txt records the raw step outcome (so it goes red on findings while the job is green), while app-check-code.txt records job.status (so it reads green with findings — 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.

Verification

  • assert-quality-report-gates-every-leg.py — fails on main naming exactly the three jobs, passes here over nineteen legs; positive control clean in both states.
  • Duplicate-key YAML load — before/after on one tree, only the fix varying.
  • assert-run-steps-resolvable.py over all sixteen workflows, with its --limit 1 positive control.
  • assert-no-producer-deletes-a-verdict.py, test-spec-coverage-gate.py, assert-seed-step-fails-loudly.py — all clean, controls clean.
  • All twenty jobs re-parsed and confirmed well-formed: app-check-code six steps, reuse five, info-xml eight.

The one assertion that cannot be run locally is quality.yml resolves (job count > 0), which dispatches a real run and counts its jobs. That is the assertion that caught the outage, and only this PR's own Quality resolve probe can close it.

Conduction Release Bot added 2 commits August 12, 2026 14:05
…loud legs could not block a merge

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.
#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.
@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

Correction to the framing this PR was opened under

It was described to me as "the Shared-workflow guard is red and that is blocking the gate-fix merge queue — six PRs." Measured against the open PRs, that is not the shape of the queue.

PR Shared-workflow guard completed
#378 fail 11:34:34Z — after the splice
#380 pass 09:09:36Z
#373 pass 08:53:54Z
#377 pass 08:47:08Z
#379 pass 08:44:53Z
#376 pass 08:15:49Z
#355 pass 2026-08-11 13:13:37Z

The break landed at 11:26:16Z. Every one of those greens predates it — they are stale verdicts about a quality.yml that no longer resolves, not evidence those branches are healthy, and they will go red on the next re-run.

Exactly one PR is measurably blocked by the two defects this fixes, and it is blocked by both. #378's resolve-probe run 31591883904 reports quality.yml resolves (job count > 0) and Every leg is inside the required check as failures, and the guard as failing over them.

So the accurate claim for this PR is: it unblocks #378 outright, and it stops the other six going red on their next re-run. The queue was not held by a red light; six of its seven were holding a green one that had expired.

A required check that passed before the thing it checks was broken is indistinguishable in the PR UI from one that passed after. Worth reading completedAt against the breaking commit rather than the bucket alone.

Also re-measured rather than quoted

#383 reported 5 pass / 13 fail for info.xml. Against every repo's live development tip with the same App Store XSD it is now 6 pass / 12 fail — the difference is larpingapp, whose manifest was reordered by efa6aead at 11:28:48Z. Validating that one file at both refs gives FAIL then PASS, so the difference is the world moving, not the instrument drifting. Eleven of the twelve failures are xs:sequence element order and need a reorder, not a code change; the twelfth (zaakafhandelapp) is a <summary> over the schema's length cap. That reorder work is not filed anywhere and will merge-block twelve apps once this lands.

…efault it off

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.
@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

⚠️ Stop — one of the three legs runs a command Nextcloud deleted. Pushed a third commit.

The PR was CLEAN and about to be merged. Before merging I checked the one thing still unmeasured: app-check-code had never executed anywhere, so nothing had ever confirmed its occ invocation works.

It does not. occ app:check-code was removed from Nextcloud server after v20.

Extracting the registered App-namespace commands from core/register_command.php, 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 and not a failed lookup. Corroborated against a second artefact: core/Command/App/CheckCode.php answers 200 at v20/v21 and 404 at v25/v31/v32/v33/v34, while core/Command/App/ListApps.php answers 200 at every one of those tags. So the 404s are a removal, not a wrong path.

Why this mattered before merge. 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. occ would be asked for a command roughly eleven majors dead, print no recognisable verdict, and trip the job's own positive control — and that control's exit 1 is unconditional. It sits above the check-code-blocking branch, so "non-blocking by default" was never true of this job's failure path. Merging as-is would have hard-failed all 18 PHP apps, not the 12 that info-xml reds, on a check that can never pass.

Worth saying plainly: the positive control did exactly what it was built to do. It refused to report "no private API usage" from a run that inspected nothing. The job is what is wrong, not the control — and without that control this would have shipped as a permanent, silent green.

What the third commit does: flips enable-check-code to false with the measurement recorded inline. The job stays wired and stays in needs: — a skipped job's result is skipped, not failure, so it blocks nothing while off, and the wiring is already right 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.

Unchanged: the outage repair, info-xml and reuse in needs:, and the 12-app info.xml blast radius. info-xml still blocks on arrival, which is what was asked for.

@rubenvdlinde
rubenvdlinde merged commit 4118bca into main Aug 12, 2026
33 checks passed
@rubenvdlinde
rubenvdlinde deleted the fix/quality-nextcloud-jobs-needs branch August 12, 2026 12:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant