S128 — PSR-12 for tests/, minus two sniffs that PSR-12 does not actually require of test code. This file is DELIBERATELY separate from the src/ gate (`phpcs --standard=PSR12 src/`), which stays untouched: src/ keeps full PSR-12 with zero exclusions. THE MEASUREMENT THAT CHOSE THESE TWO EXCLUSIONS (origin/master @ 12bdea6c, phpcs 3.13.5, one-off `--standard=PSR12 tests/ --report=summary` run — the totals footer and the per-sniff table quoted below are `summary`-report output, not what the CI gate's default `full` report prints): A TOTAL OF 2155 ERRORS AND 288 WARNINGS WERE FOUND IN 293 FILES PSR1.Methods.CamelCapsMethodName ............... 2065 (95.8% of errors) PSR1.Classes.ClassDeclaration.MultipleClasses .. 63 everything else ................................ 27 (+ 288 warnings: 286 Generic.Files.LineLength, 2 PSR1.Files.SideEffects) Only 23 of the 2443 violations were auto-fixable by phpcbf, so "run phpcbf and commit" was never on the table. The two sniff exclusions below account for 2128 of the 2155 errors; of the 27 that remained, 25 were FIXED (22 by phpcbf — call signatures, control-structure spacing, an anonymous-class brace — and 3 file-level docblocks moved above their `declare(strict_types=1);` by hand, per PSR-12 §3) and 2 are carved out per-file below. There is no baseline and no ignore list. 1. PSR1.Methods.CamelCapsMethodName — 2065 errors, and all 2065 were classified rather than assumed: every one is a snake_case test method name (`test_playback_started_dispatch_reaches_listener`, 1980 distinct names). ZERO were acronym cases and ZERO were leading-uppercase, so this is one deliberate naming convention, not accumulated sloppiness. PSR-12 §4.3 governs the declared API of production classes; PHPUnit test methods are discovered by the `test` prefix and read as sentences, and snake_case is a mainstream PHPUnit style. Renaming 1980 methods would be the largest behaviour-free diff in the repo's history, would break every `--filter` expression and `@depends` reference pointing at them, and would buy nothing. src/ is unaffected: it passes this sniff today with 0 errors. 2. PSR1.Classes.ClassDeclaration.MultipleClasses — 63 errors. These are test doubles declared in the same file as the test that uses them, which keeps a double next to its only consumer and out of the autoloadable namespace. Splitting 63 files' doubles into 63+ new files makes the suite harder to read, not more correct. WARNINGS ARE REPORTED AND DO NOT BLOCK — and the difference from `-n` matters. phpcs exits non-zero on WARNINGS ALONE, so a gate that trusts its exit code would be permanently red here over 288 pre-existing warnings, and the next person would silence it with `-n`. That is precisely how S109's defect happened: `-n` on the src/ gate SUPPRESSED 8 real PSR-12 warnings for months. So the `ignore_warnings_on_exit` config below is set instead — it changes only the exit code. Every warning is still counted and still printed in the job log, and the per-file "FOUND n ERRORS … AFFECTING m LINES" footers of the default `full` report are the lines to read there. No "A TOTAL OF" line ever appears in this job's log — that footer belongs to the `summary` report, which the CI invocation (`--standard=phpcs-tests.xml --colors`) never requests; a grep for it could never fire. The gate is phpcs's nonzero exit code, read directly by the runner, not a log line (see the corrected step comment in .github/workflows/coding-standards.yml @ 4c96f085). Do NOT add `-n` and do NOT exclude Generic.Files.LineLength; src/ keeps both, and so does this. The two remaining errors are carved out per-file below, each naming the thing that makes fixing it wrong rather than merely inconvenient. The 2 SideEffects warnings are deliberately NOT excluded: they cost nothing to leave visible, and an exclusion that buys no green is just a narrower gate. Anything added to the exclude list needs the same treatment: a count, a classification of what the violations actually ARE, and a reason PSR-12 does not require it of test code. tests/Unit/Support/StaticAnalysisScopeTest.php asserts this file exists, targets tests/, and carries EXACTLY the exclusions below — so the list cannot grow quietly, which is how an exclusion list becomes a baseline. tests tests/codeception/Helper/SyncPlay.php tests/Unit/Server/Http/Controllers/Admin/AdminSettingsControllerTest.php