Skip to content

Release: merge development into beta - #1063

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

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

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

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

Merging this PR will trigger the beta release workflow.

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

github-actions Bot and others added 3 commits August 31, 2026 10:28
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>
rubenvdlinde and others added 2 commits August 31, 2026 13:15
….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>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ de9fe0c

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.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ dc277db

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>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ 243be31

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>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ 812e8e8

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>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ 6738124

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.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ 75e33b7

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.

rubenvdlinde and others added 2 commits August 31, 2026 23:35
…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>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ 007ee6b

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.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ 054ff62

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>
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ 70d3cd2

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>
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ e84c3b6

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>
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ 2aa62bb

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.
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ f3156a8

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.
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ a35b1da

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.
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ f7ffb8e

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.
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ be5eacb

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.
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ 69e6e1e

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.

rubenvdlinde and others added 2 commits September 2, 2026 20:22
… 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
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ 923f514

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.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ c50c6dd

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>
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/decidiq @ 7a167fb

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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant