Release: merge development into beta - #90
Conversation
Quality Report
Summary
PHP Quality
Vue Quality
Security
License Compliance
composer dependencies (94 total)
npm dependencies (248 total)
PHPUnit TestsPHPUnit tests were not enabled for this run. Integration Tests (Newman)Newman integration tests were not enabled for this run. Generated automatically by the Quality workflow.
|
Quality Report
Summary
PHP Quality
Vue Quality
Security
License Compliance
composer dependencies (94 total)
npm dependencies (248 total)
PHPUnit TestsPHPUnit tests were not enabled for this run. Integration Tests (Newman)Newman integration tests were not enabled for this run. Generated automatically by the Quality workflow.
|
Quality Report
Summary
PHP Quality
Vue Quality
Security
License Compliance
composer dependencies (94 total)
npm dependencies (248 total)
PHPUnit TestsPHPUnit tests were not enabled for this run. Integration Tests (Newman)Newman integration tests were not enabled for this run. Generated automatically by the Quality workflow.
|
Quality Report
Summary
PHP Quality
Vue Quality
Security
License Compliance
composer dependencies (94 total)
npm dependencies (248 total)
PHPUnit TestsPHPUnit tests were not enabled for this run. Integration Tests (Newman)Newman integration tests were not enabled for this run. Generated automatically by the Quality workflow.
|
Quality Report
Summary
PHP Quality
Vue Quality
Security
License Compliance
composer dependencies (94 total)
npm dependencies (248 total)
PHPUnit TestsPHPUnit tests were not enabled for this run. Integration Tests (Newman)Newman integration tests were not enabled for this run. Generated automatically by the Quality workflow.
|
Quality Report
Summary
PHP Quality
Vue Quality
Security
License Compliance
composer dependencies (94 total)
npm dependencies (248 total)
PHPUnit TestsPHPUnit tests were not enabled for this run. Integration Tests (Newman)Newman integration tests were not enabled for this run. Generated automatically by the Quality workflow.
|
Quality Report
Summary
PHP Quality
Vue Quality
Security
License Compliance
composer dependencies (94 total)
npm dependencies (248 total)
PHPUnit TestsPHPUnit tests were not enabled for this run. Integration Tests (Newman)Newman integration tests were not enabled for this run. Generated automatically by the Quality workflow.
|
Quality Report
Summary
PHP Quality
Vue Quality
Security
License Compliance
composer dependencies (94 total)
npm dependencies (248 total)
PHPUnit TestsPHPUnit tests were not enabled for this run. Integration Tests (Newman)Newman integration tests were not enabled for this run. E2E Tests (Playwright)Playwright E2E tests were not enabled for this run. Generated automatically by the Quality workflow.
|
Quality Report
Summary
PHP Quality
Vue Quality
Security
License Compliance
composer dependencies (94 total)
npm dependencies (248 total)
PHPUnit TestsPHPUnit tests were not enabled for this run. Integration Tests (Newman)Newman integration tests were not enabled for this run. E2E Tests (Playwright)Playwright E2E tests were not enabled for this run. Generated automatically by the Quality workflow.
|
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ✅ | ✅ 248/248 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-03 15:17 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ❌ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ❌ | ❌ | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-03 17:46 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ❌ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ❌ | ❌ | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-03 18:04 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ❌ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ❌ | ❌ | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-07 20:52 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ❌ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ❌ | ❌ | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-07 21:24 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ❌ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ❌ | ❌ | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-12 21:23 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ❌ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ❌ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ❌ | ❌ | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-12 21:55 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ❌ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ❌ | ❌ | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-12 22:33 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ❌ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ❌ | ❌ | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-12 22:41 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ✅ | ✅ 375/375 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-05-12 23:27 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ✅ | ✅ 375/375 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Coverage: 0% (0/10 statements)
Quality workflow — 2026-05-13 04:40 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ✅ | ✅ 376/376 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Coverage: 0% (0/10 statements)
Quality workflow — 2026-05-13 04:47 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ✅ | ✅ 376/376 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Coverage: 0% (0/10 statements)
Quality workflow — 2026-05-13 05:46 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ✅ | ✅ 376/376 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Coverage: 0% (0/10 statements)
Quality workflow — 2026-05-13 07:50 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ✅ | ✅ 376/376 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Coverage: 0% (0/10 statements)
Quality workflow — 2026-05-13 09:13 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ✅ | ✅ 376/376 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Coverage: 0% (0/10 statements)
Quality workflow — 2026-05-18 18:19 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ✅ | ✅ 376/376 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Coverage: 0% (0/10 statements)
Quality workflow — 2026-05-18 18:56 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ✅ | ✅ 376/376 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Coverage: 0% (0/10 statements)
Quality workflow — 2026-05-18 19:15 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ✅ | ✅ 376/376 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Coverage: 0% (0/10 statements)
Quality workflow — 2026-05-18 20:46 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ✅ | ✅ 376/376 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Coverage: 0% (0/10 statements)
Quality workflow — 2026-05-18 21:00 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 94/94 | |||
| npm | ✅ | ✅ 376/376 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Coverage: 0% (0/10 statements)
Quality workflow — 2026-05-18 21:17 UTC
Download the full PDF report from the workflow artifacts.
Pin the exact version (no caret). The 3.0.0 major removes CnFlowCanvas, CnEditFlowsModal and CnFlowCanvasModal; this app imports none of them. Peer set is unchanged from 2.1.0-vue3.16, so no new peers are declared. Verified from the lockfile: @conduction/nextcloud-vue 3.0.0-vue3.4 exact, vue3-apexcharts 1.8.0 (below the proprietary 1.9.0 line). Built with USE_LOCAL_LIB=false; built js/ carries CnFlowEditModal and CnAppNav and none of the three removed symbols. Co-authored-by: Ruben van der Linde <juan.claude@conduction.nl>
…ed config sharing (#250) RESCUED FROM CODEBERG. This landed on codeberg.org/Conduction/larpingapp as 4f9f4123 (PR #70, 2026-07-26) and never reached GitHub. Codeberg is being retired, so it would have been destroyed with the remote. Part of the 2026-07-26 federated-config-sharing wave, six repos of which were merged only on Codeberg: decidesk, docudesk, larpingapp, opencatalogi, softwarecatalog and (already present here) hermiq. Only the hunk is ported, not the file. GitHub's copy of this register is NEWER than Codeberg's in other respects — Codeberg still carries `"icon": "Earth"` for setting where GitHub has `"Globe"` — so taking the Codeberg file wholesale would have silently reverted unrelated work. Verified equivalent to the Codeberg blob after the port: gh version 1.2.0 | cb version 1.2.0 gh configuration {'x-openregister-shareable': True} | cb configuration {'x-openregister-shareable': True} MATCH
Codeberg is retired; ConductionNL is GitHub-only. GitHub Actions never executes .forgejo/**, so these workflows contributed zero status checks. Deleted 3 of 5: pre-merge-check-strict.yaml (covered by .github/workflows/code-quality.yml) and app-tests-live.yml + tests-live.yml. KEPT app-tests.yml + tests.yml -- tests.yml carries the only definition of the l10n extraction gate and the coverage ratchets (no .github/workflows/ l10n.yml here, enable-coverage-guard unset). Also repointed the .forgejo reference in tests/e2e/visual/README.md.
Enables `quality / E2E Tests (Playwright)`, which reported `skipped` on every run before this — a conclusion indistinguishable from a pass in every summary the pipeline prints. Measured green: **171 passed / 0 failed / 1 skipped in 8.5 min** (run 30895160734, job 91946606594), 10m53s job wall against a 45 min cap. Proven able to go red: a negative control on a throwaway branch truncated every `js/*.js` to 0 bytes and ran the suite unchanged — 118 of the 122 specs that reached a verdict failed (#251, job 91930520433). The four survivors were investigated: two are legitimate backend HTTP probes, two were genuinely dead and are fixed here. Real defects fixed, not hidden: - `character.ocName` is a required `format: uuid` relation to a `player`; three fixtures passed a display name and got HTTP 400. One of them failed silently and left seventeen character-detail specs asserting against `#/characters/seed-missing`. - Sidebar nav groups defaulted closed, so none of the eight entity entries existed in the DOM on the dashboard, contradicting `dashboard/spec.md#sidebar-shows-all-entity-views`. Fixed with `"open": true` in the manifest. - The first-visit walkthrough's full-viewport dim layer intercepted clicks and cost a spec a 60 s timeout; `ci-seed.sh` now marks it seen and verifies the write read back. - Four assertions that only held while a defect was present (or asserted nothing at all) now assert the thing their scenario names. - 23 parked `test.fixme` specs un-parked after measuring that they pass. Filed rather than fixed: #252 (event-data widget renders "Data" instead of its manifest title), #255 (computed character stats are not surfaced anywhere in the UI — the one remaining skip). Merged with --admin into `development`; the human gate sits between development and beta.
…quired context never reported (#256) The org rulesets "Main Branch Protection" and "Beta Branch Protection" require the status context `branch-protection / check-branch`. GitHub composes a reusable-workflow context as `<caller-job-id> / <called-job-name>`, and this caller job was named `protect` — so the check that actually got emitted was `protect / check-branch` and the required one never appeared. Every PR to main or beta therefore sat permanently pending on a check that could not arrive, and only `--admin` could merge. Same defect petstore had (petstore#20). larpingapp has both `beta` and `development`, so the branch topology the shared check-branch job requires (development -> beta, beta|hotfix/* -> main) is satisfied and making the gate report does not newly block anything.
chore(deps): @conduction/nextcloud-vue 3.0.0-vue3.6
…pping (#261) A skipped job and a passing job are indistinguishable in the Quality Report. Every gate turned on here reported 'skipped' in every run. Each newly-enabled leg was measured against this tree BEFORE being enabled; the results are in the PR description. Legs that were measured failing are enabled anyway - the defects are pre-existing, and the only thing that changed is that CI can now see them. Journeydoc Capture and enable-axe are deliberately NOT enabled.
…o its own ruleset (#263) phpmd.xml declared the rule with a NESTED <exclude-pattern>*Migration*</exclude-pattern> which is INERT. PHPMD 2.15 reads exclude-patterns in RuleSetFactory::getIgnorePattern(), which walks $xml->children() - only elements DIRECTLY under <ruleset>. A nested one parses without error and does nothing, so lib/Migration was always scanned by the rule the pattern was meant to spare, and both migrations needed an @SuppressWarnings(PHPMD.UnusedFormalParameter) tag to stay quiet. This file was inherited byte-identically from nextcloud-app-template (ConductionNL/.github#155, this repo #262). Promoting the pattern to the top level of phpmd.xml is not the fix: PDepend applies a top-level exclude-pattern at file-collection time, so it drops the file from EVERY rule in the ruleset. Measured on openregister, that shape is silently swallowing 11 real non-UnusedFormalParameter findings in lib/Migration. UnusedFormalParameter now lives alone in phpmd-unusedparams.xml with a TOP-LEVEL */Migration/* exclude-pattern, and the composer script runs both rulesets as separate legs keeping the worst exit code, so neither leg can short-circuit the other. The 2 now-redundant suppressions are deleted. Why lib/Migration is exempt from this one rule: OCP\Migration\IMigrationStep mandates changeSchema(IOutput $output, Closure $schemaClosure, array $options), so a step that uses none of the three parameters still cannot drop them. MEASURED - PHPMD 2.15.0 / PHP 8.4.22, both legs, worst exit code: suppression-free true count 23 -> 17 retired 6 UnusedFormalParameter, all in lib/Migration newly appearing 0 lib/Migration non-UFP 0 before, 0 after (nothing was swallowed) shipped reported count 0 -> 0, exit 0 on both legs Positive-controlled with probe classes in lib/Migration and lib/Probe: the Migration probe's UnusedFormalParameter is dropped while its ElseExpression is still reported by leg 1, and the lib/Probe probe reports from leg 2 - so the exclusion is real and scoped to the one rule, and leg 2 is not a dead gate even though it reports nothing on today's tree. No phpmd.baseline.xml exists in this repo; none was added or removed. No @SuppressWarnings was added. The 4 UnusedFormalParameter suppressions outside lib/Migration and the other 19 suppressions in lib/ are untouched. phpunit -c phpunit-unit.xml: 153 tests / 541 assertions before and after. Refs #262
…#264) * refactor(phpmd): burn the true finding count from 17 to 1 and the suppressions from 25 to 1 TRUE PHPMD findings — measured with EVERY @SuppressWarnings stripped on a throwaway copy, PHPMD 2.15.0 / PHP 8.4.22 inside nextcloud:latest — go **17 → 1**. Shipped findings stay at 0. There is no phpmd.baseline.xml in this repo and there must not be: PHPMD AUTO-DISCOVERS that filename, so a baseline stays active even after the `baseline` CLI flag is removed. phpmd.xml, phpmd-unusedparams.xml, phpcs.xml and every phpunit config are BYTE-UNCHANGED. No threshold was raised, no rule removed, no baseline added. The one surviving finding ------------------------- `Application::boot(IBootContext $context)` — UnusedFormalParameter. The signature is mandated by OCP\AppFramework\Bootstrap\IBootstrap and the body is legitimately empty: register/schema initialisation moved to the `InitializeRegister` repair step. The parameter can be neither dropped nor used. This is the floor. Clean differential as a positive control: TRUE = 1 finding / exit 2, SHIPPED = 0 findings / exit 0. The single surviving suppression demonstrably suppresses exactly one real finding, and the harness discriminates in both directions — so "0 shipped" is a measurement, not an artefact of a rule that never fired. Suppressions adjudicated: 25 → 1 -------------------------------- * DEAD, deleted — 8. Six `ShortVariable` tags (EventsController ×3, CharactersController ×2, RegisterObjectFetcher ×1) sat on methods whose only short variable is `$id`, which phpmd.xml's ShortVariable `exceptions` allowlist already covers — they suppressed nothing. One `UnusedFormalParameter` on `Application::register()`, which uses `$context`. One `NPathComplexity` on the listener's `handle()`, which never fired. * FIXABLE, fixed and deleted — 16. * JUSTIFIED, kept — 1 (`Application::boot()`). All 25 originally carried a bare tag with NO reason text; the survivor now carries a written one. What the fixes actually were ---------------------------- * EventsController (7 findings: CouplingBetweenObjects, ExcessiveClassComplexity, 3× CyclomaticComplexity, 2× NPathComplexity) — extracted `EventRosterService`, which now owns the participation, attendance and run-sheet-context rules. The controller is a thin HTTP/auth boundary again. Also collapsed the authenticate-then-authorize pair into `resolveGameMaster()` returning `[uid, refusal]`, and removed a duplicate of `resolvePlayerName` that had been inlined into the cast builder. * SkillRequirementService (4) — extracted `SkillRequirementChecker` (the four requirement kinds, with `requiredConditions`/`requiredEffects` collapsed into one shared membership check) and `IdListNormaliser`, used by both. `validate()` went from ~117 lines to orchestration. * CharacterService (1) — extracted `EffectApplier`, a stateless collaborator holding effect resolution, non-cumulative dedup and signed-modifier logic. Pure move; the arithmetic is unchanged. * CharacterRequirementListener (2) — extracted `extractEntities()` (early-return, so the `else` disappears rather than relocating) and `collectVeto()`. MEASURED, not assumed: removing an `else` is not complexity-neutral, so both the ElseExpression and the CyclomaticComplexity were re-measured and both cleared. * BooleanArgumentFlag ×2 — the rule fires on the parameter's DEFAULT VALUE, not the call site, which was confirmed before changing anything. Split into intention-revealing `loadSettings()` / `reloadSettings()` over a private `importRegister(bool $force)` with NO default. Three call sites and the test updated. Tests ----- 153 tests / 541 assertions, OK — before and after, identically conditioned, and re-run after every refactor round. Where a constructor gained a dependency the tests construct the REAL collaborator over the existing mock (e.g. `new EventRosterService($this->objectFetcher)`), so every assertion still exercises the same behaviour end to end. No assertion was weakened; the two `loadSettings` delegation tests gained an `expects($this->never())` on the sibling method. Also verified inside nextcloud:latest with `composer install` run IN this worktree: phpcs 0 errors, PHPStan "[OK] No errors", `php -l` clean across lib/ and tests/. All four new services are pure-autowired; the app registers no services explicitly, so no DI wiring was needed. Not done, and why ----------------- * `SkillRequirementService::resolveXpAbility()` is public with no caller. Left alone: it is documented as a shared resolution rule with `event-xp-award-workflow` and carries a @SPEC tag. Deleting a documented public API is not a debt burn-down. * Psalm dies before analysis in a bare container on `Cannot resolve stubfile path vendor/nextcloud/ocp/OCP.bak/...`. Pre-existing and unrelated — psalm.xml is untouched here and that directory does not exist in a fresh clone. psalm.xml was deliberately NOT edited to make it pass; PHPStan covers the same ground and is clean. * `tests/integration/EffectChainIntegrationTest.php` is run by NEITHER phpunit.xml nor phpunit-unit.xml — both include only tests/unit. Its constructor call and import were updated so it stays consistent and lints, but flagging it: that file is currently dead weight in CI and deserves its own look. * fix(licence): align the touched files' @license headers with the declared EUPL-1.2 gate-28 (license-triangle) went red on this PR, and it was MY change that surfaced it: the gate is diff-scoped, so a file's stale header only gets compared against composer.json once the file is edited. 11 of the files this PR touches declared AGPL-3.0-or-later while every authoritative declaration in the repo says EUPL-1.2 — composer.json `"license": "EUPL-1.2"`, appinfo/info.xml `<licence>EUPL-1.2</licence>`, and the LICENSE file itself ("EUROPEAN UNION PUBLIC LICENCE v. 1.2"). 19 files under lib/ already said EUPL-1.2; the AGPL headers are stale copy-paste from the upstream Nextcloud app skeleton. This is not a licence change: it makes the file headers agree with the licence the project already grants under. Both docblock positions (file and class) were corrected in the files this PR touches. Also reverts lib/Controller/CharactersController.php to its development state. WHY, in full, because reverting deliberate work needs a reason: the only change there was deleting two DEAD `@SuppressWarnings(PHPMD.ShortVariable)` tags (dead because the sole short variable is `$id`, which phpmd.xml's ShortVariable `exceptions` list already allows). But editing the file at all pulled it into the diff scope of gate-49 (controller-exception-translation), which then reported `downloadPdf()` and `requirementReport()`: both call `RegisterObjectFetcher::getObject()` and catch only a broad `\Exception`, with no tracked-exception catch and no `@throws`. That is a real pre-existing debt, and the honest fix was tried: split the catch into `DoesNotExistException` (client error, 404, silent) and `\Exception` (server-side — an unconfigured register — same 404 so nothing leaks, but logged). It works and gate-49 goes green. It also costs a PHPMD finding: the `use OCP\AppFramework\Db\DoesNotExistException;` import takes CharactersController's CouplingBetweenObjects from 12 to 13, one over the threshold, taking SHIPPED findings from 0 to 1. MEASURED, not assumed. An FQCN in the catch instead of an import is not a way out either — phpmd.xml enables MissingImport. The coupling headroom is gone because `downloadPdf()` re-implements, inline via ContainerInterface + IAppManager, exactly what `Service\DocuDeskPdfRenderer` already does (`isDocuDeskAvailable`, `normaliseTemplateId`, `getTemplate`, `render`) and which `EventsController` already consumes. Consolidating onto it would drop two dependencies, delete the duplication AND leave room for the exception import — but it rewrites ~60 lines and the mocks in CharactersControllerTest, which is its own change, not a rider on a PHPMD burn-down. Rather than ship a net PHPMD regression to satisfy a different gate, this PR leaves the file alone and the debt is written up in the PR description. Net effect: TRUE findings stay at 1 and SHIPPED at 0 (the two reverted suppressions were dead, so removing them never moved either number), gate-49 returns to its development state, and gate-28 is satisfied for every file this PR does touch.
…on/* to */lib/Migration/* (#266) `*/Migration/*` is a path-SEGMENT pattern, not a directory anchor. PDepend compiles an exclude-pattern into an UNANCHORED regex — Input\ExcludePathFilter preg_quote()s the pattern and then turns `\*` into `.*` — so it matches ANY path containing a `/Migration/` segment: `lib/Service/Migration/`, `lib/Command/Migration/`, any future `lib/*/Migration/`. Those are ordinary classes with no interface-mandated signature, and a genuine unused parameter in one of them would never be reported while the run still looked clean. PROBE MATRIX, PHPMD 2.15.0 / PHP 8.4.22, three probe classes on a throwaway copy: probe */Migration/* */lib/Migration/* lib/Migration/… UFP not reported not reported (intended) lib/Migration/… Else (leg 1) reported reported (leg 1 unaffected) lib/Service/… UFP reported reported lib/Service/Migration/… UFP NOT REPORTED reported (the leak) The IMigrationStep exclusion still does exactly its job — a step that cannot drop changeSchema/preSchemaChange/postSchemaChange's three mandated parameters is still spared — and leg 1 still analyses lib/Migration for every other rule. Real numbers: UNCHANGED. A grep for `/Migration/` directories outside lib/Migration/ found none in this repo, so nothing was actually being swallowed. The trap goes regardless. One process note worth recording: the first draft of this comment contained a literal `<-` inside the <description>, which made the ruleset UNPARSEABLE. PHPMD then exited 1 and reported NOTHING — and "no findings printed" is exactly what a clean run looks like to a grep. It was caught only by reading the exit code directly. The XML is validated as part of the check now.
…ue3.6 was unpublished) (#265)
…failing CI (#269) v1.0.1 is `f4d9756` (2026-08-03) and predates three gate fixes, so every Hydra Gates run this repo has ever made executed a script in which 16 gates reported PASS when their helper never ran (ConductionNL/.github#147), gate-33 had no axe report to read and never said so (#148), and gates 6 and 7 reported PASS on an empty scope (#149). The tick was identical either way, which is why nothing in this repo's history shows it. That pin is now also RED, and the mechanism is worth writing down. quality.yml is referenced `@main` while this package is PINNED, so the two can desync. #164 flipped `hydra-gates-require-full-coverage` to default true in the shared workflow, and that flag requires a gate to DECLARE itself not-applicable. v1.0.1 contains ZERO `_skip` calls; v1.3.0 has 36. v1.0.1 has no vocabulary to declare, so every absent prerequisite became "DID NOT RUN" and failed the job — for gates the repo has no subject matter for. Measured on this branch, diff-scoped against origin/development exactly as CI scopes it, in a private mount namespace with a private tmpfs (the runner's ~50 /tmp/hydra-gate-*.log paths are shared state and two concurrent runs corrupt each other's counts, .github#158 item 6): v1.0.1 exit 98 FAIL — "GATES THAT DID NOT RUN: 24 33" v1.3.0 exit 0 PASS — those gates named NOT APPLICABLE, with reasons Independently confirmed end-to-end: doriath#160 changed this one line and nothing else, and its Hydra Gates job went failure -> success. v1.3.0 is `f7eaf2a` = .github@main at the time it was cut. Refs ConductionNL/.github#159
The repo already declares EUPL-1.2 in composer.json, package.json, appinfo/info.xml and LICENSE, but 34 source files still carried AGPL-3.0 headers in three different shapes: - `@license AGPL-3.0-or-later ...` - `@license https://www.gnu.org/licenses/agpl-3.0.html GNU AGPL v3 or later` - `SPDX-License-Identifier: AGPL-3.0-or-later` All are now the canonical shape already used by lib/AppInfo/Application.php: * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 SPDX-License-Identifier: EUPL-1.2 The SPDX identifier is first so hydra gate-28 (license-triangle) parses it. Five files under lib/ were failing that gate; it now passes. Only `@license` / `SPDX-License-Identifier` lines were touched — every `@copyright` / `SPDX-FileCopyrightText` holder and year is unchanged. Header-only change: phpunit 153 tests / 541 assertions OK before and after.
…r, closing gate-49 (#267) * refactor(characters): consolidate downloadPdf onto DocuDeskPdfRenderer, closing gate-49 CharactersController failed hydra gate-49 (controller-exception-translation) on both downloadPdf() and requirementReport(): each calls RegisterObjectFetcher::getObject() but caught only \Exception, so an OpenRegister DoesNotExistException was never translated explicitly. The obvious fix -- importing DoesNotExistException -- is a net regression on its own. Measured on PHP 8.4.22 / PHPMD 2.15.0: import alone: CouplingBetweenObjects 12 -> 13, shipped findings 0 -> 1 DocuDeskPdfRenderer already existed and its docblock already claimed both the character-sheet and the event run-sheet export as callers -- but downloadPdf() never actually used it and re-implemented the whole pipeline inline. Routing downloadPdf() through the renderer (as EventsController::downloadRunsheet already does) drops IAppManager, ContainerInterface and LoggerInterface from the controller, which buys back the budget the exception import needs. before: CBO 12, shipped PHPMD findings 0, gate-49 FAIL (2 methods) after: CBO 11, shipped PHPMD findings 0, gate-49 PASS No behaviour change. The existing downloadPdf tests were kept driving a REAL DocuDeskPdfRenderer built from the same IAppManager/Container/Logger mocks the controller used to hold, so they still exercise the identical end-to-end pipeline and act as a behaviour-preservation check rather than an assertion about the new internal shape. Tests: 153/541 -> 173/577, all passing. Adds the first tests for DocuDeskPdfRenderer, which had none despite already serving EventsController, and pins the details this refactor could have silently changed: the template body, render context and page options handed to DocuDesk, the derived download filename, and template-id lower-casing. Fail-proof (mutation testing, each reverted): isDocuDeskAvailable -> true kills 3 tests (incl. controller 424) normaliseTemplateId -> passthrough kills 8 tests (incl. controller 400) render error -> '' instead of null kills 2 tests (incl. controller 500) No suppressions, baselines, thresholds or waivers were added or changed. * fix(license): align @license with composer.json EUPL-1.2 on the files this change touches gate-28 (license-triangle) is diff-scoped, so editing CharactersController pulled a pre-existing header drift into scope: the file declared AGPL-3.0 while composer.json declares EUPL-1.2. Fixed properly rather than waived, on the two files this change touches plus the new test. DashboardController, DeepLinkRegistrationListener and LarpingAppAdmin carry the same pre-existing drift but are outside this diff and are already covered by chore/eupl-license-normalisation-2026-08-05; left alone to avoid conflicting with that branch.
…kflow needs The Hydra Gates job fails with a message that says outright it is not about this repository: hydra-gates-ref <old> does not contain: scripts/lib/check_spec_anchors.py scripts/lib/check_form_labels.py scripts/lib/check_license_triangle.py The reusable workflow floats on @main and calls those scripts BY PATH inside the PINNED package, so a pin older than the scripts cannot run the gates that implement them. A pinned ref is a silent expiry date on every upstream change, and the failure reports on the pin while saying nothing about the code. v1.5.0 is the first tag containing all of them, verified by reading each path at that tag rather than assuming the newest tag has everything. Swept across the fleet: 11 of 13 repos were pinned below v1.5.0 and every one of them was failing this way.
…#272) .coverage-baseline was read as a floor by the phpunit guard and as an exact target by the push-side staleness check. Together they demand equality with a checked-in constant, which against a moving base branch is not satisfiable: closing "stale" means committing the value the tree will measure after the PR lands. Measured on openregister — committed 58.93, development advanced 16030->16038 tests, merge result measured 58.88, guard reported a 0.05% drop. coverage-guard.php gains --against=<clover.xml>, naming a report measured at the merge base. When present it is the only floor; the committed constant is reported but not enforced. Both numbers then come from one driver in one job, so the xdebug/pcov statement-counting difference cancels rather than being baked in, and the merge base cannot go stale. Ratios are compared as exact integer cross-products, not rounded percentages: at two decimals a one-statement regression read as "unchanged" and exited 0. An empty or zero-statement report is now a hard error rather than 0%, which as the merge-base side would set the floor to zero and pass every drop. Verified on real CI clover artifacts: a genuine 1.44% drop fails, an unchanged tree passes, and adding untested code fails while adding tested code passes.
* chore(ci): move hydra-gates-ref v1.3.0 -> v1.4.0 A pinned `hydra-gates-ref` is a silent expiry date on every upstream fix: this repo cannot receive a gate-package change until this line moves. v1.4.0 is the latest tag and the first one that carries `hydra-gates/scripts/axe-run.cjs` (verified absent at v1.3.0), so it is also the first that has ConductionNL/.github#168 axe DOM scoping and ConductionNL/.github#165 gate-46 fix. `enable-axe` is deliberately NOT enabled in this commit. Ordering matters: the ref lands first, enabling axe is a separate decision. * chore(ci): stop pinning hydra-gates — track the package at @main Removes the `hydra-gates-ref` input from the `quality.yml` caller. The shared workflow already defaults it to `main`, and this repo consumes `quality.yml` itself at `@main`, so dropping the override makes both sides move together: a gate-package fix lands here without a commit here. A pin is a silent expiry date on every upstream fix, and we have paid for that twice already: - .github#159 — 22 repos sat on v1.0.1, which predated the gate fixes. 16 gates were dead fleet-wide and every single one reported PASS. A gate that never runs emits a tick identical to one that did, so nothing in any repo's history showed it. - .github#173 — the shared side flipped a default at @main while the package stayed pinned per caller. Old runners lacked the coverage accounting the new default assumed, so they went red on gates they had no subject matter for. Removing the pin closes both shapes at once. Rolling back is a revert on ConductionNL/.github main, which reaches the whole fleet in one commit; holding this one repo still is still possible by setting the input explicitly, with a reason. `enable-hydra-gates: true` is unchanged. `enable-axe` remains unset. The comment block that justified the pin is replaced with a short note saying why there is no pin. --------- Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
| uses: ConductionNL/.github/.github/workflows/release-beta.yml@main | ||
| with: | ||
| app-name: larpingapp | ||
| channel: dev | ||
| secrets: inherit |
…ommand injection) (#278) quality / Security (composer) is red on every PR here as of today: Advisory ID: PKSA-rdkp-vv9z-mjkg CVE: CVE-2026-67434 — OS Command injection Affected versions: <3.13.6|>=4.0.0,<4.0.2 Reported at: 2026-08-05T23:53:11+00:00 The advisory was published YESTERDAY and roave/security-advisories installs as dev-latest each run, so the same lockfile was clean on 2026-08-05 and is vulnerable on 2026-08-06 with no commit in between. The last green run is evidence of when it ran, not that the lockfile is safe. composer.json's existing constraint already permits the fixed version, so this is a lockfile move only: 1 update, 0 installs, 0 removals. Verified the diff touches exactly two lines, both the version string, and no other file. Part of a fleet sweep — 13 of 16 repos checked were on the affected 3.13.5.
#279) OS command injection in PHP_CodeSniffer, GHSA-hmqg-cxww-wqhq, reported 2026-08-05. Affected: <3.13.6 | >=4.0.0,<4.0.2. This repo was on 3.13.5. All 16 repos checked across the fleet are on 3.13.5 and equally affected. The advisory is live in the audit DB, so this repo's Security (composer) gate is failing until this lands. Verified - composer audit --locked: 'No security vulnerability advisories found' (was 1). - vendor/bin/phpcs --version -> 3.13.6. - composer phpcs: rc=0. - Positive control: a deliberately non-conforming file under lib/ made phpcs exit 2 with 13 findings, so the green above is a real pass and not a checker that no-ops. Probe removed; only composer.lock is modified.
…es (#280) axe-core sat in `dependencies`, declaring an accessibility *testing* library as an application runtime dependency. Measured: nothing under src/ imports axe-core, and the built production bundle does not contain it — 0 hits for `axe-core`, `axe.run` and axe's own signature strings (`aria-allowed-attr`, `color-contrast`), while those strings are present in the installed axe-core (positive control). What put it in every app manifest is @conduction/nextcloud-vue, which declares axe-core as an OPTIONAL peerDependency. nc-vue does use it, but only in `src/testing/a11y.js` — a testing helper that is never imported from `src/index.js`, so it never reaches an app bundle. nc-vue's own file header states axe-core "is a devDependency" and that consumers wanting the a11y assertion "add `axe-core` to their OWN devDependencies". This change follows that instruction. Not a bundle-size fix; the bundle is byte-identical. It stops a test dependency being declared as production surface (SBOM, `npm ci --omit=dev`, advisory triage). An optional peer is satisfied by a devDependency, so nothing breaks. Verified: npm ci + production build both exit 0.
) min-version is enforced at install time, so a 32 floor makes occ app:enable refuse on stable31, which this repo's own CI runs. The e2e seed then fails with "is not installed or enabled". The reason the floor was raised no longer holds: openregister#2372 removed every eager reference to its ContextChat provider, so the class is only loaded behind interface_exists() guards and never read on an older server. openregister#2380 restored its own 28 floor on that evidence.
…eed a player for character.ocName (#281) * fix(pdf): validate the template id before probing for DocuDesk, and seed a player for character.ocName Newman on `development` (run 31110917018, job 92648343763): 1 collection · 27 requests · 43 assertions · 33 passed · 10 FAILED. Two root causes account for all ten. ## 1. The collection sent a free-text `ocName` (8 failures — fixture wrong) The character schema declares `ocName` as `{ "$ref": "player", "format": "uuid", "x-allow-create": true }` — it is the PLAYER RELATION, not an out-of-character display name. The collection sent `"ocName": "NEWMAN character"`. Measured live against the dev instance, status code printed: POST .../objects/larpingapp/character {"ocName":"NEWMAN character"} -> HTTP 400 "Property 'ocName' should match format 'uuid' but 'NEWMAN character' does not." The app is CORRECT; the fixture was not. `x-allow-create` is not an OpenRegister annotation at all (`grep -rn x-allow-create` over openregister returns nothing), so it is a silently-ignored hint and does not permit a bare name on write. Every other object create in the collection (item, skill, condition, effect, event, player) passed, because none of them carries a relation property. The failing create left `charId` undefined, which cascaded into the 404s on read / update / delete and put the literal `null` into the PDF URLs. Fix: a `0. Setup` folder seeds a player and captures `setupPlayerId` BEFORE section 1, and the character create/update bodies reference it. Section 2's own player create is untouched, so domain-object coverage is unchanged; a matching teardown removes the seeded player. ### The negative test was passing for the wrong reason `Create character ERROR: missing required ocName (400, not 500)` asserted only `within(400, 499)`. While `ocName` was malformed the endpoint answered 400 to EVERY request, so that test would have passed even if the endpoint did nothing at all. It is now pinned to the missing-required branch by name — the body must mention `required` and `ocName`, and must NOT be the uuid-format rejection, which is also a 400. Measured, the two branches are distinguishable: {"description": "..."} -> 400 "The required properties (name, ocName) are missing." {"ocName": "not-a-uuid"} -> 400 "Property 'ocName' should match format 'uuid'" This is a strengthening, not a relaxation. ## 2. The DocuDesk probe ran before input validation (1 failure — app bug) `downloadPdf ERROR: invalid (non-UUID) template -> 400 not 500` got 424. `CharactersController::downloadPdf()` and `EventsController::downloadRunsheet()` both checked `isDocuDeskAvailable()` BEFORE `normaliseTemplateId()`. CI does not install DocuDesk alongside LarpingApp, so the availability probe answered 424 to every request and the documented 400 contract was unreachable — a crafted template value was never rejected on its own merits. Input validation is a property of the REQUEST and must not depend on which optional apps are installed. `normaliseTemplateId()` is a pure regex with no DocuDesk dependency, so the two guards are now in the correct order in both controllers. ### Failure proof No existing test covered the combination that matters — DocuDesk ABSENT and a non-UUID template. Every case fixed one guard while leaving the other in its passing state, so BOTH orders satisfied all of them, which is why the bug survived. The two tests added here cover exactly that combination. Measured: ARM A (original order): 2 failures — "Failed asserting that 424 is identical to 400" ARM B (this change): OK (27 tests, 62 assertions) full suite: OK (175 tests, 581 assertions) Three existing tests passed a non-UUID template while asserting 424; they now pass a well-formed UUID so the 424 branch is genuinely reached rather than being short-circuited by the 400. ## Not fixed here — needs a product decision `Create character AUTHZ: anonymous write returns 401` still fails. Measured: POST (no auth) .../objects/larpingapp/character -> HTTP 403 {"error":"User 'Anonymous' does not have permission to 'create' objects in schema 'Character'"} LarpingApp's own `/api/settings` correctly answers 401 to an anonymous caller. This 403 comes from OpenRegister's RBAC layer, not from LarpingApp, so the assertion is left honest and failing rather than repointed at 403. Whether an anonymous caller should get 401 (not authenticated) or 403 (operation forbidden) — and whether naming the schema to an anonymous caller is an acceptable disclosure — is an OpenRegister decision. No test was skipped, disabled, relaxed or deleted. * fix(auth): downloadPdf declared NoAdminRequired while its body required admin (gate-9) Touching downloadPdf() pulled it into gate-9's ADR-020 diff scope and exposed a pre-existing contradiction: the method carried `#[NoAdminRequired]` / `@NoAdminRequired` while its body requires an administrator — the `isAdmin()` guard added by #205 to close the character-PDF IDOR. Anyone reading the attribute would conclude any logged-in user may call it. lib/Controller/CharactersController.php:131 method=downloadPdf rule=no-admin-required-annotation-with-admin-body The attribute is removed, so Nextcloud's middleware rejects a non-admin before the controller runs. The in-body guard stays as defence in depth for direct invocation. Anonymous callers are unaffected: SecurityMiddleware raises NotLoggedIn (401) before the admin check, so the collection's `downloadPdf AUTHZ: anonymous -> 401` still holds. Why this was not caught before: my first local gate run reported gate-9 PASS. That local checkout of ConductionNL/.github predated `1558036 fix(gate-9): the admin rule matched no real isAdmin() call`, so the gate I ran had never matched anything — a dead gate reads exactly like a passing one. Re-running the version CI actually uses (hydra-gates @ main) reproduced the FAIL locally, which is also the failure proof: with #[NoAdminRequired]: [gate-9] semantic-auth: FAIL — 1 mismatch without #[NoAdminRequired]: [gate-9] semantic-auth: PASS gate-5 route-auth: PASS, gate-7 no-admin-idor: PASS unit suite: OK (175 tests, 581 assertions) Per-player self-access remains a follow-up needing a `player` ownership field on the character schema; when that lands the attribute returns alongside a real ownership check.
Automated PR to sync development changes to beta for beta release.
Merging this PR will trigger the beta release workflow.