Release: merge development into beta - #1983
Merged
Merged
Conversation
The 0.4.4 release bumped the version on main. Without this, development stays behind main and the next development -> main 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.
…260912204215 chore(release): 0.4.7-unstable.20260912204215
….4.4 chore(release): sync main back into development
github-actions
Bot
requested review from
Rem-Dam,
SudoThijn,
WilcoLouwerse,
bbrands02,
remko48,
rjzondervan and
rubenvdlinde
as code owners
September 13, 2026 17:04
…0260913184338 chore(sync): carry beta back into development
…lists (Q6.20, 12.3) (#1991) * spec(openspec): an outbound webhook is signed by default, gap register row Q6.20 * spec(openspec): the Objecten and Objecttypen API as an integriq facade, gap register row 12.3
* spec(connections): integriq's half of the connection registry * feat(connections): the connection schema, declaration contract, events and D4 resolver * feat(connections): sync, reports, health job and link-a-source endpoint * test(connections): sync, probes, listeners, health job and link endpoint * fix(connections): coding standard on the new classes, and SourcesController drops its unread logger * feat(connections): App connections page, status formatters and the link a source dialog * test(connections): Playwright coverage for the App connections page and dialog * feat(connections): adopt the dossiq amendments, unconfiguredMessage, the rule 5 message and the /connections route * chore(connections): demo rows for the connection schema, and move the version so the new repair step runs on upgrade * test(connections): named arguments and wrapped lines, so the new tests pass the coding standard too * fix(connections): rename the schema slug to app_connection, because stackiq owns connection
…ed source (#1997) Round 4 discovery cluster 26 and depth-study cluster CT-5, decision D2. One provider contract with suggest, resolve and describe, bindings over the PDOK, Haal Centraal BRP and KvK sources integriq already holds, and provenance on every resolved value so read live is a checkable claim. Asks openregister for x-openregister-property-source by name, and says plainly that it is not x-openregister-object-source. The umbrella gains a discovery wave 1 section. It records that D12 was answered for Nextcloud Mail rather than integriq, so integriq opens no mail-account change and cluster 28 moves to dossiq.
…rces and the expression allowlist (#2001) * docs(openspec): the statutory gateways and the frameworks we claim Round 4 discovery cluster 56, twelve candidates, six passers of which five documented, proving system xxllnc-zaken. Decisions D21 and D6. Size L. Nine requirements: the claim on the catalogue entry, the broker as configuration, CORV and GGK, Wmebv obligations per route, publication by reference, the ZGW registry as a binding, the on-premise bridge, jurisdiction per gateway and the WKPB registration. * docs(openspec): users and groups come from the directory and stay in step Round 4 discovery cluster 33, candidates C-access-and-privacy-82 (matrix hole), C-integrations-34 and C-integrations-21. Number 7 of the twenty-five loudest, four driven passers, proving system glpi. Size M. Nextcloud keeps the accounts; integriq keeps the connection, the mapping and the run. C-integrations-21 is recorded and not built. * docs(openspec): record every outbound message per recipient and per step Round 4 discovery cluster 23, eight candidates, six must, three matrix holes, rows 6.11, 6.20 and 6.23, proving system glpi. Size M. The record, the body behind its own permission, retry over the existing replay act, forwarding as its own record, three honest delivery states, a last-contact query and the external address as a recipient. No account, no transport: D12 gives the mail account to Nextcloud Mail. * docs(openspec): every outbound call is readable, replayable and governed by a policy Round 4 discovery cluster 27, eight candidates, four must, three matrix holes, row 6.11, proving system gitlab. Number 8 of the twenty-five loudest, five driven passers. Size M. The call record, replay over dead-letter-replay, firing by hand, the retry schedule as configuration, mapping versions on a replay, external verdicts and a blocking pre-check. dossiq retires its hardcoded StufRetryJob schedule. * docs(openspec): a channel is a declared adapter with a routing rule Round 4 discovery cluster 45, six candidates, proving system xxllnc-zaken. Size M. One adapter contract, routing as configuration with a review inbox, the signed submission route generalised from open-formulieren-intake, location and media on the inbound shape, and a reply over the arriving channel. Three candidates recorded and not built: the smart picker under D9, and the native mobile app on documented passers only under D21. * docs(openspec): integriq holds the sources a migration reads from Round 4 discovery cluster 8, integriq's half by the cluster's own mechanism line. Candidates C-configuration-88 and C-configuration-16, both matrix holes, plus the read half of C-configuration-95. Numbers 2 and 13 of the twenty-five loudest, eight driven passers on the migration path. Size M; openregister keeps the engine, the preview and the conflict policy. * docs(openspec): an expression reaches outside the instance only through an allowlist Depth study D-casetype-20 and consolidated candidate C-access-and-privacy-40, passer Valtimo. Size S. One prefixed source contract, env: resolving only an administered key with no wildcard, redaction before buffering and a declared write capability. Openregister keeps the expression language under D3 and the rest of cluster 4. * docs(openspec): index wave 3 in the integriq parity umbrella Six clusters open, cluster 60 stays closed under D12. Adds the wave 3 table, the loudest-25 numbers it answers, what other apps owe, and the eight candidates recorded and not built with their reasons. * docs(openspec): correct the wave 3 counts in the umbrella Five of the seven waiting clusters open, not six, and seven changes open, not six. Cluster 28 and cluster 60 both stay closed, each for its own reason.
…-only rows and knows limited (#2010) * feat(connections): simulated values, JSON paths, reported-only rows and the limited status Adapter values can now be a provider name or a field inside a JSON settings blob (adapter.simulatedValues, adapter.jsonPath), with defaults that keep every existing declaration's meaning. reportedOnly skips rules 3 and 5, and a simulated report stands against a newer probe (rule 4a). limited joins the status enum. The health job resolves every row after its probes. Contract: hydra#673, design D12. * test(connections): each D12 amendment, the unchanged defaults and the job's resolve without a call * test(connections): no inline ifs in the new tests, for the coding standard * refactor(connections): config reading moves to ConnectionConfigReader, so the resolver stays under the complexity limit
#2012) * docs(openspec): open the outbound sender identity cluster and index it Discovery cluster 61, the last integriq cluster recorded and not opened. The re-read D12 asked for is done: an identity is a face on a Nextcloud Mail account. Extends outbound-communication-log. * docs(openspec): correct the cluster number to 61 in the integriq umbrella
…4 and 6.27 already carried (#2013) * spec(parity): records owned by an external source, with a policy for when they disappear Closes gap row 5.19 (rated no for dossiq). Integriq declares the ownership mode and the disappearance policy on the synchronisation, projects ownership and last-seen onto the record, and refuses a local delete of a source-owned record without a written reason. Specs only, no implementation. * spec(parity): a one-off recipient on a single message, or a standing one suppressed with a reason Closes gap row 6.23 (rated no for dossiq). The recipient list of one message becomes a recorded decision: standing, added and suppressed, with a reason on every suppression and a refusal for a recipient the caller marked required. Specs only, no implementation. * docs(openspec): index the pending-proposal half of the parity programme Two rows open a change here (5.19 and 6.23) and two are already carried by outbound-communication-log in substance (6.24 and 6.27). The reservation on REQ-OCL-006 is recorded rather than resolved quietly.
…n step (#2015) * feat(directory): a directory connection that keeps Nextcloud groups in step Tasks 1 to 4 of directory-and-group-sync: the directory source and its mock fixture, the declared mapping with its create-or-refuse setting, the open-work reporter that asks rather than reads, the scheduled and on-demand runs, and the SCIM 2.0 endpoint gated by the existing consumer-backed credential. * feat(directory): the directory sync screen, its manifest page and the strings Tasks 1, 2, 5 and 6 of directory-and-group-sync on the frontend: one screen for the connections, their mapping, a preview that changes nothing, a run that the removal guard can stop, and what every run changed. Dutch and English strings for all of it. * test(directory): the run, the mapping, the reporter and the SCIM gate Tasks 1 to 6: a new member joins, a leaver is removed, a preview changes nothing, a truncated directory is guarded before writing, an unknown target group refuses naming the group, a silent consumer reads unknown and not zero, and an unauthenticated SCIM call reads nothing. Also moves the screen onto the Sources index and a typed logs page, so no new custom page is added, and adds the anonymous rate ceiling every public SCIM route needs. * feat(directory): the e2e scenarios, the SCIM contract tests and the admin docs Six Playwright scenarios drive the real screens over a mock-mode fixture, so the reader, the mapping and the membership writer under test are the production ones. The Newman folder asserts what a collection can assert without a seeded credential: every SCIM route rejects an unauthenticated call before any user is read. Ticks the change's tasks and records where the screen landed and what the hand-offs to dossiq are. * fix(directory): finish the lint pass, and keep the guard messages their translations match The reword of the ratio-guard and unknown-group messages had orphaned the four l10n files this branch itself added, so Dutch would have fallen back to the English source. The original wording is restored: it also names where the limit and the create-missing setting live. Also: one test per SCIM route for an unauthenticated call, the unused created prop dropped from the run summary, and the import order and optional catch binding the linter asked for. * fix(directory): the two guard messages fit the line limit, and every label has Dutch The ratio-guard and unknown-group messages ran over the 150 character limit, so they are trimmed and the four l10n files are trimmed with them. Shortening the source string without moving the catalogue key is what had left Dutch falling back to English. The Added and Removed columns on Directory runs had no nl.json key, which nothing else reports: check:l10n-js compares nl.json to nl.js, and a string missing from both reads as in sync. The open watcher on the run modal now carries the @SPEC gate 16 asks for. * fix(directory): drop the else in the membership write, and baseline what phpmd cannot be talked out of The full suite caught what the diff check cannot: it does not run phpmd. Nine findings, all in the new directory files. The else in the membership write is gone, because that one was worth fixing rather than suppressing. The other six are a named constructor (DirectoryEntry::fromArray), value objects carrying a boolean because it is data and not a flag, dryRun and confirmRemovals which are always called by name, and the $argument that TimedJob::run forces on every job (RegisterBootstrapJob is already baselined for exactly that). The entries are appended, not regenerated. Regenerating would have written 302 where 318 stood and dropped 16 stale entries in silence.
…ec tasks (#2018) #1558 dropped the dead Codeberg issue links; two archived tasks.md files still linked to codeberg.org/Conduction/openconnector/issues/*, which no longer resolve. They now name the pre-migration issue as plain text, and the visual-flow note records that GitHub is the only tracker. Ported from the unmerged tail of fix/repoint-codeberg-links-to-github (7d878fd); its third file already reads that way on development.
* feat(connections): a settings refresh retires older reports and probes ConnectionRefreshRequestedEvent now stamps refreshedAt on the affected rows, every row of the app when the key is null, before resolving them. D4 rules 4a and 4b count only a report or probe that is not older than refreshedAt, so a fixed setting clears an old error on save. The row keeps the observation for reading. A sync, a report, a probe and the plain resolve leave refreshedAt alone. Schema app_connection moves to 1.2.0. Contract: hydra connection-registry D3, D4, D6 and D12 item 5. * test(connections): a refresh retires older observations, and only a refresh writes refreshedAt Covers both spec scenarios at the resolver and at the service, equal times (also across offsets), a probe newer than the refresh, a retired probe and a retired simulated report, a null key stamping every row of the app, and a sync, report and plain resolve keeping refreshedAt. Inverting the older-than comparison turns both scenario tests red on their status assertions. * docs(openspec): a refresh retires older connection observations Adds the two REQ-CONN-005 scenarios from hydra#674, the refreshedAt rule to REQ-CONN-003 and REQ-CONN-004, the choices integriq makes (two refresh paths, comparison by instant, equal counts) and task group 6. * refactor(connections): both refresh paths share one resolve loop, so the classes stay under the complexity limit phpmd ExcessiveClassComplexity flagged ConnectionRegistryService at 53 and ConnectionStatusResolver at 51 (limit 50). refresh() and refreshRequested() now share resolveRows(), the version check in needsSync() is one array_diff, and the refreshedAt comparison lives in readObservation(). Behaviour is unchanged; the mutation check was repeated on the new comparison. * docs(openspec): refreshRequested saves a row whose data changed
…nside JSON (#2022) * feat(connections): requiredConfig reads false as empty and can look inside JSON A requiredConfig entry is now an app-config key, always the whole key even with dots, or {configKey, jsonPath}. A value is empty when it reads as "", false or 0 after trimming, or is JSON false, 0 or null, or the path is missing. A switch stored under the bool type is read with getValueBool, so false no longer counts as a filled setting. The path walk and the array-type read are the ones adapter.jsonPath already uses. Contract: hydra connection-registry D2, D4 and D12 items 6 and 7. * docs(openspec): a switch stored as false is not a filled setting Adds the three REQ-CONN-003 scenarios from the hydra requiredConfig amendment, the entry and emptiness rules to the requirement, the choices integriq makes (one list for every value form, typed keys read by type, objects and lists count as filled) and task group 7.
…mpty JSON list counts as empty (#2024) * feat(connections): a switched-off connection reads disabled, and an empty JSON list counts as empty A declaration can carry switch {configKey, jsonPath?, offValues?} and disabledMessage. D4 rule 2b sits below rule 2 and above rules 3 and 4: a switch that reads as off gives disabled, on reportedOnly rows too. Without offValues off means empty; with offValues it means one of them, so an unset key is off only when "" is listed. The value is read through the same path walk and emptiness rule as requiredConfig, in ConnectionConfigReader. disabled joins the status enum: app_connection moves to 1.3.0, reports may send it, and the formatter shows Switched off (Uitgeschakeld). A JSON empty array or object, decoded or stored as text such as [] or { }, now counts as empty. Contract: hydra connection-registry D2, D3, D4 and D12 items 8 and 9 (hydra#677). * test(connections): the five switch and empty-list scenarios, their controls and the validator refusals Covers the five spec scenarios, an off switch above a newer probe, a simulated report and a mock adapter, a switch with jsonPath (text and array type), unset keys with and without offValues, a reported disabled at the resolver and the service, [], {}, [ ] and [0] (filled), and declarations without a switch resolving exactly as before. Validator and schema agree on switch fixtures and refuse one without configKey or with an unknown key. * docs(openspec): a switched-off connection reads disabled, and an empty JSON list counts as empty Adds the four REQ-CONN-003 scenarios and the REQ-CONN-004 scenario from hydra#677, the switch rule and the wider emptiness rule to REQ-CONN-003, the seventh status to REQ-CONN-004, the choices integriq makes (switch read as a requiredConfig entry, rule number 2, kept time, which text is decoded) and task group 8. * test(connections): wrap a long fixture line for the coding standard * fix(l10n): translate the requiredConfig schema strings, so the schema l10n count is back at its baseline * refactor(connections): judging a config value moves to ConnectionConfigValue, so reader and resolver stay under the complexity limit phpmd ExcessiveClassComplexity rated ConnectionConfigReader at 55 and ConnectionStatusResolver at 51 (limit 50) with the switch added. Filled or empty, one of a list, and scalar as text now live in ConnectionConfigValue. The reader takes an empty adapter configKey and a missing switch itself, so the resolver's rule methods lose a branch each. Reader 45, resolver 49, value class 13. A stored switch without a string configKey never reads as off.
… every CI install
`composer install` fails on any runner whose PHP ships ext-redis below 6.1,
which is every GitHub-hosted runner — PHP 8.3 there carries 5.3.7:
Problem 1
- ext-redis is present at version 5.3.7 and cannot be modified by Composer
- symfony/cache is locked to version v7.4.14 and an update of this
package was not requested.
- symfony/cache v7.4.14 conflicts with ext-redis <6.1.
Your lock file does not contain a compatible set of packages.
symfony/cache is not a direct dependency: symfony/expression-language ^6.4
pulls it in, and the lock resolved v7.4.14, which declares
"conflict": { "ext-redis": "<6.1", ... }
Upstream has since dropped that conflict, so the fix is forward rather than a
pin. `composer update symfony/cache --with-all-dependencies` moves three
packages, all within their current major:
symfony/cache v7.4.14 -> v7.4.19
symfony/service-contracts v3.7.1 -> v3.7.3
symfony/var-exporter v7.4.0 -> v7.4.18
v7.4.19 declares no ext-redis conflict at all, and no other package in the
lock declares one either — checked across every entry in `packages` and
`packages-dev`.
composer.json is untouched; only the lock moves.
WHERE THIS WAS FOUND, AND WHY IT MATTERS BEYOND THIS REPO.
Not here. It surfaced in ConductionNL/portaliq, whose PHPUnit matrix installs
integriq as a sibling app (portaliq#564 added it to `additional-apps`). All six
of portaliq's matrix cells die before a single test runs, because the shared
workflow refuses to continue on a half-installed sibling — correctly, since it
would otherwise report integriq's missing classes as portaliq's failures. That
red then reaches every open pull request on portaliq through its merge commit.
This repository's own Code Quality on `development` is red as well.
HOW FAR THIS WAS VERIFIED, stated plainly because one step does not hold.
What is established: the CI log names the conflict and the installed ext-redis
version; the old lock carries symfony/cache v7.4.14 whose own metadata
declares `ext-redis: <6.1`; the new lock carries v7.4.19, whose metadata
declares no such conflict; and no other locked package declares one.
What is NOT established locally: this machine has no ext-redis at all, so a
plain `composer install` here passes on the OLD lock too and proves nothing. I
tried to simulate the runner with `config.platform.ext-redis`, and that test
is not discriminating either — the old lock passes under it as well, so it was
discarded rather than reported as a pass. CI is the verification.
Refs ConductionNL/portaliq#567
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ed (#2041) Prettier 3.9.6 is pinned, so this is unformatted code rather than tooling drift. Every file is exactly what prettier produces from the previous version.
…2043) Directory runs send OCS-APIRequest (the route answered 412 without a requesttoken); REQ-DS-005 pages to a uniquely named row; manifest-pages lists AppConnections and DirectoryRuns; connection-registry asserts on the App select alone.
bbrands02
previously approved these changes
Sep 23, 2026
fix: two unbound interfaces answering 500 on nine routes, and the record corrections from the #1983 review
rjzondervan
previously requested changes
Sep 23, 2026
Automated PR to sync development changes to beta for beta release.
Merging this PR will trigger the beta release workflow.