-
Notifications
You must be signed in to change notification settings - Fork 0
307 lines (299 loc) · 18.8 KB
/
Copy pathcode-quality.yml
File metadata and controls
307 lines (299 loc) · 18.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
name: Code Quality
on:
push:
# DEFAULT BRANCHES ONLY. `pull_request` below carries every other branch.
#
# This was an allow-list of branch prefixes, and that was a gate with a
# SILENT hole: a branch matching nothing got no CI at all, and its last
# visible status was 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, because the
# list said `feature/**`.
#
# The comment that stood here said adding prefixes was not the durable fix,
# and that the durable fix was to let the pull_request trigger gate it.
# THIS IS THAT CHANGE.
#
# What forced it now: a push to a branch with an open PR ran the SAME 34
# jobs TWICE on the same commit. `concurrency` cannot dedupe them — the
# group is suffixed by event name deliberately (.github#540: a
# default-branch push carries jobs a PR run does not, and a dispatch must
# not be cancellable by a standing release PR), so the two events sit in
# different lanes BY DESIGN and both run to completion. Measured fleet-wide
# 2026-08-25..27, 659 of 2,106 Code Quality runs were that duplicate — 31%
# of the fleet's most expensive workflow, re-deciding a commit another run
# was already deciding. The account ceiling is 60 concurrent jobs (Team
# plan); the fleet was measured at 53 running with 1,528 jobs queued behind
# them, the oldest run 7 hours old and not yet started.
#
# NO BRANCH LOSES ITS FLOOR. merge-hygiene.yml runs on `'**'` — every
# branch anyone pushes, no prefix list to forget — and it is the check
# `development` actually requires. That is the smoke alarm; this workflow
# is the fire brigade and belongs on the PR. Of 668 feature-branch push
# runs in that window, only NINE were on a branch with no PR run beside
# them.
#
# The default branches STAY: their push runs are not duplicates, they are
# the only carrier of Coverage Baseline Check, SBOM and Features Extract,
# none of which run on a pull_request event.
branches:
- main
- development
pull_request:
branches: [main, beta, development]
# This repo and `softwarecatalog` 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, which is the same failure the
# dead-gate work has been chasing all along: silence read as health.
#
# 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 the same shape as the org-rename outage described below, which
# also produced zero jobs and no red test.
permissions:
contents: write
actions: write
issues: write
pull-requests: write
packages: read
jobs:
quality:
# ConductionNL, NOT Conduction. e2faa092 ("org rename") pointed all eight
# reusable-workflow calls in this repo at `Conduction/.github`, which
# Actions cannot resolve — so from 2026-06-01 every run of this workflow
# produced ZERO jobs and failed instantly, taking ESLint, PHPCS, PHPMD,
# PHPStan, the license/security scan and the 4-way PHPUnit matrix offline
# without a single red test to show for it.
uses: ConductionNL/.github/.github/workflows/quality.yml@main
with:
app-name: larpinq
# No `node-version` override. This used to pin 22 to escape a shared
# default of 20, which could not run `@nextcloud/eslint-config@9` (it
# declares `engines.node: ^22.14 || ^24 || >=26` and imports
# `findPackageJSON` from `node:module`, first available in 22.14).
#
# The shared default is now 24, so this override no longer raises the
# floor — it LOWERS it. Node 22 bundles npm 10, which cannot install from
# this repo's npm 11 lockfile and does not implement the .npmrc
# release-age cooldown at all, so keeping the pin would leave both
# silently broken while looking configured.
php-version: "8.3"
php-test-versions: '["8.3", "8.4"]'
# appinfo/info.xml declares <nextcloud min-version="32" max-version="34"/>,
# so the matrix only targets server branches this app can actually run on.
#
# stable31 is REMOVED, not "dropped for coverage". `additional-apps` below
# installs openregister, which declares min-version="32" and therefore
# cannot install on NC31 — `occ app:enable openregister` fails with only a
# ::warning::, so the run continued WITHOUT its data layer and every
# /apps/openregister/... call returned Nextcloud's HTML 404 page. A stable31
# leg was testing an impossible configuration, so its red said nothing.
#
# Order matters as well as membership: newman, playwright and
# journeydoc-capture all pin `fromJSON(inputs.nextcloud-test-refs)[0]`, so
# stable31 sitting FIRST meant those three jobs ran entirely on the version
# openregister cannot load.
#
# THE LIST IS THE WHOLE DECLARED RANGE. An earlier revision of this comment
# said "stable33 is deliberately NOT added: this change removes an
# impossible leg, it does not widen the matrix" — but the same change also
# dropped stable32, which was NOT impossible, it was the declared floor.
# appinfo/info.xml declares <nextcloud min-version="32" max-version="34"/>,
# so 32, 33 and 34 each get a leg; anything narrower advertises a range to
# the App Store that no job touches.
nextcloud-test-refs: '["stable34", "stable32", "stable33"]'
# Psalm was `false` and carried no reason. The shared workflow still
# creates a job literally named `PHP Quality (psalm)` for a disabled
# tool: it prints "Psalm is disabled — skipping." and concludes
# `success`. So the run showed a GREEN psalm cell over a tool that never
# executed — a check that did not run looking exactly like one that
# passed. (The uploaded `result-php-quality-psalm` artifact was 142 bytes.)
#
# The reason it was off was a broken config, not a broken codebase:
# psalm.xml pointed its stub at `vendor/nextcloud/ocp/OCP.bak/...`, a path
# only that one developer's container-symlinked vendor tree ever had, so
# Psalm aborted before analysing anything with "Cannot resolve stubfile
# path". Fixed in psalm.xml in this same change.
#
# Measured on a clean clone + `composer install`, php:8.3-cli (the CI
# PHP version): **No errors found**, 93.05% of the codebase inferred,
# 9.65s. Positive control, so this is not a green over an empty scope:
# planting one call to an undefined function in
# lib/AppInfo/Application.php::register() moved Psalm 0 -> 1 error,
# naming the plant at Application.php:84. Plant removed and re-verified
# clean.
enable-psalm: true
# PHPStan runs clean at level 5 with an empty baseline after the legacy
# quality cleanup (larpingapp-legacy-quality-cleanup), so it is now part
# of the unified CI gate alongside phpcs + phpmd.
enable-phpstan: true
enable-phpmetrics: true
enable-frontend: true
enable-eslint: true
enable-phpunit: true
# larpinq is a THIN CLIENT on OpenRegister — appinfo/info.xml has no
# database tables of its own and every entity (character, ability, skill,
# effect, item, event, …) is an OpenRegister object. Without openregister
# installed there is nothing for the SPA to render and nothing for the e2e
# fixtures to create, so the E2E job needs it on the CI instance.
#
# Pinned to `development`, NOT `main`: this app tracks `development`, and
# pinning the foundation to `main` is what makes a CI instance behave
# unlike any environment the app is actually developed against.
additional-apps: '[{"repo":"ConductionNL/openregister","app":"openregister","ref":"development"}]'
# ── E2E browser tests ────────────────────────────────────────────────
# Previously OFF, which made the shared workflow report
# "E2E Tests (Playwright)" as `skipped` — a conclusion that reads exactly
# like a job with nothing to complain about. The suite itself
# (10 spec files under tests/e2e/) has existed and been maintained for
# months; only the switch was missing.
#
# The one real blocker was seeding: `occ app:enable larpinq` runs the
# register import from an IRepairStep, which has NO user session, so
# OpenRegister's RBAC denies it — and the repair step swallows the
# exception as a warning so `occ app:enable` still exits 0. Addressed by
# not relying on it: `ci-seed.sh` performs the import explicitly over the
# admin HTTP API (POST /apps/larpinq/api/settings/reimport ->
# loadSettings(force: true), which also merges the register.d fragments)
# and then VERIFIES the register + all 11 schema slugs, so a bad provision
# is one loud step failure instead of a dozen misleading spec failures.
enable-playwright: true
# Double duty in the shared workflow: it is both the directory the
# "Validate Playwright tests exist" step counts *.spec.ts in, AND 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 that lookup hits it: the run
# step passes no `--project`, so the ROOT config would also run
# `docs-capture` (re-shooting every documentation screenshot on every PR)
# and `visual` (whose committed PNG baselines are host-font/GPU specific
# and cannot byte-match a CI runner — its own README says so).
playwright-test-path: tests/e2e
# cwd for this step is the Nextcloud server root.
playwright-seed-command: 'bash apps/larpinq/tests/e2e/ci-seed.sh'
# ── Integration tests ────────────────────────────────────────────────
# `enable-newman` defaults to FALSE and this caller never set it, so
# tests/integration/larpinq.postman_collection.json had never executed
# in CI. It sits at the default `newman-collection-path`
# (tests/integration), which is both the directory the validator counts
# collections in and the directory the run step globs
# `*.postman_collection.json` in — flat, not recursively.
enable-newman: true
# ── Frontend Check legs ──────────────────────────────────────────────
# `frontend-checks` defaults to `[]`, and an empty list means the shared
# workflow emits NO "Frontend Check" job at all — so this repo's
# validators ran nowhere while the run still looked complete.
# `check:specs` is the aggregate (json-strict + manifest + register),
# listed as ONE leg rather than three because each leg is a fresh job
# with its own checkout + `npm ci`.
# Measured on this tree before enabling: BOTH legs FAIL —
# `check:specs` on `pages[23].type: "roadmap" not in known enum` (the same
# defect reproduces in 10 other fleet repos) and `test:l10n` on 19 missing
# locale entries. Real pre-existing defects; the gate is what makes them
# visible.
# `test:unit` is NOT listed: "Frontend Tests (unit)" already runs it.
#
# `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, 53 of 60 tracked frontend
# files in scope (docs/ excluded via .prettierignore; js/ and vendor/ via
# .gitignore, which prettier 3 also reads).
# `check:l10n-js` regenerates l10n/<locale>.js from the JSON catalogue and
# fails when the committed file is stale. Nextcloud serves ONLY the JS half
# to a browser — raw JSON out of an app directory is a 404 — so a catalogue
# that exists only as JSON renders English in the entire UI while every
# server-rendered string is translated, with no error anywhere.
# `check:schema-l10n` is a RATCHET, not a gate. Every string inside a form
# comes from the schema and is a key in THIS app's catalogue; an absent key
# renders the English source inside an otherwise translated form, silently.
# The fleet had 30,459 such strings, so this records the current count and
# fails only when it GROWS — burning it down stays an ordinary PR.
frontend-checks: '["check:specs", "test:l10n", "format", "check:l10n-js", "check:schema-l10n"]'
# ── 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` = 67.68, this repo's own
# measured coverage (917 of 1355 statements) read from clover.xml in the
# `coverage-report` artifact of run 30911642749.
enable-coverage-guard: true
# ── Hydra mechanical gates ───────────────────────────────────────────
# `enable-hydra-gates` defaults to FALSE, so this tier has never executed
# here — the job reported `skipped`, which the Quality Report renders
# identically to a pass.
# `enable-axe` deliberately NOT set: a vanilla Nextcloud 34 already carries
# serious/critical violations on core's OWN routes that DOM scoping does
# not remove. Enabling axe is a separate decision.
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).
# 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.