Skip to content

train: land 23 ready PRs (15 Oct, f) - #3962

Merged
rubenvdlinde merged 83 commits into
developmentfrom
train/2026-10-15-f
Oct 15, 2026
Merged

rubenvdlinde merged 83 commits into
developmentfrom
train/2026-10-15-f

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Merge train of 23 ready PRs, built from the real heads with git merge --no-ff (no squash, no cherry-pick). Landing this PR makes every included head an ancestor of development, so GitHub marks them merged.

Included heads (tip of each stack)

Excluded

Conflict notes

  • lib/Settings/dossiq_register.json info.version: highest seen was 0.20.46 (feat(intake): publishing a case type re-checks the forms other apps bound to it #3909), so the train is 0.20.47. No two PRs changed the same schema object version (d179: 1.19.0 to 1.22.0 and 1.44.0 to 1.45.0; L8 touched other objects). php tools/schema-version-digests.php run once at the end.
  • appinfo/info.xml: version 0.4.47-unstable.20261015100000 (later than every head and development). Repair steps: CarryBagCheckSetting (R2) then ReconcileCaseRecordGrants (L8) last, each once.
  • Catch-return-null ceiling 251 = 251 entries.
  • .debt-baseline.json: development's as base, tightened only: eslint.inlineDisables 15 to 14, phpmd.suppressWarnings 185 to 178, psalm.inlineSuppressions 253 to 251. Not raised.
  • .procedure-code-baseline.json 182: gate 119 measures 182 on this head (development 185/186 before R8's deletions). The measured number is below the baseline development carries, so it is written.
  • l10n: key union, npm run l10n:build run, catalogues in canonical order.
  • Docblock @uses lines of the same tests were added by several PRs and by development (test(ci): the coverage metadata names the classes these tests run #3942/fix(ci): development's risky tests after #3946 and the debt ratchet, without raising it #3952); unioned and deduplicated.
  • Fixes made in the train: @uses lines for 25 test classes that executed unlisted classes (failOnRisky under a coverage driver), and caseCommunicationRecord.spec.js now expects forms in the acknowledgement default channels (d179 added it).

Exit codes (final head b8bc190, before the last small development merge #3958 which merges cleanly)

  • composer lint 0, check:refusal-grounds-snapshot 0, phpcs 0, phpmd (both rulesets) 0, phpstan 0, psalm --threads=1 0 (no errors)
  • phpunit --no-coverage 0 (8803 tests, 1 warning, 6 skipped)
  • phpunit with pcov (before the last two development merges, image since removed): 0 risky tests; 11 errors are zip_unavailable because the container lacks ext-zip (the same tests pass in the local run)
  • vitest --maxWorkers=2 0 (303 files, 3377 tests); npm run lint 0; npm run format 0; test:l10n 0; check:schema-l10n 0; check:l10n-js 0; gate 119 0 (182 against 182)
  • node scripts/debt-ratchet.js exit 1: phpunit.skips is 6 against a baseline of 5. The extra skip is PrivateScopeVerdictDriftTest::setUp from feat(review): a document review is private to the people on its case #3828 (skips when no sibling openregister checkout exists; CI clones it). Left as is, baseline not raised.
  • _rbac: false outside tests: three reads, no writes: Service/Access/ObjectReadGrants::find, Service/Intake/IntakeFormBindingCheck (schema mapper find), Service/Zgw/ZtcTypeNameResolver (searchObjectsPaginated).

Admin and rollout steps named by the included PRs

  • Grants: ReconcileCaseRecordGrants now runs on occ upgrade as the service account; it needs openregister with the grant_manage special right (openregister #4601, merged) and #4600/#4633 (merged).
  • Forms intake: an intake from a Nextcloud Forms form now sends an acknowledgement by default (forms is in the default channels). Switch it off per case type if an organisation does not want it.
  • BAG: the three BAG lookup routes are retired (refactor(bag): the three unused BAG lookup routes leave dossiq #3902). The BAG location check moves to integriq (integriq#2677, merged); CarryBagCheckSetting sets integration.bag.check to on where the old client was on. Afterwards occ config:app:delete dossiq can remove integration.bag.mode, integration.bag.baseUrl, integration.bag.apiKey, integration.bag.source, pdok_bag_endpoint, pdok_bag_source.
  • Deploy integriq with BagObjectReader before this dossiq where the BAG check is on.

documentReview carries the private block. The Woo case type lists it:
the assignee reads and updates every review of the case, a batch
reviewer the reviews of their own batch, supervisors all of them.

Every rule that reads reviews now reads the whole case
(DocumentRelevance::forCase, REQ-CRA-011), so what is outstanding, which
pages are unseen and what a rule may mark do not depend on who asks, and
a marking never makes a second review for a document. The relevance
route, the pages-seen route (ReviewWrites) and the triage apply
reconcile the case when they create a review. A reconcile that cannot
run after a write is logged and answered as failed.

Register 0.20.40, documentReview 1.0.3, Woo case type object 1.4.6.
An administrator runs POST /api/cases/reviewer-access/reconcile once
after the upgrade.
… records

A record made before its schema became private has no grant, so its handler
and reviewers were refused it until an administrator called the reconcile
route. The repair step ReconcileCaseRecordGrants now runs that reconcile at
the end of the upgrade (case-reviewer-access REQ-CRA-012).

- CaseRecordGrantsBackfill walks every case of every declaring case type a
  page at a time and asks CaseReviewerAccess::reconcile() per case. The
  administrator route runs the same walk and is the fallback.
- OpenRegister writes a grant only for a signed-in owner, owning group member
  or administrator, and an upgrade has nobody signed in: the walk runs as an
  administrator account, puts the session back and names the account.
- CaseRecordLookup::rowsOf() reads every page of a case's records, not the
  first thousand.
- A run that could not finish is a warning in the upgrade output and an error
  in the log, never a success.
The access tests over the shipped register decide with dossiq's reading of
PermissionHandler::privateScopeVerdict(). PrivateScopeVerdictDriftTest runs
the real method from the sibling OpenRegister checkout, in a process of its
own, over 5,184 combinations of scope, record, caller, grant and action, and
fails on the first one the model answers differently.
… private to the people on the case

woo-review-triage REQ-WRT-006, the three records of the collection.

- wooSearchPlan, wooExclusion and wooCollectionQuery carry the private block
  (each 1.0.1); the Woo request type lists them with readers: assignee and
  assigneeMay: update (1.4.7); register 0.20.41.
- Their writers reconcile after a create, so the assignee and the supervisors
  hold their grants in the same request.
- Each read is split (case-reviewer-access REQ-CRA-011): what a person is
  shown stays under their own access, and a rule reads the whole case. Was a
  plan recorded, which plan a save changes, the totals of the collection
  report, was this document excluded already, and which documents still hold
  up the decision.
- The upgrade's reconcile step covers the existing records of all three.
- wooRequestConfiguration stays as it is: Q-dossiq-L8-3.
A bound Nextcloud Forms submission writes intakeChannel forms, as
leaf-integrations asks. The case schema's enum did not list it, so the
store refused every such case while the unit tests passed. The value is
added to the case enum and to the case type's acknowledgement channel
list. Case 1.45.0, caseType 1.20.0, register 0.20.39.

The written payload is now validated against the real case schema.
CI runs PHPUnit with a coverage driver and fails a test that executes a
class its coverage metadata does not list.
…chema when it is saved

A case type maps the answers of its intake form in intakeFormMapping
(fields and fixed values). Saving a case type with a bound form runs
OpenRegister's form destination validator on that mapping and the live
case schema, before the case type is stored, and refuses the save on
findings (decision 181). A submission is written through OpenRegister's
form submit with the same mapping; without it the object service saves
the same object.

The service no longer writes a start date. The receipt listener sets it
for every case by one rule (decision 218). REQ-LEAF-103 is amended.

caseType 1.21.0, register 0.20.40, 11 l10n keys.
…t what must be answered before a case exists

A case type's intakeRequirements.requiredBeforeCreation is enforced on
every case create and the case schema does not know it. A bound form
that fills none of a listed property opens no case. The binding check
now adds a required-unmapped finding for each such property on a case
type that is not a draft, so publishing it, or saving it while
published, is refused. A draft case type is not held to the list.

portaliq bindings are not covered: portaliq development answers nothing
when a destination changes (task 4.3b).
…the first working day sentence is still owed

Task 2.2 of a-request-form-opens-the-case-at-once, checked against
portaliq development 7b7ae1fd. The sentence about the first working day
is not on the portal's confirmation screen (Q-dossiq-d179-2).
…plan when it is sent

A case saved as a draft is not received yet (decision 180), and gets
its case number only on leaving draft (decision 201). The three create
listeners now read the receipt moment: they skip a draft's create and
run on the event OpenRegister sends when the draft is sent. A case
created outside draft is handled on its create, as before.

The case flow still starts for a draft: OpenRegister's flow trigger
object.created does not know the draft status (Q-dossiq-d179-3).
…a failed or capped read is not a run over nothing

The walk took its case types from the shared listing, which answers one
page (5,000) and answers nothing when its read fails. A failed read then
reported every case reconciled over 0 cases. The walk now reads the case
types a page at a time through its own lookup; a failed read stops the
run as an error. REQ-CRA-012 says so, and notes that decision 222 (run as
the background service account) waits on OpenRegister (Q-dossiq-L8-4).
Decision 224. The case type delete check, drc-004 and drc-007 let a request
through when their lookup failed. They now refuse with 503
check-not-completed, name the rule, and log the rule and the object.
drc-003 gives the same answer instead of claiming a duplicate, and the
controller's relation check before a document delete (drc-008a) follows.

Two sites leave the catch-return-null list, ceiling 263 to 261.
# Conflicts:
#	lib/Woo/WooSearchPlans.php
#	tests/Unit/Controller/WooCollectionControllerTest.php
#	tests/Unit/Controller/WooSourcesControllerTest.php
#	tests/Unit/Listener/WooStartFromListenerTest.php
#	tests/Unit/Woo/WooCollectionReportTest.php
#	tests/Unit/Woo/WooGatherAddTest.php
#	tests/Unit/Woo/WooSearchPlansTest.php
… facades

method-decomposition slice 8h. The engine couples to 18 classes against 13
and takes 14 constructor arguments. The design gives it one facade over what
a case declares and one over what a move writes; the engine keeps the order
of the checks.
Characterisation before the StatusTransitionService split (method-decomposition
slice 8h), green on the single class: the order of the five refusals, the
conflict check, the one save of a closing move with the version it read, the
timer after the save, the record that cannot be written back, the free-form
refusals and the shape of the available transitions. Seven mutations of the
engine turn it red.
…fourteen collaborators

method-decomposition slice 8h. StatusTransitionService coupled to 18 classes
against 13 and took 14 constructor arguments. Transitions/CaseDeclarations
reads what a case declares about its status and its moves, and
Transitions/CaseMoveLedger holds what a move writes. The engine keeps the
decision and the order of the checks, takes nine arguments and couples to 12
classes. Both suppressions are gone; phpmd.suppressWarnings 183 to 181.

The public methods and their answers are unchanged. The characterisation
test and the five existing engine tests are green with only their
constructor call changed.
The reports screen gets its second half: the parties of the mail a case
collected, by sender, recipient and domain, with the count of unreadable
headers and the CSV. It is the generic component CaseMailPartiesReport
(decision 182), mounted by the existing reports view, reading GET
/api/cases/{id}/reports/mail-parties. The screen draws only the halves on
offer, the menu entry shows while either report is offered, and ?case=
fills in the case. Writes the 5.1 e2e spec (not run, decision 139).
Appended at the end they fail the catalogue order check (l10n coverage),
which exists so branches do not all conflict on the last line.
# Conflicts:
#	appinfo/info.xml
#	lib/Settings/dossiq_register.json
#	lib/Settings/register.d/81-woo-verzoek.json
#	tests/Unit/Repair/ReconcileCaseRecordGrantsTest.php
CarryBagCheckSetting sets integration.bag.check to on where the old BAG
client was switched on (integration.bag.mode test or live, or
integration.bag.source pdok), only while the key does not exist, so a
second run and an administrator's own choice change nothing. Registered
under post-migration with the version moved; its test finds the step
through appinfo/info.xml.

With the check on, a refusal caused by integriq missing or too old now
says integriq must be installed or updated. Catalogues sorted (test:l10n).
The sync now logs a failed cross-register write at error with the rule,
the resource and the objects (Q-dossiq-L11-2); its own test still read the
old warning lines.
…rs leave ZtcController

Method-decomposition slice 8i. ZtcController carried two class suppressions:
12 public methods against 10, and complexity 79 against 50. The three publish
routes move to ZtcPublishController with handlePublish() and publishedCopy();
the URLs, verbs, rate limits and auth attributes are the same, only the route
names change to ztcPublish#. The datumGeldigheid filter becomes
Zgw/ValidityDateFilter and the ztc-010m name lookup Zgw/ZtcTypeNameResolver.
Bodies are moved unchanged. Both suppressions go; phpmd.suppressWarnings
180 to 178 (stacked on 8h).
# Conflicts:
#	lib/Settings/dossiq_register.json
# Conflicts:
#	appinfo/info.xml
#	lib/Settings/dossiq_register.json
# Conflicts:
#	appinfo/info.xml
#	lib/Settings/dossiq_register.json
# Conflicts:
#	tests/Unit/BackgroundJob/AcknowledgementDispatchJobServiceAccountTest.php
#	tests/Unit/Controller/PortalWooRequestSubmissionKeyTest.php
#	tests/Unit/Controller/WOOAssessmentControllerExtensionNoticeTest.php
#	tests/Unit/Service/AcknowledgementDutyTest.php
#	tests/Unit/Service/Intake/SystemCaseStoreTest.php
#	tests/Unit/Service/Termijn/TermLettersTest.php
#	tests/Unit/Service/TermijnNotificationServiceTest.php
#	tests/Unit/Settings/WorkflowGuardConformanceTest.php
#	tests/Unit/Woo/PortalFiledCaseOpensLikeAStaffCaseTest.php
#	tests/Unit/Woo/WooRequestIntakeReceiveTest.php
#	tests/Unit/Woo/WooRequestIntakeTest.php
#	tests/Unit/Woo/WooWritesMatchTheRealSchemasTest.php
# Conflicts:
#	.debt-baseline.json
#	tests/Unit/Service/Termijn/TermLettersTest.php
@rubenvdlinde
rubenvdlinde merged commit bc3ee33 into development Oct 15, 2026
7 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ 2f22742

Check PHP Vue Security License Tests
lint ✅
phpcs ✅
phpmd ✅
psalm ✅
phpstan ✅
phpmetrics ✅
eslint ✅
stylelint ✅
build ✅
check-manifest ✅
check-vue3-compile ✅
test-l10n ✅
format ✅
check-schema-l10n ✅
check-l10n-js ✅
check-debt-ratchet ❌
check-types-e2e ✅
composer ✅ ✅ 109/109
npm ✅ ✅ 659/659
app:check-code ⏭️
info.xml ✅
REUSE ❌
lockfile sync ✅
PHPUnit ❌ 8802/8802 · 3 skipped (no test failed, the job failed for another reason — see its log)
Newman ⏭️
Playwright ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it locally with npx playwright test, or from the Actions tab on a branch with no open pull request into development.
Hydra gates ❌

Quality workflow — 2026-10-15 18:51 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