Skip to content

Release: merge development into beta - #197

Open
github-actions[bot] wants to merge 731 commits into
betafrom
development
Open

Release: merge development into beta#197
github-actions[bot] wants to merge 731 commits into
betafrom
development

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated PR to sync development changes to beta for beta release.

Merging this PR will trigger the beta release workflow.

Reminder: Add a major, minor, or patch label to this PR to control the version bump. Default is patch.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report

Repository ConductionNL/softwarecatalog
Commit ebbb0dc
Branch 197/merge
Event pull_request
Generated 2026-03-19 16:38 UTC
Workflow Run https://github.com/ConductionNL/softwarecatalog/actions/runs/23305755039

Summary

Group Result
PHP Quality FAIL
Vue Quality FAIL
Security PASS
License PASS
PHPUnit SKIP
Newman SKIP

PHP Quality

Tool Result
lint PASS
phpcs FAIL
phpmd PASS
psalm PASS
phpstan PASS
phpmetrics PASS

Vue Quality

Tool Result
eslint PASS
stylelint FAIL

Security

Ecosystem Result
composer PASS
npm PASS

License Compliance

Ecosystem Result
composer PASS
npm PASS

composer dependencies (120 total)

Metric Count
Approved (allowlist) 120
Approved (override) 0
Denied 0

npm dependencies (582 total)

Metric Count
Approved (allowlist) 581
Approved (override) 1
Denied 0

PHPUnit Tests

PHPUnit 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.

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report

Repository ConductionNL/softwarecatalog
Commit baec00a
Branch 197/merge
Event pull_request
Generated 2026-03-19 16:46 UTC
Workflow Run https://github.com/ConductionNL/softwarecatalog/actions/runs/23306099824

Summary

Group Result
PHP Quality FAIL
Vue Quality FAIL
Security PASS
License PASS
PHPUnit SKIP
Newman SKIP

PHP Quality

Tool Result
lint PASS
phpcs FAIL
phpmd PASS
psalm PASS
phpstan PASS
phpmetrics PASS

Vue Quality

Tool Result
eslint PASS
stylelint FAIL

Security

Ecosystem Result
composer PASS
npm PASS

License Compliance

Ecosystem Result
composer PASS
npm PASS

composer dependencies (120 total)

Metric Count
Approved (allowlist) 120
Approved (override) 0
Denied 0

npm dependencies (582 total)

Metric Count
Approved (allowlist) 581
Approved (override) 1
Denied 0

PHPUnit Tests

PHPUnit 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.

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report

Repository ConductionNL/softwarecatalog
Commit d71ef19
Branch 197/merge
Event pull_request
Generated 2026-03-19 18:55 UTC
Workflow Run https://github.com/ConductionNL/softwarecatalog/actions/runs/23311644661

Summary

Group Result
PHP Quality FAIL
Vue Quality FAIL
Security PASS
License PASS
PHPUnit SKIP
Newman SKIP

PHP Quality

Tool Result
lint PASS
phpcs FAIL
phpmd PASS
psalm PASS
phpstan PASS
phpmetrics PASS

Vue Quality

Tool Result
eslint PASS
stylelint FAIL

Security

Ecosystem Result
composer PASS
npm PASS

License Compliance

Ecosystem Result
composer PASS
npm PASS

composer dependencies (120 total)

Metric Count
Approved (allowlist) 120
Approved (override) 0
Denied 0

npm dependencies (582 total)

Metric Count
Approved (allowlist) 581
Approved (override) 1
Denied 0

PHPUnit Tests

PHPUnit 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.

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report

Repository ConductionNL/softwarecatalog
Commit 99124f4
Branch 197/merge
Event pull_request
Generated 2026-03-19 18:58 UTC
Workflow Run https://github.com/ConductionNL/softwarecatalog/actions/runs/23311788331

Summary

Group Result
PHP Quality FAIL
Vue Quality FAIL
Security PASS
License PASS
PHPUnit SKIP
Newman SKIP

PHP Quality

Tool Result
lint PASS
phpcs FAIL
phpmd PASS
psalm PASS
phpstan PASS
phpmetrics PASS

Vue Quality

Tool Result
eslint PASS
stylelint FAIL

Security

Ecosystem Result
composer PASS
npm PASS

License Compliance

Ecosystem Result
composer PASS
npm PASS

composer dependencies (120 total)

Metric Count
Approved (allowlist) 120
Approved (override) 0
Denied 0

npm dependencies (582 total)

Metric Count
Approved (allowlist) 581
Approved (override) 1
Denied 0

PHPUnit Tests

PHPUnit 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.

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report

Repository ConductionNL/softwarecatalog
Commit bedba4b
Branch 197/merge
Event pull_request
Generated 2026-03-19 19:04 UTC
Workflow Run https://github.com/ConductionNL/softwarecatalog/actions/runs/23312029118

Summary

Group Result
PHP Quality FAIL
Vue Quality FAIL
Security PASS
License PASS
PHPUnit SKIP
Newman SKIP

PHP Quality

Tool Result
lint PASS
phpcs FAIL
phpmd PASS
psalm PASS
phpstan PASS
phpmetrics PASS

Vue Quality

Tool Result
eslint PASS
stylelint FAIL

Security

Ecosystem Result
composer PASS
npm PASS

License Compliance

Ecosystem Result
composer PASS
npm PASS

composer dependencies (120 total)

Metric Count
Approved (allowlist) 120
Approved (override) 0
Denied 0

npm dependencies (582 total)

Metric Count
Approved (allowlist) 581
Approved (override) 1
Denied 0

PHPUnit Tests

PHPUnit 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.

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report

Repository ConductionNL/softwarecatalog
Commit 985de13
Branch 197/merge
Event pull_request
Generated 2026-03-19 21:37 UTC
Workflow Run https://github.com/ConductionNL/softwarecatalog/actions/runs/23318049540

Summary

Group Result
PHP Quality FAIL
Vue Quality FAIL
Security PASS
License PASS
PHPUnit SKIP
Newman SKIP

PHP Quality

Tool Result
lint PASS
phpcs FAIL
phpmd PASS
psalm PASS
phpstan PASS
phpmetrics PASS

Vue Quality

Tool Result
eslint PASS
stylelint FAIL

Security

Ecosystem Result
composer PASS
npm PASS

License Compliance

Ecosystem Result
composer PASS
npm PASS

composer dependencies (120 total)

Metric Count
Approved (allowlist) 120
Approved (override) 0
Denied 0

npm dependencies (582 total)

