-
Notifications
You must be signed in to change notification settings - Fork 3
292 lines (284 loc) · 17.5 KB
/
Copy pathcode-quality.yml
File metadata and controls
292 lines (284 loc) · 17.5 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
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:
# 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
- beta
- development
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: stackiq
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 `stackiq` 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 stackiq` 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 `stackiq`
# 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/stackiq/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 `stackiq` 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).
# `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:manifest", "check:vue-demi", "test:l10n", "format", "check:schema-l10n", "check:l10n-js"]'
# ── 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.