Release: merge development into beta - #197
Conversation
Quality Report
Summary
PHP Quality
Vue Quality
Security
License Compliance
composer dependencies (120 total)
npm dependencies (582 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 (120 total)
npm dependencies (582 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 (120 total)
npm dependencies (582 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 (120 total)
npm dependencies (582 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 (120 total)
npm dependencies (582 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 (120 total)
npm dependencies (582 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 (120 total)
npm dependencies (582 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 (120 total)
npm dependencies (582 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 (120 total)
npm dependencies (582 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/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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
Quality Report — ConductionNL/softwarecatalog @
|
| 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
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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>
Quality Report — ConductionNL/softwarecatalog @
|
| 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>
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Quality Report — ConductionNL/softwarecatalog @
|
| 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.
Automated PR to sync development changes to beta for beta release.
Merging this PR will trigger the beta release workflow.