Metric Count
Approved (allowlist) 581
Approved (override) 1
Denied 0

PHPUnit Tests

PHPUnit 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.

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report

Repository ConductionNL/softwarecatalog
Commit cac4c4b
Branch 197/merge
Event pull_request
Generated 2026-03-23 16:15 UTC
Workflow Run https://github.com/ConductionNL/softwarecatalog/actions/runs/23447563193

Summary

Group Result
PHP Quality FAIL
Vue Quality FAIL
Security PASS
License PASS
PHPUnit SKIP
Newman SKIP

PHP Quality

Tool Result
lint PASS
phpcs FAIL
phpmd PASS
psalm PASS
phpstan PASS
phpmetrics PASS

Vue Quality

Tool Result
eslint PASS
stylelint FAIL

Security

Ecosystem Result
composer PASS
npm PASS

License Compliance

Ecosystem Result
composer PASS
npm PASS

composer dependencies (120 total)

Metric Count
Approved (allowlist) 120
Approved (override) 0
Denied 0

npm dependencies (582 total)

Metric Count
Approved (allowlist) 581
Approved (override) 1
Denied 0

PHPUnit Tests

PHPUnit 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.

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report

Repository ConductionNL/softwarecatalog
Commit 2d92441
Branch 197/merge
Event pull_request
Generated 2026-04-09 09:48 UTC
Workflow Run https://github.com/ConductionNL/softwarecatalog/actions/runs/24183667745

Summary

Group Result
PHP Quality FAIL
Vue Quality FAIL
Security PASS
License PASS
PHPUnit SKIP
Newman SKIP
Playwright SKIP

PHP Quality

Tool Result
lint PASS
phpcs FAIL
phpmd PASS
psalm PASS
phpstan PASS
phpmetrics PASS

Vue Quality

Tool Result
eslint PASS
stylelint FAIL

Security

Ecosystem Result
composer PASS
npm PASS

License Compliance

Ecosystem Result
composer PASS
npm PASS

composer dependencies (120 total)

Metric Count
Approved (allowlist) 120
Approved (override) 0
Denied 0

npm dependencies (582 total)

Metric Count
Approved (allowlist) 581
Approved (override) 1
Denied 0

PHPUnit Tests

PHPUnit 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.

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report

Repository ConductionNL/softwarecatalog
Commit c176ba1
Branch 197/merge
Event pull_request
Generated 2026-04-09 10:00 UTC
Workflow Run https://github.com/ConductionNL/softwarecatalog/actions/runs/24184203950

Summary

Group Result
PHP Quality FAIL
Vue Quality FAIL
Security PASS
License PASS
PHPUnit SKIP
Newman SKIP
Playwright SKIP

PHP Quality

Tool Result
lint PASS
phpcs FAIL
phpmd PASS
psalm PASS
phpstan PASS
phpmetrics PASS

Vue Quality

Tool Result
eslint PASS
stylelint FAIL

Security

Ecosystem Result
composer PASS
npm PASS

License Compliance

Ecosystem Result
composer PASS
npm PASS

composer dependencies (120 total)

Metric Count
Approved (allowlist) 120
Approved (override) 0
Denied 0

npm dependencies (582 total)

Metric Count
Approved (allowlist) 581
Approved (override) 1
Denied 0

PHPUnit Tests

PHPUnit 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.

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 6c8d08c

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm ✅ 582/582
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-05-03 15:17 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ bc9432a

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-05-03 17:47 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 62280a4

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-05-03 18:04 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 954821d

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-05-05 09:14 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 533d8dc

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-05-07 20:53 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ ec04faa

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-05-07 21:24 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ b849b29

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-05-10 07:09 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 7176811

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-05-10 08:33 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 75b1d84

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-05-10 19:20 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 612957d

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-05-10 19:31 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ d94e250

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm ✅ 622/622
PHPUnit
Newman
Playwright ⏭️

Coverage: 0% (0/282 statements)


Quality workflow — 2026-05-10 21:09 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 1a1e9c7

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm ✅ 622/622
PHPUnit
Newman
Playwright ⏭️

Coverage: 0% (0/282 statements)


Quality workflow — 2026-05-11 20:20 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 919e14b

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm ✅ 623/623
PHPUnit
Newman
Playwright ⏭️

Coverage: 0% (0/282 statements)


Quality workflow — 2026-05-12 21:00 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 05b95eb

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm ✅ 623/623
PHPUnit
Newman
Playwright ⏭️

Coverage: 0% (0/282 statements)


Quality workflow — 2026-05-12 21:43 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 33b6854

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm ✅ 623/623
PHPUnit
Newman
Playwright ⏭️

Coverage: 0% (0/282 statements)


Quality workflow — 2026-05-12 22:08 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 4f950b5

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm ✅ 623/623
PHPUnit
Newman
Playwright ⏭️

Coverage: 0% (0/282 statements)


Quality workflow — 2026-05-12 22:31 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 4f950b5

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm ✅ 623/623
PHPUnit
Newman
Playwright ⏭️

Coverage: 0% (0/282 statements)


Quality workflow — 2026-05-13 04:37 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 364fcb3

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm ✅ 623/623
PHPUnit
Newman ⏭️
Playwright ⏭️

Coverage: 0% (0/282 statements)


Quality workflow — 2026-05-13 04:45 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 364fcb3

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm ✅ 623/623
PHPUnit
Newman ⏭️
Playwright ⏭️

Coverage: 0% (0/282 statements)


Quality workflow — 2026-05-13 04:52 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 364fcb3

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm ✅ 623/623
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-05-13 04:56 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 3b21d4c

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 120/120
npm ✅ 623/623
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-05-13 05:14 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 76c3f61

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
composer ✅ 128/128
npm ✅ 718/718
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-09 20:53 UTC

Download the full PDF report from the workflow artifacts.

