Skip to content

feat(quality): the last four Nextcloud checks — app:check-code, REUSE, multi-DB PHPUnit - #385

Merged
rubenvdlinde merged 3 commits into
mainfrom
feat/three-nextcloud-checks
Aug 12, 2026
Merged

feat(quality): the last four Nextcloud checks — app:check-code, REUSE, multi-DB PHPUnit#385
rubenvdlinde merged 3 commits into
mainfrom
feat/three-nextcloud-checks

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Nextcloud runs five checks we did not. #383 added the first (info.xml XSD). This adds two more. Multi-database PHPUnit is the remaining one — a larger change to the phpunit job, tracked separately.

occ app:check-code

Nextcloud's own tool for private/deprecated API usage. Our closest equivalent was one hand-written PHPCS sniff covering one symbol (\OC::$server), written by hand precisely because static analysis couldn't see the removal while nextcloud/ocp sat a major below the declared min-version. app:check-code is the general form: it knows the whole private surface of the server it ships with and moves forward when Nextcloud does.

The server is fetched at the version the app declares, anchored to the <nextcloud> element — unanchored, that pattern returns 8 for thirteen fleet apps and a plausible-but-wrong 28 for a fourteenth.

REUSE

Nextcloud runs reuse.yml on every app. We had a PHPCS sniff checking @license/@copyright in PHP docblocks — a proxy for one file type that says nothing about images, JSON, YAML, or the LICENSES/ directory REUSE requires. It also closes a loop: the SPDX InvalidEndChar exception in the shared ruleset is only worth having if something actually parses those expressions.

Both non-blocking by default — a decision, not timidity

Every app reaches into server internals somewhere (OCA\DAV, OC_App, Doctrine\DBAL are all ignored by name in phpstan-base.neon), and only 2 of 4 apps sampled ship a REUSE.toml. A gate that is red on arrival is a gate nobody turns on — that is how openregister ended up with an E2E job that had never once succeeded. Each has a *-blocking input to flip per app.

app:check-code carries a positive control: occ exits 0 on paths that inspect nothing, so the step asserts the log holds a recognisable verdict before trusting any exit code.

⚠️ Touches quality.yml, as does #383. Merge #383 first; I'll rebase if they conflict.

…loud checks

Nextcloud runs five checks this fleet did not. #383 added the first
(info.xml against the App Store XSD). This adds two more; multi-database PHPUnit
is the remaining one and is a larger change to the phpunit job, tracked
separately.

occ app:check-code
------------------
Nextcloud's own tool for private and deprecated API usage. Our closest
equivalent was ONE hand-written PHPCS sniff covering ONE symbol —
\OC::$server, removed in NC 34 — written by hand precisely because static
analysis could not see the removal while nextcloud/ocp sat a major below the
declared min-version. app:check-code is the general form of that sniff: it knows
the whole private surface of the server it ships with, needs no per-symbol
maintenance, and moves forward when Nextcloud does.

The server is fetched at the version the app DECLARES, anchored to the
<nextcloud> element. Unanchored, the pattern returns 8 for thirteen fleet apps
(from <php min-version="8.3"/>) and a plausible-but-wrong 28 for a fourteenth.

REUSE
-----
Nextcloud runs reuse.yml on every app. We had a PHPCS sniff checking @license
and @copyright in PHP docblocks — a proxy for one file type that says nothing
about images, JSON, YAML, or the LICENSES/ directory REUSE requires.

It also closes a loop the coding-standard migration opened: the SPDX
InvalidEndChar exception in the shared ruleset exists because a trailing full
stop turns a machine-parsed SPDX expression into an invalid one. That exception
is only worth having if something actually parses those expressions.

BOTH ARE NON-BLOCKING BY DEFAULT, and that is a decision rather than timidity.
Every app here reaches into server internals somewhere — OCA\DAV, OC_App and
Doctrine\DBAL are all ignored by name in phpstan-base.neon — and only 2 of the
4 apps sampled ship a REUSE.toml at all. A gate that is red on arrival is a gate
nobody turns on: that is how openregister ended up with an E2E job that had
never once succeeded. Each carries a `*-blocking` input to flip per app once its
findings are worked down.

app:check-code carries a positive control. occ exits 0 on paths that inspect
nothing — an app directory it cannot see, a command that bailed on the
environment check — so the step asserts the log contains a recognisable verdict
before treating any exit code as evidence. A silent exit 0 must not read as
"no private API usage".
…check

Nextcloud ships phpunit-mariadb, phpunit-mysql, phpunit-oci and phpunit-sqlite
as separate workflow templates, because database-portability bugs are invisible
on a single backend. This fleet ran PostgreSQL only.

The `database` input already accepted sqlite, pgsql and mysql, but the phpunit
job wired exactly one of them: the postgres service was keyed off
`inputs.database`, and the install block had a pgsql branch with everything else
falling through to a bare `--database <x>` that works for sqlite and cannot work
for mysql — no host, no port, no credentials, and no service container to
connect to. So "mysql" was a value the input accepted and the job could not
honour.

WHAT CHANGED
- `database-test-matrix`, a JSON array, joins the phpunit matrix beside
  php-version and nextcloud-ref.
- A mysql:8.4 service sits beside postgres:16. Both are keyed off
  matrix.database rather than inputs.database, and a service whose image
  evaluates to an empty string is not started — which is how one job definition
  serves several backends.
- The install block is a case statement with real host/port/credentials per
  backend, and an explicit failure on an unknown one.
- pdo_mysql and mysqli join the extension list unconditionally. Conditional
  extensions would mean a mysql leg silently falling through and testing sqlite
  while its name said mysql.

INERT UNTIL AN APP OPTS IN. The default is an empty array, which collapses to
`[inputs.database]` — today's dimensions exactly. Both sides of that comparison
go through fromJSON on purpose: a string test against '[]' accepts '[ ]' and
'[""]' and then resolves to an EMPTY MATRIX VECTOR, and an empty vector does not
skip a job, it deletes it from the run with no row, no annotation and no trace.
Measured on this repo's own self-test for frontend-checks: 19 jobs instead of
21. Verified here against all three spellings of empty plus a real three-backend
array.

POSITIVE CONTROL IN THE JOB. `maintenance:install` can exit 0 having fallen back
to another backend, and a suite passing on sqlite while its leg is labelled
mysql is worse than no leg at all — it is a green cell asserting something about
a database it never touched. The step reads `occ config:system:get dbtype` back
and fails if it does not match the leg's own name.

That completes the five checks Nextcloud runs and we did not: info.xml XSD
(#383), app:check-code and REUSE (this branch), integrity:sign-app (#375), and
multi-database PHPUnit.
@rubenvdlinde rubenvdlinde changed the title feat(quality): adopt occ app:check-code and REUSE — two more of the five Nextcloud checks feat(quality): the last four Nextcloud checks — app:check-code, REUSE, multi-DB PHPUnit Aug 12, 2026
#383 (info.xml XSD) and this branch (app:check-code, REUSE, multi-database
PHPUnit) both insert jobs immediately before the Vue Quality group, so git saw
four conflicts that are all pure additions rather than competing edits. Resolved
by keeping both sides.

Verified after resolving: 20 jobs, no duplicate job names, YAML parses, and all
four of info-xml / app-check-code / reuse / phpunit are present. A keep-both
resolution that silently dropped one side would look identical to a clean merge
in the diff, so the job list is checked rather than assumed.
@rubenvdlinde
rubenvdlinde merged commit 0c30578 into main Aug 12, 2026
7 of 10 checks passed
rubenvdlinde pushed a commit that referenced this pull request Aug 12, 2026
…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 added a commit that referenced this pull request Aug 12, 2026
… and disable the dead app:check-code (#389)

TWO DEFECTS, plus a third found on the way in.

1. quality.yml produced ZERO JOBS fleet-wide since 11:26:16Z. #385 merged from a
branch written before #383; its hunks anchored on what had become info-xml's
Checkout step, and the clean textual merge spliced two jobs into a third —
app-check-code lost its steps:, reuse swallowed info-xml's body, a stray
sparse-checkout landed inside a run: block, and the final upload-artifact ended
up with two name: keys. Every core app consumes this file at @main, so both apps
that pushed after 11:26 got a run with no jobs at all (openconnector 31592994170,
larpingapp 31592027242). An unresolvable reusable workflow never goes red on its
own; only this repo's resolve probe noticed. Note for future linting: PyYAML's
safe_load parses the broken file happily and reports twenty jobs, so a generic
YAML lint would have called it clean.

2. None of the three Nextcloud legs was in the Quality Report's needs:, which is
the only meaningful required check across the fleet. Now 16 -> 19. That is three
jobs, not five: multi-database PHPUnit is a matrix dimension of phpunit and was
already covered, and integrity:sign-app lives in release.yml, which has no
Quality Report to gate.

3. occ app:check-code WAS REMOVED FROM NEXTCLOUD AFTER v20, so that job cannot
pass anywhere here. Extracting registered App-namespace commands from
core/register_command.php, same expression, only the tag varying: v20 yields
CheckCode alongside the others, v34 yields the others without it — v20 is the
control proving the extraction can produce the token. Corroborated on a second
artefact: core/Command/App/CheckCode.php is 200 at v20/v21 and 404 at v25 through
v34, while ListApps.php is 200 at every one of those tags. Every app here
declares min-version 31+, and the job's positive control carries an unconditional
exit 1 that sits above the check-code-blocking branch — so merging it into needs:
would have hard-failed all 18 PHP apps on an impossible check. enable-check-code
now defaults false, with the job left wired and still in needs: (a skipped result
is skipped, not failure) so the wiring is right when someone rebuilds the check.
The positive control was correct throughout: it refused to certify a run that
inspected nothing.

BLAST RADIUS, re-measured rather than quoted. info-xml has no blocking flag and
gates on arrival. Against every repo's live development tip: 6 pass, 12 fail —
not #383's 5/13. The difference is larpingapp, reordered by efa6aead at
11:28:48Z; that one file validates FAIL at the old ref and PASS at the new one.
Eleven of the twelve are xs:sequence element order; the twelfth is an over-length
summary. All are a manifest edit, not a code change. That work is not filed
anywhere yet.

reuse and info-xml are both proven to work — reuse passes in this PR's own
resolve probe, and info-xml produced a real schema error on openregister run
31591669849 that a local validator reproduces byte for byte. Only app-check-code
was dead. From needs: all three looked identical.

Also corrected: the queue was not held by a red guard. Six of the seven open PRs
carry a Shared-workflow guard pass from BEFORE the break — stale greens that
would flip red on any re-run. Only #378 is measurably blocked, by both defects.

check-code-blocking and reuse-blocking are untouched. Neither was asked to move.
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