Release: merge development into beta - #1063
Conversation
The 1.1.7-beta.20260831102647 release bumped the version on beta. Without this, development stays behind beta and the next development -> beta promotion conflicts on the version file. Version files resolve to development's side, which is the higher line, so this never moves a version backwards.
Dependabot cannot propose composer updates for this app. Its updater fails
with:
Your requirements could not be resolved to an installable set of packages.
- edgedesign/phpqa[v1.27.0, ..., v1.27.2] require ext-xsl * ->
it is missing from your system.
config.platform pins php 8.3 so composer resolves against a known PHP version,
but says nothing about extensions. edgedesign/phpqa requires ext-xsl and the
resolving environment does not have it, so the resolve fails before any bump
can be computed.
CI is unaffected, which is why this went unnoticed: composer install replays
the committed lock and never re-resolves, so the pipeline stays green while
dependabot -- which does re-resolve -- fails every time. The shared quality.yml
installs no xsl extension anywhere and never invokes phpqa.
Declaring ext-xsl beside the php pin makes resolution assume exactly what the
committed lockfile already assumes. Verified in a clean composer:2 container
without --ignore-platform-reqs: the same file fails without this line and
resolves (103 installs, lock written) with it.
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
…1062) Per-widget `valueColor` is applied as an INLINE style on the number, so it beats the canonical card accent, and a hex literal opts the card out of NL Design System theming — which kpi-card.css forbids outright, because the nldesign app re-themes by overriding the Nextcloud tokens. Two outcomes, no third: * a colour that merely restated the default accent is deleted — `#0082c9` IS `--color-primary-element` in the default theme, so the tile looks identical and now re-themes correctly; * a colour that carried meaning becomes `variant`, which is themed and drives the icon tint and the number together so the two cannot disagree. `variant` on a stat/delta resolves through VARIANT_COLORS, which was moved onto the `-text` tokens first (nextcloud-vue#888) — the plain fill tokens failed WCAG AA at 1.08:1 as a foreground colour, and these conversions would have been the first tiles to hit that path. Edited as text rather than re-serialised, so the manifest keeps its hand-laid-out formatting; every `valueColor` in this file was confirmed to sit on a KPI widget first. Manifest schema validation passes. Prepared in a fresh clone — the workspace checkout is held by other in-flight work. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
….1.7-beta.20260831102647 chore(release): sync beta back into development
Levels this app with the fleet. 2.27.2 adds two fixes the earlier 2.27.0 pin does
not carry:
* headerless is no longer chromeless — a flat KPI card in a borderless wrapper
had no card, border or background at all;
* a stat `variant` paints from the `-text` tokens rather than the fill tokens,
which failed WCAG AA at 1.08:1 as a foreground colour. The KPI colour
cleanup converted hardcoded values to `variant`, so this is what makes those
conversions contrast-safe.
Lockfile only, and npm pruned nothing.
Prepared in a fresh clone — the workspace checkout is held by other in-flight
work.
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ⏭️ | ||||
| phpcs | ⏭️ | ||||
| phpmd | ⏭️ | ||||
| psalm | ⏭️ | ||||
| phpstan | ⏭️ | ||||
| phpmetrics | ⏭️ | ||||
| eslint | ⏭️ | ||||
| stylelint | ⏭️ | ||||
| build | ⏭️ | ||||
| composer | ⏭️ | ⏭️ | |||
| npm | ⏭️ | ⏭️ | |||
| app:check-code | ⏭️ | ||||
| info.xml | ⏭️ | ||||
| REUSE | ⏭️ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-31 11:48 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 12:51 UTC
Download the full PDF report from the workflow artifacts.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 15:04 UTC
Download the full PDF report from the workflow artifacts.
Dependabot cannot update these packages. Its npm updater fails with: npm error code EOVERRIDE npm error Override for <pkg> conflicts with direct dependency The package is listed BOTH as a direct dependency and as a literal overrides entry pinned to the same range. Dependabot bumps the direct dependency and does not move the override, npm rejects the mismatched pair, and the update is abandoned -- so these packages can never be updated, security advisories included. npm's self-reference is the idiom for exactly this: $<name> tells the override to follow whatever the direct dependency resolves to, instead of a range that has to be kept in lockstep by hand. This repo already uses it (see the vue entry under @vue/test-utils). Transitive consumers still get pinned to one version -- the direct dependency's -- which is what the override was there to do. What changes is that the pin tracks the dependency instead of duplicating it. Verified in a clean container: bumping the direct dependency with the literal override reproduces the EOVERRIDE above; with the self-reference the same bump resolves. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 16:57 UTC
Download the full PDF report from the workflow artifacts.
…top ci-seed lying about missing schemas (#1073) * fix(ci): repair the schema-l10n ratchet development is failing, and stop ci-seed lying about missing schemas Two fixes that were written alongside seed-profiles (#1024) but did not make it into the squash that landed. The first is a live breakage on `development`. ## 1. `development` is failing check:schema-l10n right now seed-profiles declared `Regeling.status` — a property that was REQUIRED and named by the schema's own `x-openregister-lifecycle` map, but never declared, so OpenRegister created no magic-table column and the lifecycle could never advance. The description it shipped with was 782 characters of engineering rationale: which magic table was measured, why the column was missing, what the transition map could not do. `fieldsFromSchema()` renders a property description as **the helper text under a form field**, so that text was pointed at somebody filling in a regulation. It is also not a translatable sentence, and it pushed the uncovered-schema-string ratchet from 1632 to 1633. So the description is now one short line for the reader of the form, and the rationale moved to a `_statusNote` sibling that no UI renders and the checker does not read. The short line is in en.json and nl.json, with l10n rebuilt. Ratchet: 1631 uncovered against a baseline of 1632. ## 2. "Schemas missing after import" was a paging artefact `ci-seed.sh` verified the register import by fetching `?_limit=1000` and reporting every required slug it could not find: ::error::Decidiq schemas missing after import: ['meeting', 'action-item', 'minutes', 'vote', 'transcript', ...] All ten were in the database. A dev instance with 35 apps installed holds 1111 schemas, so ten decidiq rows fell off the end of the page. The import had worked; the question was too small — and the error sent you to investigate the import, which is the worst shape a wrong answer can take. The limit now outruns any plausible instance, and truncation is DETECTED rather than assumed away: a response that comes back exactly full says so, and names the page as the suspect instead of the import. ## Verification 1275 unit tests, 378 vitest, PHPCS 0 errors, PHPStan clean, manifest valid, and all three l10n checks green. Prettier applied to the e2e spec. * fix(rbac): the governance scope backfill asked for a schema slug that does not exist `GovernanceRoleScopeProjector::reconcileAll()` called `setSchema('governancebody')`. The register declares `governance-body`, hyphenated, like every other slug this app uses. So `findAll()` threw. Its only caller is `ProjectGovernanceRoleScopes`, a repair step, which catches `\Throwable` and downgrades it to a warning — correctly, because a repair must never fail an upgrade. The result is that every upgrade this app has ever run printed Governance RBAC scope backfill skipped: Schema slug "governancebody" is not carried by register "decidiq" (id 28), which carries 91 schema(s). 0 schema(s) elsewhere on this instance carry this slug. and REQ-RBAC-001 never projected a single body. Nothing failed. Nothing was done. The warning even names the real cause, and reads as an environment problem. Measured on a live instance, before and after: `reconcileAll()` threw; it now returns **22 bodies reconciled**. ## The guard matters more than the character `setSchema()` takes a slug the register owns, resolved at runtime, so code and register can drift with nothing to notice — and the one caller that would have noticed is required to swallow it. So the new test compares every `setSchema('…')` literal in `lib/` against every slug the register and its fragments declare. It fails on the original typo, naming both the slug and the file: 'governancebody (asked for in GovernanceRoleScopeProjector.php)' Eleven other call sites were already correct; this was the only one that was not, and it was invisible. 1276 unit tests pass. PHPCS 0 errors, PHPStan clean. --------- Co-authored-by: Ruben van der Linde <juan.claude@conduction.nl>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-31 20:22 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 21:12 UTC
Download the full PDF report from the workflow artifacts.
…ion templates and per-body configuration (#1075) * feat(settings): give the unified decision templates a page, and retire the two that outlived their schemas The model refactor landed. The UI did not follow. `unified-decision-templates` (archived 2026-08-19) folded ProcessTemplate and VveDecisionTemplate into one DecisionTemplate, folded the 2017 modelreglement's categoryRules into each template's own votingRule/quorumRule, and marked all three superseded schemas active:false with their rows kept. It shipped no page. Measured on a live instance 2026-08-31: decision-template 28 objects NO page vve-decision-template 6 objects on the gear modelreglement-preset 3 objects on the gear So an operator opening the gear found two entries backed by retired schemas and no way at all to reach the templates the app actually uses. `context` on the unified schema is association | corporate | legislative | operations | citizen, so the VvE-only page could show one fifth of them: a municipality and a company board had templates and nowhere to see them. This adds one index and one detail page over `decision-template`, removes the two superseded pages and their menu entries, and puts `DecisionTemplates` in their place in the gear. No data migration: the removed pages address schemas OpenRegister already reports inactive, and their rows stay exactly where they are. ## The page immediately revealed a second defect 28 templates, of which 13 were DUPLICATES — each built-in present once with its seeded slug and once with none. The unified fragment seeds the thirteen built-ins, describing them as ports of the legacy rows, and supersession deliberately KEPT those rows. The migration reads them and creates a decision-template for each. Its idempotency index keys on `migratedFrom.sourceUuid`; a seeded row carries no `migratedFrom`, so it never matched and the migration duplicated every built-in on its first run. Nobody had seen it, because the schema had no surface to see it through. The migration now also matches on NAME — the honest key, since the seeds are ports of those very rows and a seeded `slug` is an import-time identifier OpenRegister does not expose as a queryable property, the same trap `bodyUuidForSlug()` already documents. A row created during the pass joins the index by reference, so two legacy rows sharing a name produce one template rather than two. Verified on a live instance: 28 → 15 after removing the duplicates, and 15 again after re-running the migration.⚠️ Existing instances keep their duplicates. This stops new ones; it does not delete. The migration is documented and tested as purely additive, and a row an operator has since edited must not be destroyed by a repair step. ## Verification 1279 unit tests (3 new, each failing without this change), PHPCS 0 errors, PHPStan clean, manifest Ajv PASS, nav ceiling holds at 6 primary entries. Live: the page lists 15 templates across association, corporate, legislative and operations, and the gear no longer offers either retired entry. * feat(config): generalise VvE configuration into per-body governance configuration `VveConfiguration` binds a governance body to four things, and every one of them is a general idea wearing a Dutch VvE name: deedOfDivisionDocument (splitsingsakte) the document that CONSTITUTES the body modelReglementVersion (1992/2006/2017) the VERSION of the model regulation fractionDenominator (breukdelen) the DENOMINATOR weighted votes use majorityOverrides[].decisionCategory a TEMPLATE CATEGORY being overridden A company limited by shares has articles of association, an articles version, and a share count its votes are weighted by. It could describe none of that, because the schema only offered VvE words. So a body that was not a VvE had no configuration at all, and the gear offered an entry a municipality had no use for. `modelRegulation` was worse than merely specific: it $ref'd `modelreglement-preset`, a schema unified-decision-templates RETIRED. It pointed at nothing still live, so it is not carried across; the plain `modelReglementVersion` string that change added alongside it for exactly this reason becomes `regulationVersion`. Superseded non-destructively, as unified-decision-templates did and as 67-model-debt-cleanup did for BoardProxy: VveConfiguration keeps its definition and rows (active:false, hardDelete:false), and the migration copies each row. `required` narrows to governanceBody alone. It was governanceBody + modelRegulation + fractionDenominator; the first now points at a retired schema and the second is meaningless for a body whose members each have one equal vote, so requiring either would make the generic schema undeclarable for most organisations. ## Three defects the live run found, in order 1. **RBAC.** A repair step runs during `occ upgrade` with no session, so OpenRegister saw 'Anonymous' and refused `create`. The step reported each failure as a warning, which does not fail an upgrade: it would have said "Update successful", then "0 migrated, 2 skipped", and nothing anyone reads would say the migration had not happened. Wrapped in runAsSystem(), one scope around the whole traversal, like the sibling template migration. 2. **Slug vs UUID.** The legacy rows hold `governanceBody: vve-parkstaete`. OpenRegister resolves slug references at IMPORT time, but a direct saveObject() validates strictly and rejected it for not matching format uuid. Resolved through `@self`, where a seeded slug actually lives. 3. **Idempotency was a lie.** Worse than 2, and caused by fixing it: the first run stored the RESOLVED uuid while the source still held the slug, so the second run compared a slug against an index of uuids, matched nothing, and created a THIRD configuration for a body that already had one. Measured live. The body reference is now resolved BEFORE the idempotency check, so both sides speak the same identifier. A null inside a majority override is also dropped: the validator rejects `quorumFraction: null` with "should be type 'string' but is 'null'" rather than treating it as unset. ## Seeds and copy The example-set seeds move onto the generic schema. `vve-zeewaarts-configuratie` is DROPPED rather than carried: it referenced `governanceBody: 00000000-…` and a null-UUID splitsingsakte, so it described nothing. Property descriptions render as the helper text under a form field, so all six are one short line for the person filling the form, with the engineering rationale in a `_designNote` no UI renders. Dutch written for each, not left as identity: the schema-l10n ratchet is 1614 against a baseline of 1632. ## Verification 1289 unit tests (10 new; the two idempotency regressions fail when the fix is reverted, naming the duplicate). PHPCS 0 errors, PHPStan clean, manifest Ajv PASS, nav ceiling holds, prettier clean. Live: 2 migrated, re-run reports 0 migrated and the count is unchanged, and every vve-configuration row still exists untouched. * fix(quality): satisfy PHPMD on the two migrations Two findings CI caught that I had not run locally. `MigrateVveConfigurationToBodyConfiguration` is 42 characters, over the 40-char LongClassName threshold. Renamed to `MigrateVveToBodyConfiguration`, which still says what it migrates from and to. `migrateSchema()` reached a cyclomatic complexity of exactly 10, the configured threshold, because the seeded-name check added a branch. The two ways a legacy row can already be represented — migrated on an earlier run, or carried by a seed — are now one `alreadyPresent()` predicate, which is also the clearer reading: the caller asks one question instead of testing two indexes inline. phpmd exits 0 on both configs. 1289 unit tests, PHPCS 0 errors, PHPStan clean. * fix(l10n): give the three new manifest strings a Dutch key gate-102 (manifest-l10n-coverage) caught what my own review did not: the new menu labels and the new column header shipped with no l10n/nl.json key, so a Dutch instance would render 'Decision templates', 'Body configuration' and 'Context' in English inside an otherwise translated gear. This is the same trap as the setup-wizard copy earlier in this chain, and the reason the gate exists: manifest strings are user-facing text that no code search finds, because nothing in src/ references them as literals. Manifest strings without a Dutch key: 0. l10n rebuilt, all 26 catalogues up to date. --------- Co-authored-by: Ruben van der Linde <juan.claude@conduction.nl>
* chore(register): retire the product schema, pipelinq owns it
Three apps shipped a schema slugged `product`: decidiq, filinq and
pipelinq. Slugs are global on a shared OpenRegister and a bare relation
resolves by slug alone, so the copies could bind to each other. Pipelinq's
is the fleet's commercial catalog entry and keeps the slug.
Nothing in decidiq read this copy: zero references across src/ and lib/.
The `Decision.product` relation that pointed at it goes too, otherwise the
descriptor ships a \$ref to a schema it no longer carries.
Removing it from the descriptor is only half the job. ImportHandler unions
the freshly-imported schema ids into the register's existing list, so the
live row survives a descriptor deletion. Verified on the dev instance:
occ openregister:schemas:prune-retired --app decidiq --slug product --apply
-> product (id=987, app=decidiq): 0 objects, referenced by 1 register(s)
DELETED (objects removed=0, table dropped=yes)
* test(register): follow the retired Product schema
RegisterJsonTest hard-codes the schema list and the schema.org annotation
map, so retiring Product left it asserting 39 schemas against 38 and
expecting a schema:Product annotation that no longer exists.
---------
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ⏭️ | ||||
| phpcs | ⏭️ | ||||
| phpmd | ⏭️ | ||||
| psalm | ⏭️ | ||||
| phpstan | ⏭️ | ||||
| phpmetrics | ⏭️ | ||||
| eslint | ⏭️ | ||||
| stylelint | ✅ | ||||
| build | ❌ | ||||
| composer | ⏭️ | ⏭️ | |||
| npm | ⏭️ | ⏭️ | |||
| app:check-code | ⏭️ | ||||
| info.xml | ⏭️ | ||||
| REUSE | ⏭️ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-31 21:49 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-31 22:34 UTC
Download the full PDF report from the workflow artifacts.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-01 06:21 UTC
Download the full PDF report from the workflow artifacts.
`npm run lint` was `eslint src`, so two whole trees were never linted. Across
the fleet that hid roughly 3,900 errors, none of which any CI leg had shown.
Most of it was the config, not the code, and the same two defects were in
every app.
**scripts/ had no config block at all.** These are CommonJS Node CLI checkers,
and flat config defaults every `.js` to ESM with browser-ish globals, so eslint
read the CommonJS wrapper itself as undefined identifiers: `require`,
`process`, `__dirname`, `__filename`, `module`. A `scripts/**` block now
declares the environment. Declaring beats suppressing here: `no-undef` is the
rule that catches a genuinely misspelled identifier, and dozens of fake
findings would bury a real one. A second block covers `scripts/**/*.mjs`, which
is ESM and needs Node's globals without the CommonJS wrapper.
**The tests block applied a non-TypeScript-aware rule to TypeScript.** It named
`tests/**/*.ts` while setting the CORE `no-unused-vars`, which v9 deliberately
turns off for `.ts` in favour of the `@typescript-eslint` version. The core
rule reads the parameter names inside a function TYPE as bindings, so
t?: (app: string, key: string) => string
reports `app` and `key` as unused variables, and every unused `catch (e)` in a
`.ts` spec reports twice. The block is split now: `.js`/`.mjs` on the core
rule, `.ts`/`.tsx` on the TypeScript one, same patterns on both.
Also: stale `eslint-disable` comments naming plugins eslint 10 no longer
registers, which are themselves errors ("Definition for rule ... was not
found"), and a rule that must not parse shell scripts.
The genuinely real findings were the useful part: dead locals, unused imports,
dead helper functions, unused `catch` bindings, extensionless relative imports,
and a handful of `== null` comparisons spelled out so they still match null AND
undefined.
Verified per app: `npm run lint` 0 errors over src + tests + scripts,
`prettier --check` clean, and the unit suite still green.
Co-authored-by: Ruben van der Linde <juan.claude@conduction.nl>
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-01 11:23 UTC
Download the full PDF report from the workflow artifacts.
The lint pass left two shapes that eslint and prettier both call clean while
the Playwright suite fails to load. `development` collected ZERO tests.
A Playwright callback's destructuring is a FIXTURE REQUEST, not a binding list.
Renaming an unused `request` to `_request` asks for a fixture that does not
exist and the file never loads. The binding is renamed, the key kept:
async ({ page, request: _request }) => {
And the type-import conversion added a second `import type { Page }` to a file
that already imported `Page` alongside `ConsoleMessage`, which is a
duplicate-identifier SyntaxError.
`npx playwright test --list` parses every spec without running one, and is the
check that catches both. A green linter cannot.
Co-authored-by: Ruben van der Linde <juan.claude@conduction.nl>
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-01 12:54 UTC
Download the full PDF report from the workflow artifacts.
Nextcloud serves an app under both /apps/decidiq/... and /index.php/apps/decidiq/..., but generateUrl() returns only the form the instance is configured for. Used as the vue-router base, that means a visitor arriving on the other form has a pathname the router cannot strip its base from. No route matches, the catch-all takes over, and they land on the dashboard with no error at all. Measured live on learniq, which had the identical base, across all 282 of its routes: /apps/learniq/courses resolved to Courses, and /index.php/apps/learniq/courses resolved to the dashboard. Not one broken page, every deep link in that URL form. routerBase() derives the base from the pathname, so it always matches the URL the visitor actually arrived on. openregister, opencatalogi, stackiq, larpinq, zaakafhandelapp, pipelinq and keepiq already do exactly this.
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-01 19:51 UTC
Download the full PDF report from the workflow artifacts.
* fix(integration): accept the decision types dossiq delegates dossiq's case flow raises decisions with decisionType=advice (AdviceDelegationService) and decisionType=bezwaar-decision (BezwaarDecisionDelegationService). Both sat outside DecisionIntegrationService::ALLOWED_TYPES, so createDecision refused them, the DecisionRequested listener left the event unhandled, and dossiq correctly failed closed: the whole decidiq leg of the case flow was unreachable (found in the 2026-09-01 acceptance run). The closed list is a fix instruction, not a bypass target: extend the vocabulary in every home it has. That is four places, now pinned together by a parity test so they cannot drift again: - Decision.decisionType enum in decidesk_register.json (schema 0.9.0 -> 0.10.0, register 0.11.0 -> 0.12.0) - its copy in decidiq_mock_register.json (demo data 1.1.0 -> 1.2.0) - the DecisionTemplate narrowing in register.d/68 (0.1.0 -> 0.2.0) - ALLOWED_TYPES in DecisionIntegrationService The decisions leaf sorts decisionType=advice into its Adviezen bucket; bezwaar-decision keeps falling through to the Besluiten catch-all. Regression tests: createDecision accepts both delegated types, still refuses an unknown type, and the four vocabulary homes are asserted identical (the parity test was proven to fail on a planted divergence). stackiq sends contract and contract-renewal, both already accepted; dossiq's DECISION_TYPE_BEZWAAR (bezwaar-beslissing) has no call sites and is deliberately not admitted. * chore(build): guard the built chunks against a second Dexie version The installed 1.0.1-unstable build (2026-08-24) shipped js/decidiq-integration-init.js with a vendored dexie 4.4.4, built with nc-vue before its build externalised dexie. That script loads on every page of the instance, so every newer app's 4.4.5 then threw 'Two different versions of Dexie loaded' at module init and blanked every SPA on the rig. A rebuild against current nc-vue resolves one 4.4.5 across all three chunks; this postbuild guard (the openregister#3307 / dossiq#1636 script) makes the build fail rather than ship a mix or a version the lockfile does not resolve.
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-01 21:20 UTC
Download the full PDF report from the workflow artifacts.
… run (#1096) * fix(decision): schema-valid flow-created decisions + declare the guarded lifecycle edges Defect 1: a decision raised through the delegation event (dossiq's requestDecision node) arrived without the schema-required text: the listener dropped the delegation context, and createDecision() wrote title/text as empty strings and decisionDate as an invalid empty date-time. The object saved (the create path skipped required-property validation) but every later PUT was then rejected on exactly that validation, leaving the decision un-updatable (observed live: decision 7f2dc8f4). createDecision() now derives text from the supplied text, else the delegation context (question/reasoning/motivation/description), else a translated fallback naming the source app and subject; title falls back to the subject label, then the source app; an empty decisionDate is omitted. The listener forwards payload.context. Defect 2: DecisionTransitionGuard permits deliberating->decided (decide-without-vote, licensed by the decision-management spec for operational domains) and archive-from-decided, but the schema's x-openregister-lifecycle declared neither edge, so OR's LifecycleValidationListener rejected both at save time: dead auth code. Both edges are now declared (canonical + mock register), with the domain-policy guard still filtering on top, and DecisionTransitionMatrixTest pins guard<->schema parity in both directions so the accepted grammar stays the executable one. * fix(audit): land governance audit on OR's audit trail instead of a schema no register carries Every decidiq audit append failed with 'Schema slug audit-trail is not carried by register decidiq': the C3 board-portal retirement mapped board-audit-log-entry to OR's auditTrail but only renamed the schema slug the writer saves to, so every governance action since (decision transitions included) silently produced no audit row while the action itself persisted. AuditLogService now consumes OR's audit surface (ADR-022, the audit-trail-fleet-wide-consumption spec, and the same AuditTrailMapper::createAuditTrailEntry() path dossiq's parafering audit uses): appends land as namespaced decidiq.audit.{action} entries attached to the acted-on object, with actor, the full uid list and the payload in the entry context; hash chaining and sealing are the platform's (AuditSealJob / AuditHashService), so verify() delegates to verifyChain() and query()/export() read the decidiq namespace back out of the trail. Failure is loud by contract: an unavailable surface or an unresolvable uid list logs at error level and returns success=false, pinned by tests. Also fixes three appends that were ALWAYS refused with 'Unknown action' because their action names were never in ACTIONS: integration-create, integration-subscribe, and the retention purge (which sent the dotted transcript.retention.purge; the job now sends transcript-retention-purge, and its test asserts membership in ACTIONS so the fake can no longer accept what production refuses). New OR test stubs (AuditTrail, AuditTrailMapper, AuditHashService) follow the signature-parity contract (#399). * refactor(decision): extract DelegatedDecisionDefaults and settle the analyzers PHPMD flagged DecisionIntegrationService at complexity 63 (threshold 55) after the title/text derivation landed, so the derivation moves to its own small collaborator, DelegatedDecisionDefaults, which the service consumes. AuditLogService imports DateTimeZone properly (MissingImport), psalm.xml gains referencedClass entries for the three OR audit classes (cross-app, loaded at runtime), and one new test's @SPEC anchor is repointed at the decidesk-decision-events spec that actually exists (gate-46). * fix(l10n): regenerate nl.js with the repo generator The nl browser catalogue was rebuilt by hand with lexically sorted keys, but scripts/build-l10n-js.js emits the json's own key order (the numeric 2026/2027 keys lead), so check:l10n-js called it stale. Regenerated via npm run l10n:build; check:l10n-js reports all 26 catalogues up to date.
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-01 22:44 UTC
Download the full PDF report from the workflow artifacts.
… not code (#1099) The vocabulary lived in four homes: the ALLOWED_TYPES constant, the Decision enum in both registers, and the DecisionTemplate narrowing in fragment 68, pinned together by a parity test. Adding a type cost a release in four places, which is how dossiq's advice and the pending woo-decision need each stalled. One authority now: the decision_types app setting. SeedDecisionTypes writes today's vocabulary (woo-decision included) once and never overwrites an admin's edits. DecisionTypeRegistry validates referentially, falling back to the seed only while no row is stored. The integration hub fails closed on an unknown type and its refusal names the fix: an administrator adds the type, no release. The schema declarations drop their enums on purpose: a declared enum drifts from the store and recreates the four-homes problem. The parity test inverts, proving no second authority exists and that the seed covers every type a fleet caller sends (dossiq, stackiq). Also bumps conduction/hydra-gates to v1.11.0.
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-02 11:26 UTC
Download the full PDF report from the workflow artifacts.
…ngine (#1101) * feat(approval-routes): the engine absorbs the parafering runtime The stage-typed vocabulary, mandated delegate signing judged against the local toedeling register, the terminal return (terugsturen), parallel co-signing groups, a conclusion announced from every concluding path with the full sign-off record on the event, and the ask mirrored onto OpenRegister's task surface. dossiq's retirement of its local runtime (parafering-runtime-to-decidiq) depends on this and merges after it. * refactor(approval-routes): the guard and the step mapper get one owner each PHPMD flagged the absorbed runtime's weight honestly: the engine class had grown past the complexity threshold. The fail-closed authorisation questions move to ApprovalStageGuard (the split dossiq's ParafeerStepGuard made, for the same reason) and the pure step-to-stage shaping to ApprovalRouteStepMapper. Neither is a second engine: nothing in either advances a route or writes a row. * fix(manifest): the FlowDetail page's type is flow-detail, and the mapper methods carry their spec 'flow' is not in the v2 page-type enum and resolves to no component in the renderer's page map, so the FlowDetail route rendered nothing manifest-driven. Found by gate-53 on the assembled manifest; pre-existing, fixed in passing per the repo's standing rule. The step mapper's methods gain the @SPEC tags gate-16 asked for.
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-02 12:23 UTC
Download the full PDF report from the workflow artifacts.
… inside (#1103) its own card.⚠️ THE CAUSE IS AN INSET, NOT A COMPONENT. A card widget renders `flush` and then has its padding put BACK by the card-fit rule (`padding: 8px 14px`), so the KPI sits inside the wrapper while the WRAPPER draws the border, radius and background the user reads as "the card". The library's `--clickable:hover` rule then drew a 2px border and a drop shadow on the KPI itself, 8-14px in from the edge being hovered. Measured live on dossiq, while genuinely hovered: the tile carried a 2px rgb(0,103,158) border and its own shadow, inset 9px from the wrapper's top and 15px from its left. A non-card-fit tile sits at 1px, so its border lands on the wrapper's own edge and reads as one card. That inset is the whole difference, which is why it looked app-specific and was not. nextcloud-vue#932 moves the affordance to the wrapper. No layout change: nothing moves, one card outlines. Verified in the browser with a real pointer hover: before inner 2px rgb(0,103,158) + shadow wrapper grey, no shadow after inner transparent, no shadow wrapper rgb(0,103,158) + shadow Every app with clickable stat, gauge or delta tiles had this. This bump is one of the fleet sweep that clears it. Dependency change only: package.json and the nextcloud-vue entry in package-lock.json. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
…d the type picker from the registry (#1104) * fix(approval-routes): advance stages through OR's patch path, and feed the type picker from the registry Two defects from the live rig proof. A partial DecisionStage payload saved with a uuid is a FULL REPLACE to OpenRegister, which validates it whole and 400s on the required sequence, stageType, decisionMakerType and label it omits — so no approval route raised over the dossiq seam could ever advance or conclude. Every stage transition now goes through RegisterObjectStore::patch(), a thin wrapper over OR's sanctioned patchObject() merge path. The sweep found the same class in AgendaService (advanceBobPhase, processHamerstukken, reorderItems all 400d the same way) and in ApprovalStageTaskProjector's taskUuid linkage; all are on the patch path now. The route-engine test fakes MERGED uuid-bearing saves, which live OR does not, so the suite was green while every advance 400d — they now replace and validate required properties exactly like live OR, and fail 9 tests against the old code. The create-proposal pickers hardcoded five decision types, so a type an administrator added to the decision_types registry validated fine at the write path and never appeared in any picker. A new GET /api/v1/decision-types endpoint serves the registry's vocabulary, both pickers build their schema from it through a shared proposalFormSchema(), and the thirteen shipped types carry translated labels in every locale. An unreachable registry degrades to the shipped seed instead of blocking creation. * style(frontend): apply prettier to the new picker module and its spec
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-09-02 19:20 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-02 20:05 UTC
Download the full PDF report from the workflow artifacts.
…istry endpoint (#1107) Three HIGH defects from the rig re-proof. Unlabeled steps wedge every cross-app route (defect 2). The decision-stage schema requires a label, instantiate() wrote '' for steps without one, OpenRegister stored NULL, and the patch recording the FIRST sign-off then 400'd — dossiq's held routes carry no step labels, so the cross-app case always hit it. The schema keeps its requirement (the route timeline displays the label); instantiate() now derives one from the stage type and step number via the shared mapper (labelOf()). The ordering bug the 400 exposed is fixed too: record() writes the stage FIRST and appends the action row after, so a refused stage write no longer leaves an orphan action row per retry — pinned by a no-orphan test on exactly the legacy NULL-label shape. An idempotent post-migration repair step (RepairDecisionStageLabels, runAsSystem, fail-soft) backfills derived labels onto existing NULL-label stages; the orphan action rows are deliberately KEPT — they are the audit record of what the signer did. Conclusions were never announced (defect 3). provenanceOf() resolved the route with findAll(['id' => ...]), and a top-level id filter matches NOTHING in OpenRegister (identity lives in @self), so sourceApp resolved empty, every conclusion read as 'internal route' and dossiq never heard one. The store gains find() (get-by-uuid, RBAC-scoped), and the sweep of the whole class fixed three sites: the announcer's provenance, assertSubjectAccessible() (which refused every valid subject), and MandateDirectory::resolve() (which silently passed every LOCAL mandate as external, skipping the effective/window/delegate checks). AuditLogService's ['uuid' => ...] filter is NOT this class: it goes to OR's AuditTrailMapper where uuid is a real column. Every stateful test fake now returns zero rows for top-level id/uuid filters, like live OR — a fake that resolves the dead form agrees with the caller and cannot fail. The registry endpoint shipped dead (defect 5). decisionTypes#index (GET /api/v1/decision-types) was declared below the api#index wildcard (GET /api/v1/{resource}); Nextcloud matches in declaration order, so the wildcard answered 404 'Unknown resource' and the picker silently fell back to its 13 shipped types. The literal route now precedes the wildcard, and a new contract test loads the real route table and asserts EVERY literal route precedes every wildcard that matches it (verified red on the pre-fix table, naming exactly this route; no other literal is shadowed). Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-03 06:40 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.