Release: merge development into beta #715
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Code Quality | |
| on: | |
| # ⚠️ The `push:` trigger was MISSING from this workflow, and it is the only | |
| # repo in the fleet where that was true (checked against all 22 others; every | |
| # one of them carries this same list). | |
| # | |
| # Three jobs in the shared quality workflow run ONLY on `push`: | |
| # | |
| # SBOM `github.ref` must be main/beta/development | |
| # Features Extract `github.event_name != 'pull_request'` | |
| # Coverage Baseline Check `github.event_name == 'push'` | |
| # | |
| # With no push trigger, none of them could ever fire here. The last push run | |
| # on `development` is 23289135240, from **2026-03-19** — five months of | |
| # merges during which the SBOM was never regenerated, `docs/features.json` | |
| # was never checked against `openspec/specs/`, and the coverage ratchet never | |
| # ran. | |
| # | |
| # This is the hardest version of the problem to see: the inputs all say | |
| # `true`, the PR checks are green, and the Quality Report on every PR looks | |
| # complete — because on a PR those three jobs are *correctly* skipped. There | |
| # is no red, no skip anyone would question, and no run at all to inspect. You | |
| # can only find it by asking what the LAST PUSH RUN was, which nothing | |
| # prompts you to do. | |
| # | |
| # `enable-coverage-guard` was switched on here in the previous commit; without | |
| # this trigger its push-side half would have been dead on arrival. | |
| push: | |
| # An ALLOW-LIST of branch prefixes is a gate with a hole in it, and the | |
| # hole is SILENT: a branch matching nothing gets no CI at all, and its last | |
| # visible status is whatever it inherited — indistinguishable, on every | |
| # dashboard, from a branch that passed. | |
| # | |
| # Two live examples, both found 2026-08-14: `perf/**` was uncovered in | |
| # openconnector, where a merge carrying unresolved conflict markers and 84 | |
| # failing tests was pushed and nothing ran; and `feat/**` was uncovered in | |
| # openregister — note the list said `feature/**`, so every branch anyone | |
| # named `feat/...` had been running unchecked. | |
| # | |
| # Prefixes are added rather than replaced with `**` because this workflow is | |
| # expensive (PHPUnit matrix, Newman, Playwright). The fast structural checks | |
| # DO run on `**` — see merge-hygiene.yml, added in the same change. | |
| # | |
| # ⚠️ Adding prefixes is not the durable fix; the next invented one is | |
| # uncovered again. The durable fix is branch protection requiring a PR into | |
| # development, which the pull_request trigger below already gates correctly. | |
| branches: | |
| - main | |
| - beta | |
| - development | |
| - feature/** | |
| - feat/** | |
| - bugfix/** | |
| - hotfix/** | |
| - perf/** | |
| - refactor/** | |
| - chore/** | |
| - fix/** | |
| pull_request: | |
| branches: [main, beta, development] | |
| # Same family of defect as the missing `push:` above, one step further along: | |
| # there was no way to run this workflow ON PURPOSE either. | |
| # | |
| # This repo and `larpingapp` were the ONLY two of the sixteen fleet apps | |
| # without a manual trigger — checked on 2026-08-10 by reading | |
| # `.github/workflows/code-quality.yml` on `development` in all sixteen; the | |
| # other fourteen already carry it. | |
| # | |
| # The cost was not "slightly less convenient". Every fleet-wide gate sweep run | |
| # in this programme is a `workflow_dispatch` fan-out, so this repo was not | |
| # failing those sweeps and was not passing them — it was ABSENT from the | |
| # results table. In a table of fourteen verdicts an unmeasured repo looks | |
| # exactly like one that was never a problem: silence read as health, which is | |
| # precisely how the five-month `push:` outage documented above survived. | |
| # | |
| # A dispatch here is also worth more than a re-run of CI. The shared workflow | |
| # scopes `workflow_dispatch` to the FULL repository (there is no PR target and | |
| # no previous pushed tip to diff against, so ADR-020 diff-scoping has nothing | |
| # to scope to and the gates scan the entire tree). A push run on `development` | |
| # typically covers a single commit's files; this is the only way to ask "what | |
| # is the state of the whole app?" without opening a pull request. Expect it to | |
| # be redder than a PR — that is the honest answer, not a regression. | |
| workflow_dispatch: | |
| # Permission CEILING for the called quality pipeline, NOT a grant. Copied | |
| # verbatim from openconnector, where this exact block is live on `development` | |
| # with all ~30 quality jobs green — so it is a measured ceiling, not a guess. | |
| # | |
| # ⚠️ Most jobs in the callee declare NO permissions of their own (only | |
| # journeydoc-capture and features-extract do), which means they inherit THIS | |
| # ceiling exactly. Every entry below is load-bearing: | |
| # | |
| # contents: write journeydoc-capture and update-baseline `git push` | |
| # the regenerated screenshots / coverage baseline | |
| # actions: write journeydoc-capture re-dispatches downstream work | |
| # (a GITHUB_TOKEN push does not fire workflow_run) | |
| # issues / pull-requests: write | |
| # the Quality Report comment — `github.rest.issues | |
| # .createComment`, which 403s under contents+packages | |
| # read alone (quality.yml notes this at its own step) | |
| # packages: read pulling org images for the Nextcloud test matrix | |
| # | |
| # ⚠️ Do NOT "tighten" any of these to `read`. GitHub statically validates the | |
| # called workflow's DECLARED job permissions against this grant — including for | |
| # jobs an `if:` will skip — so capping one makes the whole call fail to START: | |
| # zero jobs, no annotations, and a Code Quality run that is absent rather than | |
| # red. That is quieter than the outage it would cause. | |
| permissions: | |
| contents: write | |
| actions: write | |
| issues: write | |
| pull-requests: write | |
| packages: read | |
| jobs: | |
| quality: | |
| uses: ConductionNL/.github/.github/workflows/quality.yml@main | |
| with: | |
| app-name: softwarecatalog | |
| php-version: "8.3" | |
| # THE LIST IS THE WHOLE DECLARED RANGE. appinfo/info.xml declares | |
| # <nextcloud min-version="32" max-version="34"/>, so 32, 33 and 34 each get | |
| # a leg. Adopting NC 34 by REPLACING the list left 32 and 33 advertised to | |
| # the App Store with no job touching them — the declared floor became the | |
| # untested end, which is the same drift as never testing 34, reversed. | |
| # stable34 leads because newman, playwright and journeydoc-capture all read | |
| # `fromJSON(inputs.nextcloud-test-refs)[0]` as their single server, and 34 | |
| # is the major this app had never been exercised on. | |
| nextcloud-test-refs: '["stable34", "stable32", "stable33"]' | |
| enable-psalm: true | |
| enable-phpstan: true | |
| enable-phpmetrics: true | |
| enable-frontend: true | |
| enable-eslint: true | |
| enable-phpunit: true | |
| enable-sbom: true | |
| # ⚠️ `Conduction/openregister` 404s — the org is `ConductionNL`. The | |
| # earlier org-ref fix corrected the `uses:` line above but not this | |
| # EMBEDDED repo reference, because a `uses:`-grep does not see it. With a | |
| # dead ref the openregister dependency was never checked out, so every | |
| # job that needs it ran against a missing app. | |
| # `ref` was absent, so this checked out openregister's DEFAULT branch | |
| # (`main`). Every other app in this fleet is developed against | |
| # openregister `development`, and the e2e suite drives OpenRegister's | |
| # object API directly (tests/e2e/workflows/_fixtures.ts), so testing | |
| # against `main` measures a different backend than the one this app is | |
| # written for. Pinned to `development` to match. | |
| additional-apps: '[{"repo":"ConductionNL/openregister","app":"openregister","ref":"development"}]' | |
| # Newman disabled: tests/magic-mapper-import.postman_collection.json was | |
| # written against a dev env with URL rewriting + a fixed disk layout — it | |
| # hits bare paths like `/configurations` and uploads files from | |
| # `/var/www/html/custom_apps/openregister/...`, neither of which exists on | |
| # the CI runner (NC lives under `server/apps/`, no rewrite → `/index.php/...`). | |
| # Re-enable after the collection is rewritten to the CI base_url/paths + | |
| # a seed step provisions the `voorzieningen` register/`module` schema. | |
| enable-newman: false | |
| newman-collection-path: "tests" | |
| # ── E2E browser tests ──────────────────────────────────────────────── | |
| # `playwright-test-path` does double duty in the shared workflow: | |
| # 1. it is the directory the "Validate Playwright tests exist" step | |
| # counts *.spec.ts in; | |
| # 2. it is the FIRST place the run step looks for a config — | |
| # `${playwright-test-path}/playwright.config.ts`, falling back to | |
| # the repo root only if that file is absent. | |
| # We ship tests/e2e/playwright.config.ts precisely so lookup (2) hits it. | |
| # The run step passes no `--project`, so the ROOT config would also run | |
| # its `visual` project — pixel baselines whose own header says a CI Linux | |
| # runner cannot byte-match a dev-container PNG. The tests/e2e config | |
| # declares only `smoke` + `chromium`. | |
| enable-playwright: true | |
| playwright-test-path: tests/e2e | |
| # `occ app:enable softwarecatalog` runs InitializeSettings, but that | |
| # repair step has no user session (OpenRegister RBAC can deny it), | |
| # swallows its own exception as a warning, and imports with | |
| # `force: false` — so a fresh install can come up with no `voorzieningen` | |
| # register at all and nothing exits non-zero. The script imports | |
| # explicitly over the admin API and fails loudly if the registers, | |
| # schemas, or the app's own register→schema mapping still aren't there. | |
| # cwd for this step is the Nextcloud server root. | |
| playwright-seed-command: 'bash apps/softwarecatalog/tests/e2e/ci-seed.sh' | |
| # Integration Tests (Newman) stays OFF here, deliberately. The | |
| # `enable-newman: false` further up is not a default nobody chose: it | |
| # records that the collection's requests hard-code | |
| # `/var/www/html/custom_apps/openregister/...` paths that do not exist on | |
| # a CI runner (Nextcloud lives under `server/apps/`, no rewrite), and that | |
| # it assumes a seeded `voorzieningen` register / `module` schema. Turning | |
| # it on would produce a guaranteed red whose cause is already written | |
| # down. Flipped back on in the commit that rewrites the collection to the | |
| # CI base_url and adds the seed step. | |
| # | |
| # Noted while checking this, because it is the kind of thing that reads as | |
| # working when it is not: the validator counts collections RECURSIVELY | |
| # (`find`) while the run step globs them FLAT (`for collection in | |
| # *.postman_collection.json` after `cd`). A `newman-collection-path` that | |
| # only satisfies the validator passes validation and then runs nothing. | |
| # The configured `tests` path does satisfy both here. | |
| # ── Frontend Check legs ────────────────────────────────────────────── | |
| # `frontend-checks` defaults to `[]`, and an empty list means the shared | |
| # workflow emits NO "Frontend Check" job at all — so these three | |
| # validators ran nowhere while the run still looked complete. | |
| # Measured on this tree before enabling: ALL THREE PASS. They are enabled | |
| # to keep them passing, not because they are currently broken. | |
| # `test:l10n:parity` is deliberately NOT added: measured on this tree it | |
| # is short 404+ translations across the required locales. That is a | |
| # translation backlog, and a permanently-red leg is one that gets switched | |
| # off again. | |
| # `test` / `test:unit` are NOT listed: "Frontend Tests (unit)" runs them. | |
| # | |
| # `format` (prettier --check) is listed because the shared workflow has NO | |
| # prettier job of its own — `quality.yml` runs eslint and stylelint and | |
| # mentions prettier ZERO times. This repo already carries | |
| # `@nextcloud/prettier-config` and a `format` script, so without this leg | |
| # `npm run format` never runs outside a developer's editor and the tree | |
| # drifts straight back out of format between merges — the same inert- | |
| # formatter failure mode that made the old `.prettierrc` worth deleting. | |
| # Centralising the config never stopped drift; the gate does. | |
| # Measured on this tree before enabling: PASSES, 188 of 235 tracked | |
| # frontend files in scope (l10n/ and docs/ excluded via .prettierignore / | |
| # .gitignore, which prettier 3 also reads). | |
| frontend-checks: '["check:manifest", "check:vue-demi", "test:l10n", "format"]' | |
| # ── Coverage ratchet ───────────────────────────────────────────────── | |
| # `enable-coverage-guard` defaults to FALSE, which is why both | |
| # "Coverage Baseline Protection" and "Coverage Baseline Check" have only | |
| # ever reported `skipped`. It needs two inputs this repo did not have, | |
| # both added in this commit: `scripts/coverage-guard.php` (byte-identical | |
| # to openregister's) and `.coverage-baseline` = 13.01, this repo's own | |
| # measured coverage (4077 of 31330 statements) read from clover.xml in the | |
| # `coverage-report` artifact of run 30911570224. That is the lowest figure | |
| # in the fleet by a wide margin and is recorded here as measured, not | |
| # rounded up: the ratchet's job is to stop it falling further, and a | |
| # baseline set above the truth would fail on the first honest run. | |
| enable-coverage-guard: true | |
| # ── Hydra mechanical gates ─────────────────────────────────────────── | |
| # `enable-hydra-gates` defaults to FALSE, so this tier had never executed | |
| # here — the job reported `skipped`, which the Quality Report renders | |
| # identically to a pass. | |
| enable-hydra-gates: true | |
| # No `hydra-gates-ref` here on purpose. The shared workflow defaults it | |
| # to @main, and this workflow is itself consumed at @main, so the two | |
| # sides move together and a gate fix reaches this repo without a commit | |
| # in this repo. A pin is a silent expiry date: 22 repos sat on v1.0.1 and | |
| # 16 gates were dead fleet-wide while every one reported PASS (.github#159), | |
| # and a default flipped at @main later reached those old runners and made | |
| # them red on gates they had no subject matter for (.github#173) — this | |
| # repo felt both, the second one blocking #434. | |
| # To hold this repo still for a specific reason, set the input explicitly | |
| # and say why — it is still honoured. To roll back for everyone, revert on | |
| # ConductionNL/.github main. | |
| # | |
| # THIRD CAUSE, and the one that is failing this repo RIGHT NOW | |
| # (.github#177): quality.yml@main began executing three gate helpers BY | |
| # NAME — check_spec_anchors.py, check_form_labels.py and | |
| # check_license_triangle.py — which exist in NO tag before v1.5.0. | |
| # Verified by DIRECTORY LISTING of each tag, not by per-file lookups: | |
| # those answered "present" uniformly across v1.0.0..v1.5.0, and the | |
| # uniformity across independent inputs was the tell that the instrument | |
| # was wrong. So the Hydra Gates job here fails at "Verify the pinned gates | |
| # package satisfies this workflow", before a single gate runs, with the | |
| # workflow's own words: "This is NOT a code-quality finding about your | |
| # repository." Removing the pin is the repair. | |
| # | |
| # Unpinning also picks up v1.5.1's push scoping (.github#179): on a push to | |
| # `development`, `origin/development` IS `HEAD`, so the diff was empty by | |
| # construction — <= v1.4.0 passed over it (permanently green) and v1.5.0 | |
| # refused with exit 99 (permanently red). The scope is now | |
| # `github.event.before...HEAD`, what the push actually changed. | |
| # | |
| # `enable-axe` is deliberately still NOT set — a vanilla Nextcloud 34 | |
| # reports serious/critical violations on core's OWN routes that DOM | |
| # scoping does not remove. Enabling axe is a separate decision. |