Skip to content

chore(ci): remove dead Forgejo/Codeberg CI configuration #823

chore(ci): remove dead Forgejo/Codeberg CI configuration

chore(ci): remove dead Forgejo/Codeberg CI configuration #823

Workflow file for this run

name: Code Quality
on:
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
- development
- feature/**
- feat/**
- bugfix/**
- hotfix/**
- perf/**
- refactor/**
- chore/**
- fix/**
pull_request:
branches: [main, master, development, beta]
workflow_dispatch:
# Deduplicating a `push` run against the `pull_request` run for the SAME head
# ref is the point of this block, and for a feature branch it is exactly right:
# two runs of identical jobs, one of them wasted.
#
# It is wrong for `main` and `development`, because the push run there is NOT a
# duplicate — it is the only carrier of the push-only jobs: "Coverage Baseline
# Check" (`github.event_name == 'push'`), "SBOM" and "Features Extract". And
# those two branches always have an open PR whose `head_ref` IS the branch
# name: the standing "Release: merge development into beta" (#126 here).
# `github.head_ref` on that PR run and `github.ref_name` on the push run both
# render the identical group `quality-development`, and `cancel-in-progress`
# then kills whichever started first — normally the push run.
#
# HONEST SCOPE OF THE EVIDENCE HERE: unlike most of the fleet, this repo shows
# NO concurrency-cancelled `development` push run in its last 20 (its two
# cancellations are the shared workflow's 45-minute cap, not this block), and
# its push-only jobs demonstrably DO execute — run 31035521461 and 31030407282
# both report Coverage Baseline Check success. So this change is PREVENTIVE,
# not a repair of an observed outage: the collision is latent in the expression
# and has simply not won the race here yet. It is applied for fleet uniformity
# with openconnector#1158, where the same expression cancelled 15 of the last
# 20 push runs.
#
# Suffixing only the default-branch push keeps feature-branch dedup untouched
# (`quality-feature/x` for both events, exactly as before) and gives the two
# default branches' push runs a lane of their own.
concurrency:
# SUFFIXED BY EVENT NAME, not just by `-push`.
#
# The previous expression gave a push on `development` its own lane
# (`-push`) but left EVERYTHING ELSE sharing `quality-development` — and
# that is not a quiet lane: `Sync to Beta` keeps a PR open whose head_ref
# IS `development`, so its run computes the same group and is re-triggered
# on every merge.
#
# A `workflow_dispatch` therefore shared a group with that PR and was
# cancelled by it. Measured on shillinq 2026-08-21: dispatch 32487948678
# cancelled by pull_request run 32490160836 (head_branch `development`).
# A run someone deliberately asked for could essentially never complete.
#
# That reaches past ad-hoc verification: the fleet gate-drift sweep
# (.github#523) dispatches per app with `--ref development`, because
# `schedule:` cannot choose a branch. Under the old group those runs are
# cancelled and report neither pass nor fail — and a routine that produces
# no verdict is indistinguishable from one that never ran.
#
# This is hermiq's form, already live there. Pull requests keep the bare
# group (so a PR still supersedes its own earlier run); push, dispatch and
# schedule each get their own lane.
group: quality-${{ github.head_ref || github.ref_name }}${{ (github.event_name != 'pull_request' && (github.ref_name == 'main' || github.ref_name == 'development')) && format('-{0}', github.event_name) || '' }}
cancel-in-progress: true
# Permission CEILING for the called quality pipeline. GitHub statically
# validates the called workflow's declared job permissions against this
# grant — even for jobs that are disabled — so it must cover the maximum
# any nested job declares: journeydoc-capture (contents+actions write),
# update-baseline / features-extract (contents write), and the Quality
# Report PR comment (issues / pull-requests write).
permissions:
contents: write
actions: write
issues: write
pull-requests: write
jobs:
quality:
uses: ConductionNL/.github/.github/workflows/quality.yml@main
with:
app-name: keepiq
php-version: "8.3"
php-test-versions: '["8.3", "8.4"]'
# Order matters: the PHPUnit matrix uses the whole list, but the E2E
# (Playwright), Newman and Journeydoc jobs all check out
# `fromJSON(nextcloud-test-refs)[0]` as their single server. That server
# has to be one OpenRegister can load, because `additional-apps` below
# installs it and keepiq's AppHost integration delegates to it —
# OpenRegister's lib/ContextChat/ContentProvider.php implements
# `OCP\ContextChat\IContentProvider`, which exists in stable32 and NOT in
# stable31 (verified: raw.githubusercontent.com returns 404 for the
# stable31 path). On stable31 every `occ` invocation printed
# `Interface "OCP\ContextChat\IContentProvider" not found` while loading
# commands from openregister's info.xml.
#
# The earlier reorder fixed the FIRST-entry problem for E2E/Newman/Journeydoc
# but left "stable31 is still covered by the PHPUnit matrix" — and that leg
# is broken by the same fact. The phpunit job ALSO installs `additional-apps`
# (shared quality.yml, "Checkout additional apps" + "Enabling app: …"), and
# its `occ app:enable openregister` failure is only a ::warning::, so the
# stable31 leg ran on without OpenRegister loaded. openregister has since
# made the floor explicit — `<nextcloud min-version="32"/>`, 8d5181f7a — so
# NC31 is now a configuration this fixture cannot produce at all.
#
# Removing stable31 corrects an impossible configuration; it does not reduce
# coverage, because nothing was being covered on that leg.
#
# 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.
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-newman: true
# Keepiq adopts OpenRegister's AppHost engine in lib/AppInfo/Application.php
# (ADR-040 / ADR-022) and ships lib/Settings/keepiq_register.json, so
# OpenRegister must be present in the test instance — otherwise
# SettingsService::isOpenRegisterAvailable() is false, InitializeSettings
# skips the register import with a warning, and the app boots without the
# host engine it delegates to.
#
# NOTE the org is ConductionNL, not Conduction. `Conduction` does not
# exist, and the shared workflow clones additional-apps UNAUTHENTICATED —
# so a bad ref does not report a clean 404. git falls through to
# credential prompting and the step dies with
# fatal: could not read Username for 'https://github.com' (exit 128)
# which reads as an auth problem rather than a typo. Six fleet repos hit
# this; in pipelinq it killed all four PHPUnit legs and the E2E job at the
# clone step, so those gates had never executed a single test.
additional-apps: '[{"repo":"ConductionNL/openregister","app":"openregister","ref":"development"}]'
enable-sbom: true
# ── 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 run BOTH
# of its projects — including `visual`, whose own header records that a
# CI Linux runner cannot byte-match a dev-container PNG baseline. The
# tests/e2e config declares only the chromium regression project and
# writes its report to the app root, where the workflow's
# upload-artifact steps actually look.
enable-playwright: true
playwright-test-path: tests/e2e
# Keepiq's nine workflow specs UNLOCK the vault with the development
# master password seeded by lib/Repair/SeedDevelopmentData.php — and that
# repair step returns immediately unless the `debug` system config is
# true, which `occ maintenance:install` does not set. A stock CI install
# therefore comes up with no dev vault at all and nothing exits non-zero.
# The script turns debug on, re-runs the repair steps, imports the
# register through OpenRegister's admin API, and fails loudly if the
# suite / secrets / schemas still aren't there.
# cwd for this step is the Nextcloud server root.
playwright-seed-command: 'bash apps/keepiq/tests/e2e/ci-seed.sh'
# ── Frontend Check legs ──────────────────────────────────────────────
# `frontend-checks` defaults to `[]`, and an empty list means the shared
# workflow emits NO "Frontend Check" job at all — so these two validators
# ran nowhere while the run still looked complete. Both are self-contained
# `node` scripts, which is what a leg has to be (each leg is a fresh job
# with its own checkout + `npm ci`).
# `test` / `test:unit` are NOT listed: the shared "Frontend Tests (unit)"
# job already auto-detects and 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, 294 of 310 tracked
# frontend files in scope. This repo has TWO documentation trees and both
# are excluded by .prettierignore — `docs/` and the separate `docusaurus/`
# site, which has its own package.json and its own toolchain.
# `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:manifest", "test:l10n", "format", "check:l10n-js", "check:schema-l10n"]'
# ── Coverage ratchet ─────────────────────────────────────────────────
# `enable-coverage-guard` defaults to FALSE, which is why both
# "Coverage Baseline Protection" (PR side) and "Coverage Baseline Check"
# (push side) 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 the copies in
# openregister and procest) and `.coverage-baseline` = 55.78, this repo's
# own measured coverage (6838 of 12258 statements) read from clover.xml
# in the `coverage-report` artifact of run 30911329266.
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. Note that gate-33
# (axe-core) reads tests/axe/report.json, which the Playwright job writes
# ONLY when `enable-axe: true` — un-skipping Playwright is not the fix;
# with axe off the gate declares itself not applicable.
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.