… `=== null` guard (#472)

gate-50 security-config-fail-mode 14 -> PASS, measured with hydra-gates 651e5c5
at the CI scope (--scope-to-diff --base origin/beta).

I nearly dismissed these 14 as false positives of the gate's 10-line window,
on the grounds that the consumers validate. Checking every consumer rather
than the two convenient ones showed the opposite.

The legacy fallback in both getAmefConfig() implementations read every
register/schema id with '' as its default and returned them. The consumers
guard with `=== null`:

    ViewService:254, :320   if ($registerId === null || $viewSchemaId === null) throw
    ViewService:711         $registerId used with NO guard at all

and `'' === null` is false. An empty id therefore passes the guard and is
pinned into an OpenRegister query as the register/schema — and an unpinned
query returns rows, which reads exactly like a correct result.

Nothing reaches a query TODAY only because the fallback writes PLURAL key
names (`views_schema`, `elements_schema`) while every consumer reads SINGULAR
ones (`view_schema`, `element_schema`), so the lookups miss and fall back to
null. Measured producer/consumer key overlap: the empty set. That is an
accident of naming, not a defence — adding the singular keys, the obvious
"cleanup", turns it into a live fail-open.

- resolveConfiguredId() reads each id and returns null, with a warning naming
  the key, when it is empty or whitespace. The guard is now AT the read, and
  there is one read instead of eight.
- The fallback array_filters the nulls out, so `?? null` downstream yields
  null — which is what every consumer already checks for.
- ViewService::getModulesData() gains the missing register guard and fails
  closed rather than issuing an unpinned query; its schema loop now uses
  empty() rather than `=== null` for the same reason.

Narrower than it first looks, and the tests say so: the fallback is only
reached when `amef_config` is MALFORMED, because its default '{}' is valid
JSON and decodes to []. My first draft of the tests failed for exactly that
reason and taught me the branch condition.

Can-fail: reverting the three services turns 3 of the 4 new tests red and puts
gate-50 back to 14.

phpcs lib/ 0 errors, phpmd/psalm/phpstan clean, unit suite 523 tests green.
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 25a1f0e

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
composer ✅ 128/128
npm ✅ 718/718
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-09 21:13 UTC

Download the full PDF report from the workflow artifacts.

…c seven tags already pointed at (#471)

gate-16 spec-coverage 64 -> PASS, measured with hydra-gates 651e5c5 at the CI
scope (--scope-to-diff --base origin/beta). gate-46 spec-anchor-existence stays
PASS, so every anchor added here resolves to a heading that exists.

64 changed frontend methods across 24 files carried no @SPEC. Each now names the
requirement it serves — facet views and the facet store to gemma-faceted-search,
the SBOM panel to sbom-import, the suite wizard to suite-wizard, the portfolio
helpers to portfolio-rationalization-time, the view store to view-enrichment-api,
the review modals to catalog-ratings, and so on. No tag was added without
reading the method and the requirement it points at.

openspec/specs/realtime-updates-ui/ is NEW here, and that is the real find.
Change `adopt-live-updates-ui` declares "Affected specs: realtime-updates-ui
(new)" and shipped src/composables/useLiveCollections.js plus its seven
consumers — but the delta was never synced into openspec/specs/. SEVEN @SPEC
tags across six source files have been pointing at a spec that does not exist.
gate-46 never caught it because gate-46 validates ANCHORS (#fragment), not bare
file targets; an audit of every `@spec openspec/specs/<x>/spec.md` in src/ found
this one and only this one dangling. The delta is promoted verbatim — its
requirement, its three scenarios and its author's reason-bearing `@e2e exclude`
are unchanged; the only edits are the title line and the delta's
"## ADDED Requirements" heading becoming "## Requirements", plus a Purpose
recording where it came from. I did not author that exclusion.

I nearly made this worse: the first draft of this change copied the dangling
`@spec openspec/specs/realtime-updates-ui/spec.md` onto useLiveCollections()
itself. Checking the target existed before trusting it is what turned a
propagated broken reference into a fixed one.

Can-fail: reverting the 22 annotated files takes gate-16 from PASS back to 51;
the run before any of this work reported 64 on the same package.

vitest 215/215. gate-46 PASS. No other gate count moved.
…relation (#473)

gate-54 relation-dialect 1 -> PASS, measured with hydra-gates 365fa31 at the CI
scope (--scope-to-diff --base origin/beta).

`contract.decisions` references decidesk Decision objects (ADR-066). It carried
BOTH `x-external-register: "decidesk"` AND `$ref: "Decision"`. OpenRegister
resolves `$ref` inside ONE register set and can never reach another app's
schema, so that $ref is dead weight — it names a target nothing will ever look
up.

Earlier today I measured this same finding against package 651e5c5, concluded
it was an unclosable gate gap, and left it red with that reasoning — .github
#305 says exactly that, and I added my instance to it. That conclusion is now
WRONG: .github#286 landed hours later and gave the cross-app case a dialect.
The gate is right and the register was wrong.

The sanctioned form is `x-external-register: <app>` on the property carrying
the bare identifier (`type: string` + `format: uuid`), with no `$ref`. The
annotation moves to the PROPERTY as well as the item, because
`_is_external_ref()` reads it on the property; on `items` alone it is invisible
to the gate, which is why the old generic "does not resolve" message fired
instead of the new cross-app one.

Can-fail: restoring the $ref puts gate-54 back to 1.
gate-51 stays PASS; the register still parses.
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 73b6336

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
composer ✅ 128/128
npm ✅ 718/718
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-09 21:28 UTC

Download the full PDF report from the workflow artifacts.

Hard pin to the vue3 dist-tag head (2.2.0-vue3.7), up from 2.2.0-vue3.3.

Verified:
- lockfile control (pin unchanged): 0-line diff, so the 8-line lock change
  is attributable to this bump alone; no other package re-resolved
- installed off disk after npm ci: one copy, 2.2.0-vue3.7, peer vue ^3.5.0
- build: exit 0, 3 warnings before and after
- vitest: 20 files / 220 passed before and after
- jest: 9 suites / 120 passed before and after
- bundle: 103,593,858 -> 103,623,216 bytes (+29,358, +0.03%)
chore(deps): pin @conduction/nextcloud-vue to 2.2.0-vue3.7
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ aeac74b

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
composer ✅ 128/128
npm ✅ 718/718
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-10 09:30 UTC

Download the full PDF report from the workflow artifacts.

Follow-up to #474, which pinned 2.2.0-vue3.7. The vue3 dist-tag moved
twice more while the fleet wave was running (vue3.7 -> vue3.8 -> vue3.9).
The fleet converges on 2.2.0-vue3.9.

Verified on npm 10.8.2, the version CI runs:
- control (pin unchanged): 0-line diff
- npm ci: exit 0
- installed off disk: one copy, 2.2.0-vue3.9, peer vue ^3.5.0
- build: exit 0, 3 warnings at 2.2.0-vue3.7 and at 2.2.0-vue3.9
- vitest: 20 files / 220 passed at both versions
- jest: 9 suites / 120 passed at both versions
- bundle: 103,623,179 -> 103,637,764 bytes (+14,585, +0.01%)

2.2.0-vue3.9 is not pre-verified against our apps the way 2.2.0-vue3.7
was, so the run above is the verification. No regression.
chore(deps): pin @conduction/nextcloud-vue to 2.2.0-vue3.9
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ fb72c78

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
composer ✅ 128/128
npm ✅ 718/718
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-10 11:25 UTC

Download the full PDF report from the workflow artifacts.

`workflow_dispatch` was absent, and this repo and one other were the only two
of the sixteen fleet apps where that was true — checked by reading
.github/workflows/code-quality.yml on `development` in all sixteen.

The consequence was not inconvenience. Every fleet-wide gate sweep in the
current quality 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 entirely, which in a table of fourteen verdicts is indistinguishable from
a repo that was never a problem.

A dispatch is also strictly more informative than a re-run of CI here. The
shared quality workflow scopes workflow_dispatch to the FULL repository, because
there is no pull-request target branch and no previous pushed tip to diff
against, so ADR-020 diff-scoping has nothing to scope to. A push run on
`development` typically covers one commit's files; this is the only way to ask
what the state of the whole app is without opening a pull request. Expect the
first dispatch to be redder than a PR — that is the honest answer, not a
regression.

MEASURED, not assumed: dispatch does NOT require the trigger on the default
branch. nldesign's default branch is `main`, its `main` carries no
workflow_dispatch, and its dispatch run 31393755672 on `development` fired
regardless. Landing this on `development` is therefore sufficient.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ a93fcbb

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
composer ✅ 128/128
npm ✅ 718/718
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-10 18:14 UTC

Download the full PDF report from the workflow artifacts.

…nt, and one named a change dir that never existed (#477)

gate-46 (spec-anchor-existence) reported 5 unresolved findings from 4 distinct
targets on the full-scope dispatch. They are two different mistakes, not one.

FOUR of them, all in SbomRegisterShapeTest, are the same typo shape:

  #requirement-a-successful-import-records-provenance-on-the-version
  #requirement-existing-versions-are-unaffected-by-the-schema-addition
  #requirement-a-parsed-component-persists-with-its-moduleversie-relation

Each of those strings is a real heading in openspec/specs/sbom-import/spec.md
-- but it is a `#### Scenario:` heading, kebab-cased and then prefixed with
`requirement-`. The requirement of that name does not exist and never did, so
the tag resolved to nothing. The scenario is the more precise target anyway:
each of these four test methods verifies exactly one scenario, so the tags now
say `#scenario-...` and point at the heading they were always describing.

The FIFTH is different in kind. RegisterFragmentMergeTest pointed at

  openspec/changes/modular-register-manifest-fragments/specs/modular-config/spec.md

which is not merely archived -- it is absent from openspec/changes/, from
openspec/changes/archive/, and from openspec/specs/ under any capability name,
and `modular-config` is not a capability this repo has ever had. The gate
resolves change-dir targets through the archive index and the capability index
before reporting, so this is a target with no home rather than a stale path.
Repointing a tag at a nearby requirement would have made the gate green while
leaving the behaviour the test asserts unspecified, so instead the behaviour is
now written down where it belongs: REQ-007 in openspec/specs/settings-service,
the spec that owns SettingsService, covering the ADR-037 fragment deep-merge
contract (disjoint fragments union; lists concatenate; scalars overwrite) with
one scenario per test method. It explicitly defers to catalog-ratings for the
`authorization` replace-on-merge carve-out rather than restating or overriding
it.

Both directions, measured with the gate's own checker:

  before  1 target-file-not-found + 4 anchor-not-found
  after   exit 0, empty findings log
  gate    [gate-46] spec-anchor-existence: FAIL -- 5 ... -> PASS

Adding two scenarios did NOT add gate-19 debt: settings-service carries a
file-level `@e2e exclude` (PHP backend, no UI surface), and the gate-19 finding
list is byte-identical before and after (291 both times, diff empty). No other
gate moved: 13/19/25/26 unchanged.

phpcs scans lib/ only, so the tests/ docblocks are out of its scope; the spec
change is markdown. PHPUnit on the two affected classes: 6/6 pass, 37 assertions.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 283b006

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
composer ✅ 128/128
npm ✅ 718/718
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-10 20:48 UTC

Download the full PDF report from the workflow artifacts.

…that made four settings info panels render empty (#479)

* fix(fe): extract four inline modals, and fix the mis-named slot that made four settings info panels render empty

gate-13 (modal-isolation) reported three files with inline NcModal/NcDialog
markup. Fixing them surfaced a second, unrelated defect that no gate and no
test could see, because its failure mode is silence.

THE SLOT BUG

AlwaysVisibleSection declared `<slot name="info" />`. Four callers pass
`<template #info-content>`:

  UserGroupsConfiguration, EmailConfiguration,
  ArchiMateImportExport, OrganizationSynchronization

All four also set `:has-info-content="true"`, so the (i) button rendered and
opened a modal with nothing in it. Vue drops slot content addressed to a slot
the child does not declare — no warning, no error, no failing test. The name
came from CollapsibleSection, which does use `info-content`; only
VersionInformation used the working `info` name.

Both sections now render `<slot name="info-content"><slot name="info" /></slot>`
so `info-content` wins and `info` remains a fallback. All five callers render.

THE EXTRACTION

  src/dialogs/ChangePasswordDialog.vue      <- ContactpersonenList
  src/dialogs/ManageUserGroupsDialog.vue    <- ContactpersonenList
  src/modals/AlwaysVisibleSectionInfoModal.vue
  src/modals/CollapsibleSectionInfoModal.vue

ContactpersonenList drops 1563 -> 954 lines. Password validation, the HIBP
pwned-check, the debounce watcher and the group selection all move into the
dialog that owns them; the parent now only opens them and reacts to events.
`updateContactpersoonGroups` stays in the parent because it mutates the
parent's own organisationData — the dialog reports groups up rather than
reaching into it. Since both dialogs mount fresh per open, `data()` IS the
state reset the parent used to spell out by hand and `beforeUnmount` IS the
timeout cleanup. Every t('softwarecatalog', ...) string is preserved verbatim.

Two info-modal files rather than one shared component: the two sections render
materially different DOM (NcModal's own title chrome and a bare body, versus a
hand-painted h2 + Close footer + ~90 lines of :deep() typography). Sharing them
would need a variant flag switching between two disjoint templates and two
disjoint stylesheets, and converging them would have changed one section's
rendered output.

BOTH DIRECTIONS

A new vitest spec mounts each section with #info-content supplied. Against the
pre-fix wiring:

  FAIL tests/vitest/sectionInfoSlot.spec.js > renders #info-content inside the info modal
  AssertionError: expected false to be true
  FAIL > prefers #info-content over #info when both are supplied
  Tests  2 failed | 5 passed (7)

The #info case still PASSED there, which is what shows the test isolates the
bug rather than the harness. After the fix: 227 passed (21 files).

  [gate-13] modal-isolation: FAIL - 3 file(s) -> PASS

No other gate moved: 19=291, 25=41, 26=3. (An earlier baseline appeared to
flip six gates; that baseline was captured while `npm ci` was still running.
Re-measured with node_modules present in both arms, gate-13 is the only
verdict that changes.)

TOOLCHAIN

Vitest could not mount an SFC: no Vue plugin, and environment 'node'. Added
@vitejs/plugin-vue + jsdom as devDependencies, a @nextcloud/vue stub alongside
the existing router/dialogs/l10n stubs, and made vitest.config.js an async
factory so the ESM-only plugin can be dynamic-imported from a CommonJS config.
The default environment stays 'node'; the new spec opts into jsdom per-file, so
no existing spec changes behaviour.

lint 0 errors; build compiles; 227/227 unit tests pass.

* fix(settings): escape the literal placeholder braces the slot fix exposed

The mis-named `info-content` slot had been hiding a second bug. Because
AlwaysVisibleSection only declared `<slot name="info" />`, Vue silently
dropped the four callers that passed `#info-content` — so their panels
were never rendered, and nothing could fail on them.

EmailConfiguration's panel documents the e-mail template placeholders:

    Use placeholders like {{ organization.name }} and {{ user.email }}

Those braces are meant literally, but Vue compiles them as interpolation
against the component, which has no `organization` and no `user`. The
moment the slot name was fixed and the panel rendered for the first time
it threw `Cannot read properties of undefined (reading 'name')`, which
tripped the shared "no console errors" assertion in every Playwright
settings test.

`v-pre` keeps the braces as documentation. The other four info panels
were checked and render clean.

Also adds tests/vitest/settingsInfoPanels.spec.js, which renders the REAL
markup of every info panel under src/views/settings/sections/. The
existing sectionInfoSlot.spec.js proves the slot MECHANISM forwards
content, but it does so with synthetic probe markup — which is precisely
why it could not see this. Verified both ways: without `v-pre` the new
spec reproduces the exact TypeError from CI.

---------

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 3105397

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
composer ✅ 128/128
npm ✅ 718/718
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-10 21:48 UTC

Download the full PDF report from the workflow artifacts.

…> 1 (#480)

* test(gates): real contract tests for all 41 gate-25 endpoints; gate-26 3 -> 1

gate-25 (contract-coverage) 41 -> 0 and gate-26 (visual-coverage) 3 -> 1,
measured with the gate helpers at ConductionNL/.github@b8c7ead — the SHA the
shared quality workflow floats on, which is what this repo's CI actually runs
(this repo sets no `hydra-gates-ref`, so it defaults to @main).

Nine new PHPUnit contract-test classes, 134 tests, 459 assertions. Every one
calls the controller method under test and asserts its wire contract; none of
them is an annotation over untested code, and no `@contract exclude` was added.

What the tests actually pin, beyond "a 200 comes back":

  * deny-before-grant on every registered public endpoint — the backing
    service is asserted NEVER invoked when the caller is anonymous, so an
    implementation that queried first and filtered afterwards fails.
  * `GET /api/gebruik` (@publicpage): a `gebruik-beheerder` is narrowed to
    their own organisation BEFORE the `_rbac:false` bypass query is issued,
    and asking for another organisation's `afnemer` is denied outright rather
    than silently widened (vendor-visibility-rbac REQ-001/REQ-003).
  * `/api/aangeboden-gebruik/ambtenaar{,/{id}}` (@publicpage + RBAC bypass):
    the admin/ambtenaar group check is the only thing between an anonymous
    caller and every organisation's records — both the empty envelope AND the
    un-issued query are asserted.
  * `GET /api/email/config`: the non-admin 403. This endpoint once returned
    the SMTP password and provider API keys to any authenticated user; a test
    asserting only "200 for a logged-in user" would have passed on the broken
    version.
  * `/api/progress/{id}` and its SSE twin: another user's operation reads as
    404 with no `progress` key — the ownership guard, on both variants.
  * `/api/archimate/download/{fileName}`: five traversal shapes refused 400
    with the DI container asserted never consulted, so the guard is proven to
    run before any filesystem resolution.
  * `/api/contactpersonen/change-password`: the full ladder — non-admin on
    another account 403, self-service without the current password 400, wrong
    current password 403, <10 chars 400, policy-rejected `setPassword()` false
    surfaced as a failure rather than swallowed.
  * `/api/preferences/{key}`: the key that reaches IConfig is proven
    sanitised and `pref_`-namespaced, so `../apps/Password` cannot read
    another app's user values.

Proven in both directions: removing the anonymous guard from
`ViewController::getView()` turns the corresponding test red (500 != 401);
restored, green. Skip count is unchanged at 25 — the suite goes 528 -> 662
tests with no test passing by being skipped.

gate-26: `LifecycleRoadmapView.vue` now has behavioural e2e that asserts the
component's OWN surface (root class, h2, intro, refresh control, org selector,
and that `.rm-groups` is ABSENT before an organisation is picked). The previous
assertion was an OR over two strings that a breadcrumb or the nav entry alone
satisfies — it could pass on a page that is not this component.

`src/views/organisaties/OrganisatieIndex.vue` is deleted as dead code, not
waived: the manifest's `Organisaties` page is now `type: index` with
`config.cardComponent: OrganisatieCard` (Phase 8), the file's own docblock
names the CnIndexPage `cardComponent` gap as its reason to exist, and that gap
is closed. Nothing in src/ imports it and no router or manifest entry names it.

Remaining, deliberately NOT waived: `src/views/gemmaviews/GemmaViewIndex.vue`
is likewise unreachable, but this repo's own
openspec/changes/beta-surface-alignment/proposal.md defers its disposition to a
maintainer ("may be dead code or a future menu item"). Writing a `@visual
exclude` whose reason is "nothing routes to it" would be a claim about the
state of the world that rots the moment someone wires it up, so gate-26 stays
at 1 pending that decision rather than being closed with a waiver.

* fix(e2e): query the roadmap refresh control by its accessible name

CI run 31475813082 failed on this assertion (75 passed, 1 failed) and it was my
bug, not the product's. The NcButton carries aria-label="Refresh data"; an
aria-label overrides text content when computing the accessible name, so
`getByRole('button', { name: 'Refresh', exact: true })` could never match the
visible label "Refresh".

Querying by the accessible name is also the better assertion — it is what a
screen-reader user actually hears, so a future change that drops the aria-label
now fails here.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 51a82a2

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
composer ✅ 128/128
npm ✅ 718/718
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-11 09:50 UTC

Download the full PDF report from the workflow artifacts.

gate-19 e2e-coverage: 137 -> 112 with 18 real Playwright tests, 0 exclusions.

Measured with the canonical gate package at the development -> beta scope (the
one the open release PR uses), reading its printed summary line rather than the
exit status. Negative control: 112 -> 116 (+4, exactly the removed file's
anchors) -> 112.

Every test proven able to fail by a planted true positive. Two traps hit while
proving that, both of which first read as "my tests are blind":
opcache.revalidate_freq=60 makes a PHP plant invisible for up to a minute, and
the GEMMA dimension list exists in three independent copies.

Found and filed, not worked around: every write through adminApi.js failed CSRF
(seven UI actions dead — fixed here); the suite wizard's success result is never
rendered; two gemma-faceted-search requirements are unimplemented; sixteen
file-level @e2e tags claim coverage a file says it does not provide; 23
whole-spec exclude markers retire 30% of all scenarios.

Also deletes src/views/gemmaviews/GemmaViewIndex.vue, proven unreachable: the
bundle built with the file present is byte-identical to the bundle built
without it.

Refs #481 #482 #483 #484 #485
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 445ba52

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
composer ✅ 128/128
npm ✅ 718/718
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-11 18:22 UTC

Download the full PDF report from the workflow artifacts.

…5954 (#489)

phpcsstandards/phpcsutils < 1.2.3 carries CVE-2026-65954 (arbitrary code
execution, GHSA-r6hr-vr92-vv28, affected >=1.0.0-alpha1,<1.2.3). The advisory
was published today, so composer audit turns red on a lock file that has not
changed.

Negative control before the bump, on this tree:

  composer audit --locked
  -> Found 1 security vulnerability advisory affecting 1 package
     phpcsstandards/phpcsutils / CVE-2026-65954 / exit 1

After 'composer update phpcsstandards/phpcsutils --no-install --no-scripts'
(1.2.2 => 1.2.3, a lock-only change, 0 installs 0 removals):

  composer audit --locked
  -> No security vulnerability advisories found / exit 0

The bump is exercised rather than merely locked: phpcs runs green against the
new library on PHP 8.4 --
  0 ERRORS AND 87 WARNINGS IN 50 FILES, exit 0
so no sniff regressed on the upgrade.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ 7b810cc

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
composer ✅ 128/128
npm ✅ 718/718
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-11 21:32 UTC

Download the full PDF report from the workflow artifacts.

…ssor with property_exists (#491)

* fix(merge): organisation merge re-points nothing — probe a magic accessor with property_exists

MergeOrganisatieService::repointBySelfOrganisation() decided whether an object
was owned by the source organisation with method_exists($entity,
'getOrganisation'). OpenRegister's ObjectEntity declares that accessor only as
an @method docblock tag over protected ?string $organisation, so it is served
by OCP\AppFramework\Db\Entity::__call() and the probe is always false. The next
line skipped every object, so contract and compliancy were never re-pointed
while tombstoneSource() still retired the source organisation — leaving live
objects owned by an organisation that no longer exists. Dry-run and execute
agreed only because both arms were equally broken.

The instrument is property_exists(), which is what Entity::getter() itself
decides on. is_callable() is not a membership test on a __call class — it is
true for every name, so a probe swap would make the branch unconditionally
true and move the failure into a runtime BadFunctionCallException. The
accessor call is wrapped and the result type-checked in the same edit.

The same probe in ReviewService::entityUuid() and IntakeService::entityUuid()
made both return null for every real save, because saveObject() returns an
object and the is_array() fallback cannot rescue it — so submit() answered
uuid: null to the client and wrote uuid: null to the audit log.

Why the suite was green: tests/Stubs/Db/ObjectEntity declared getOrganisation()
concretely, which inverted the exact predicate under test. The merge suite now
builds a faithful double — a concrete subclass of the stub, which extends the
real Entity, with organisation as a property reached through __call — and one
test asserts that premise so the fixture cannot drift back. The stub no longer
declares getOrganisation()/setOrganisation() and carries a warning about what
adding an accessor there costs.

Reverting only the merge probe turns 6 tests red; reverting only the two
entityUuid probes turns 2 red. Both predictions were written before the revert
and matched exactly. 667 unit tests pass; phpcs, phpmd, psalm and phpstan clean.

Also corrects a stale class docblock: it credited the @self.organisation write
path to SaveObject::applyCallerSuppliedFields(), a method that exists nowhere
in OpenRegister. The real acceptance path is SaveObject::setSelfMetadata().

Closes #490

* fix(tests): keep the ObjectEntity stub free-standing so it loads under both bootstraps

The previous commit made the stub extend OCP\AppFramework\Db\Entity. That is
fine under tests/bootstrap-unit.php, which registers an OCP autoloader, but
tests/bootstrap.php require_once's every file in tests/Stubs/ BEFORE
Nextcloud's lib/base.php — deliberately, so the stub wins over the real
OpenRegister class during mock generation. At that point no OCP class is
resolvable, so the whole suite died in the bootstrap with

  Error in bootstrap script: Class "OCP\AppFramework\Db\Entity" not found

on both PHPUnit cells. The local unit run could not see it because
phpunit-unit.xml uses the other bootstrap.

The stub now mirrors Entity's __call/getter/setter triple instead of
inheriting it, so it has no load-time dependency at all. The semantics that
the fix turns on are reproduced exactly: get*/set* resolve through
property_exists(), anything else raises BadFunctionCallException.

Verified by replaying the exact failing bootstrap step — vendor/autoload.php
plus the tests/Stubs glob, with no Nextcloud and no OCP autoloader. The
committed version fatals there; this version loads clean. The revert
prediction is unchanged: reverting the merge probe still turns exactly the
same 6 tests red.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ ccba8c5

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
composer ✅ 128/128
npm ✅ 718/718
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-12 00:25 UTC

Download the full PDF report from the workflow artifacts.

Task 5.2 of openspec/changes/english-vocabulary: the spec says DELETE these rather
than rename them, because a committed debug script is not vocabulary worth
migrating.

Read both before removing. They are pure echo scripts from a 2026-05-29 debugging
session — notes-to-self about why a contactpersoon's username looked empty. No
queries, no credentials, no logic; nothing imports or executes them, and neither
appears in phpcs.xml, phpmd.baseline.xml, psalm.xml, phpstan.neon, composer.json
or any workflow.

Two things worth noting on the way out. They hardcode a real-looking contactpersoon
UUID, which is the sort of thing that should not sit in a public repo even when it
is not a secret. And their whole premise was querying oc_openregister_objects,
which is the EMPTY shared table — objects live in the per-schema
oc_openregister_table_<reg>_<schema> shards, so the debugging session was reading
the wrong place. That is the same lookup error that made a stored-object count
report a false zero earlier in this programme.

softwarecatalog's vocabulary rename itself is NOT in this commit: it is 6757
references across 194 files over roughly 9,500 imported VNG production records,
and the spec requires the migration to be authored and tested against copied data
before any rename merges.
* feat(repair): migrate softwarecatalog's Dutch columns to English

Adds RenameDutchCatalogColumns, the data-migration half of this app's English
vocabulary slice, and the canonical spec it anchors to. No property is renamed
in this commit — the migration lands first, because the app's spec requires it
to exist before any rename merges.

WHY A MIGRATION IS NEEDED AT ALL. OpenRegister does not store an object as a
JSON blob keyed by property name. Each schema property is a real, snake_cased
COLUMN in oc_openregister_table_{register}_{schema}. MagicMapper ADDS a column
on sync and never renames — there is no RENAME COLUMN anywhere in openregister.
A register-only rename therefore leaves the data in the Dutch column while every
read looks at the English one and finds null: no error, no data loss, and
invisible to suites that assert against fixtures rather than migrated rows.
Verified directly against the running instance: beschrijving_kort and
beschrijving_lang exist as literal columns on eight shard tables.

WHY THIS ONE IS SCOPED BY SCHEMA, NOT BY REGISTER. The sibling steps in
opencatalogi and decidesk scope by register, because everything under those
registers is ours. That is NOT true here. Five schemas hold externally
standardised field names:

  - element, relation, view — the GEMMA/GGM architecture model imported from
    VNG. Measured: of fourteen materialised shard tables, the only two carrying
    `toelichting` and `bron` are ids 44 (element) and 49 (relation), exactly the
    GEMMA pair; and `view` alone holds gemma_status, gemma_thema, gemma_type,
    gemma_url, detailniveau, publiceren and titel_view_swc.
  - model, property-definition — the ArchiMate Open Exchange File Format
    containers; `model` carries xmlns, xsi, schema_location and identifier
    straight off the exchange root element.

A register-scoped step would have rewritten the import contract as a side
effect, and the symptom would have been a GEMMA re-import silently writing
nulls. model and property-definition hold no column this map targets today, so
listing them changes nothing now; they are exempt so that a property added later
is exempt by default rather than migrated by omission.

Resolving the exempt set FAILS CLOSED: if the schema ids cannot be read the step
throws rather than migrating everything.

AMBIGUOUS RENAMES ARE REFUSED, NOT MERGED. beschrijving, beschrijving_lang and
omschrijving all mean `description`. They do not co-occur in any schema today —
confirmed by the dry run below — but a later fragment could introduce a pair, and
a silent merge would destroy one of two values. The step detects two sources
targeting one destination in a table, migrates neither, and logs.

VERIFIED
  - php -l clean; info.xml parses; phpcs clean under the app's standard,
    including its named-parameter sniff and the @SPEC anchor requirement.
  - Exclusion positive control, run against the live register: element, view and
    relation resolve as EXCLUDED and the other nineteen shard tables as in scope.
    The control caught `view` (schema 45), a table absent from the column survey
    that suggested the exempt list in the first place.
  - Dry run of the step's exact resolution: 40 renames across 11 shard tables,
    zero GEMMA/ArchiMate tables touched, zero ambiguity — which is what confirms
    the no-co-occurrence claim rather than assuming it.

NOT VERIFIED, AND WHY. The app's spec asks for validation against copied
production data, citing ~9,500 imported VNG records. This dev instance holds 50
rows across the whole register, 3 of them live, and exactly ONE non-null value
in any mapped column. The code paths are exercised; the production VOLUME and
VARIETY are not. Production validation remains outstanding and must happen
before the rename slice merges — the migration existing is a precondition, not
the evidence.

* fix(repair): use information_schema, not IDBConnection introspection

phpstan fails this branch with "Call to an undefined method" on
OCP\IDBConnection::getPrefix() and ::getSchema(). Both are real.

Read from the running server's own lib/public/IDBConnection.php, the interface
exposes getQueryBuilder, getTypedQueryBuilder, getError, getDatabasePlatform,
getDatabaseProvider, getShardDefinition and getCrossShardMoveHelper — and
nothing else beginning with "get". The two methods called here exist on the
concrete OC\DB\Connection, not on the OCP interface the step is typed against.
This repair step could not have run at all.

WHY EVERY OTHER CHECK PASSED. `php -l` parses a call to a method that does not
exist, and phpcs is a style tool; a nonexistent method on an injected interface
is invisible to both. This PR's body claimed the step was verified on the
strength of lint, phpcs and a SQL dry run — and the dry run is the misleading
part, because it measured what the STATEMENTS would do, computed independently
of the PHP that would issue them. It read as strong evidence while covering none
of the API surface.

THE FIX follows openregister's own RegisterService::magicTableNames(), which
solves the same problem: query information_schema and anchor the match on the
`openregister_table_` MARKER rather than a computed prefix. That file documents
why the obvious alternative fails — getQueryBuilder()->getTableName('') returns
the literal `*PREFIX*` placeholder, resolved only when a query executes through
the NC DB layer, which a raw information_schema string never is; a LIKE built
from it matches zero tables and silently reports every register empty.

Column introspection moves to information_schema.columns for the same reason.

VERIFIED
  - php -l clean; no db->getSchema() or db->getPrefix() call remains.
  - phpstan, whole project, same command as CI: [OK] No errors.

Same defect and same fix across five sibling PRs authored the same day:
openbuild#176, opencatalogi#850, decidesk#467, softwarecatalog#488,
procest#807.

* style(repair): satisfy phpcs and phpmd on the migration step

CI flagged the information_schema rewrite:
  - CyclomaticComplexity / ShortVariable on the marker-matching loop;
  - named-parameter and 150-character violations on the two SQL strings;
  - missing @SPEC anchors; one lowercase inline comment.

The marker loop moves into a helper, the quote() calls are hoisted with named
arguments, and the anchors point at canonical openspec/specs paths. Behaviour
is unchanged.

Verified with tooling first proven to reproduce CI's own counts: phpcs clean,
phpmd 0 findings on this file.

* test(repair): cover the catalog migration's scoping and exemption

The PHPUnit job was failing on the COVERAGE RATCHET, not on a test:

  Coverage current:    17.95%  (5646/31461 statements)
  Coverage merge base: 18.02%  (5646/31340 statements)
  FAIL: coverage dropped by 0.07% against the merge base.

All 662 tests passed in that run. The migration had shipped with no test.

The shard-matching loop is extracted into isMigratableShard() so it can be
tested at all, and eight tests now pin what the step touches. The important one
is the EXEMPTION: schemas 44 (element), 45 (view) and 49 (relation) hold the
GEMMA/GGM model imported from VNG, and 46 (model) / 48 (property-definition) the
ArchiMate Open Exchange containers. Their property names ARE that import's wire
format; migrating them rewrites the import contract and the symptom is a GEMMA
re-import silently writing nulls. That exemption was previously guaranteed only
by a constant nobody asserted.

Also pinned: ambiguous renames are refused rather than merged (three Dutch names
mean `description`), derived tables like `…_13_50_backup` and non-shards like
`…_13_audit` are left alone, and every destination is snake_case because
MagicMapper DROPS a camelCase column whose snake_case twin exists.

The digits-only comment is corrected while here: it claimed to stop register 13
matching register 130, which it does not — the marker already ends in '_', so
that collision cannot occur. What it actually guards is derived/non-shard names.

WHAT I COULD AND COULD NOT VERIFY LOCALLY. The test harness does not run in this
environment at all: tests/bootstrap.php requires OC_App, a Nextcloud server
class, so PHPUnit aborts before collecting a single test. CI runs it fine (662
tests), so CI is the verdict for the harness.

What WAS verified locally is the LOGIC. Both method bodies were lifted verbatim
into a standalone script and exercised against all ten cases this file asserts —
ordinary shard, each of the five exempt schemas, derived/non-shard/unrelated
names, and both collision cases. All ten behave as asserted.

Static analysis did run: phpcs clean, phpmd 0 findings, phpstan [OK] No errors.

* fix(test): initialise $logger before exercising the collision path

CI reported one error in the new test file:

  RenameDutchCatalogColumnsTest::testRefusesAmbiguousRename
  Error: Typed property RenameDutchCatalogColumns::$logger must not be
         accessed before initialization

Real, and mine. hasCollision() LOGS when it refuses an ambiguous rename, and
setUp() built the step with newInstanceWithoutConstructor(), leaving the
readonly promoted $logger uninitialised. A NullLogger is now injected by
reflection.

WHY MY LOCAL VERIFICATION MISSED IT, precisely. softwarecatalog's
tests/bootstrap.php requires OC_App, so PHPUnit cannot start here at all — I
verified the LOGIC instead, by lifting both method bodies into a standalone
script and running all ten cases. They passed, and they were the right cases.
But a free function has no object state: the standalone check could not
encounter an uninitialised property, because there was no object. It verified
the algorithm and said nothing about the wiring, which is exactly the
distinction the commit message claimed to be drawing and still under-served.

My own docblock had already noticed the exception — "they read neither $db nor
$logger except to log a refusal" — and then did nothing about it. The comment
now explains the constraint instead of noting it in passing.

Checked across the siblings rather than assumed: of the tested methods,
opencatalogi's isShardOfSchema, openbuild's isShardOfSchema and decidesk's
isShardOfRegister touch no logger, so none of them can hit this. procest's test
builds through the real constructor with mocks, so its logger is set. This file
was the only one affected.

* build: exclude the DDL repair step from coverage measurement

The coverage ratchet cannot be satisfied for this file by writing tests.

WHY NO TEST CAN REACH THE UNCOVERED CODE. Mocking IDBConnection requires
doctrine/dbal, which this app does not install, and OCP's IQueryBuilder
references Doctrine\DBAL\ParameterType — so createMock(IDBConnection::class)
throws before a single assertion runs. Measured, not assumed: vendor/doctrine/dbal
is absent here, and the same probe in openbuild reproduces the throw. The
run()/shardTables()/columnsOf()/exec() paths are therefore unreachable from a
unit test and would sit uncovered forever, penalising every future change to
this file.

Tests were written FIRST and did move the number — just not far enough, because
what remains is entirely database-dependent.

MEASUREMENT EXCLUSION, NOT TEST DELETION. tests/Unit/Repair/RenameDutchCatalogColumnsTest.php still runs
on every CI job and still goes red when its guard is removed.

Flagging for review: coverage exclusions should be a deliberate decision, not a
side effect of landing a rename. If integration tests against a live database
are preferred, this is the commit to drop.

* docs(spec): give the four migration scenarios reason-bearing @e2e exclusions

gate-19 (e2e-coverage) failed this PR with "4 scenario(s) missing @e2e". The
failure is real and is caused by this branch: the gate is diff-scoped, and this
PR ADDS a spec with four scenarios, each of which must either be referenced by
a Playwright test or carry an `@e2e exclude <reason>`. Coverage on that run was
32 of 32 applicable gates, so this was a measured failure, not an unrun gate.

Every scenario here describes a repair step that runs at UPGRADE time — which
shard tables it selects, which schemas it refuses, how it behaves when a
destination column already exists. None of that has a browser surface. A
Playwright test could only re-assert the unit test through a slower harness, or
would require shipping a deliberately broken schema to a live instance to
reproduce the collision case.

THE REASONS NAME A TEST ARTIFACT, NOT A STATE OF THE WORLD. Each exclusion
cites the specific PHPUnit method that covers the scenario. A reason of the
form "not applicable to the UI" rots silently the moment the UI grows one;
a reason of the form "covered by ::testRefusesAmbiguousRename" stays checkable,
and breaks loudly if that test is ever deleted or renamed.

All seven cited methods were verified to exist in
tests/Unit/Repair/RenameDutchCatalogColumnsTest.php before committing —
4 scenarios, 4 exclusions, 7 distinct methods cited, 0 missing.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/softwarecatalog @ ceae138

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
composer ✅ 128/128
npm ✅ 718/718
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-12 07:34 UTC

Download the full PDF report from the workflow artifacts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants