Skip to content

refactor(signing): a decision is signed through filinq, and an unsigned end is shown - #3936

Open
rubenvdlinde wants to merge 6 commits into
developmentfrom
refactor/r2-signing-through-filinq
Open

rubenvdlinde wants to merge 6 commits into
developmentfrom
refactor/r2-signing-through-filinq

Conversation

@rubenvdlinde

@rubenvdlinde rubenvdlinde commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What this does

Decision 221: dossiq stops talking to LibreSign and asks filinq for a signature. Filinq owns signing (its providers, LibreSign among them, its signer rails and its expiry). Decision 182: the dossiq half is named by what it does, a signing request on a document.

  • Service/Signing/DocumentSigningRequester raises filinq's DocumentSigningRequestedEvent by class name (source app dossiq, the decision as subject, its file, the signer's user id, a signature level) and answers whether filinq took it, with filinq's request id. The level comes from the app config key signing.level (SES, AdES or QES; SES when unset). Filinq refuses a level its provider does not offer, and the request then reads "not taken".
  • BeschikkingService::onderteken asks and returns at once:
Situation Answer Decision
filinq takes the request 202 status unchanged, signing.state: requested, filinq's request id
a request is already open 409 signing_in_progress unchanged
filinq not installed, or it does not take the request 503 signing_unavailable unchanged, nothing written
  • Listener/SigningConcludedListener hears filinq's SigningConcludedEvent for dossiq's own requests (registered by name in CrossAppListenerRegistrar, like filinq's document event). A conclusion from filinq's expiry job writes as the background service account.
Filinq concludes Decision
signed status signed; signature with the level, signingRequestId and the signed document as compositeContent.fileId
declined, cancelled, expired signing.state: not-concluded, signing.outcome = filinq's status; status unchanged; the signature can be asked again
a request the decision is not waiting on nothing changes

Decision 242: every conclusion is written as the background service account (BackgroundServiceAccount::runAs()), never as the signed-in signer. With no usable service account nothing is written (no _rbac: false, no admin fallback): the decision still reads signing.state: requested, its nearest state without a write, and an error naming the decision, the signing request and filinq's status is logged so it can be reconciled. Both paths are tested in SigningConcludedListenerTest.
Filinq ends every request it took, at the latest when signing_request_expiry_days (default 30) pass (SigningExpirationJob, hourly), so a decision does not wait without end.

  • AuditPacketBuilder: validatierapport.json now says available: false with the signing request id, because filinq publishes no validation report. Before, the mock adapter vouched valid: true for a report nobody made.
  • Deleted after git grep found no other caller: SigningAdapterInterface, LibresignApiClient, LibresignSigningAdapter, LibresignResultAssembler, LibresignStatusMapper, MockSigningAdapter, the signing binding in BeschikkingAdapterRegistrar (the archival alias stays) and three test files.
  • ZgwDocumentService::storeRaw is deleted: git grep finds no caller in lib/, src/ or another app (its one caller was the deleted LibreSign assembler); its storage tests now use storeBase64.
  • The settings answer signingAvailable / signingHint (filinq enabled) instead of libresignAvailable / libresignHint. Nothing under src/ read the old keys.
  • beschikking schema 1.1.0 to 1.2.0: signing (state, requestId, requestedAt, concludedAt, outcome, level, tspProvider, signatory) and signature.level, signature.signingRequestId; l10n en and nl; digests regenerated.
  • Spec: openspec/changes/signing-through-filinq (REMOVED REQ-LBS-001 to 005, three ADDED requirements). openspec validate --strict: valid.

What it fixes on the way: the old adapter polled LibreSign for a few seconds and then answered "pending" (202) and recorded nothing, so a signature placed later never reached the decision. Without LibreSign, the mock adapter wrote a made-up qualified signature onto the decision.

The R5 freeze, measured

gh pr list -R ConductionNL/dossiq --state open --limit 100 gave 52 open PRs. For each, the changed files were listed (gh pr diff --name-only, and the files API where the diff was too large or empty). The only open, non-draft PR touching BeschikkingService or Beschikking/* is #3726, the release PR merging development into beta, which carries development's own content. So the freeze no longer applies.

Rollout, for an administrator

  1. Install and enable filinq before this lands. Without it a decision cannot be signed (503), and the settings say so (signingHint).
  2. Optional: occ config:app:set dossiq signing.level --value=QES (or AdES) when filinq's provider offers it (filinq's LibreSign provider offers QES only with its libresign_qualified switch on). Unset means SES.
  3. A decision a handler tried to sign in the old LibreSign flow and that answered "pending" was never signed in dossiq; ask for its signature again.

Shared files

Verified

  • Tests first where the behaviour is new: BeschikkingServiceTest (22: waiting state, filinq absent, expired/declined/cancelled shown as not concluded and asked again, a stray conclusion ignored, the audit packet's report), SigningConcludedListenerTest, a CrossAppListenerRegistrarTest row (the real stub event reaches the listener), BeschikkingControllerTest (202, 503, 409).

  • Touched suites (every test file naming a changed class, 19 files): exit 0 (refactor/r2/logs/sig-touched.log), and again after the last fixes (66 tests, exit 0).

  • Stages, one at a time: lint 0, check:refusal-grounds-snapshot 0 (sig-strict.log); phpcs 0 (sig-strict2.log); phpmd exit 2 with only the 2 inherited TermLetters.php:401,425 findings (after moving the conclusion out of BeschikkingService, which had reached phpmd's class complexity ceiling, and naming two registrations by string); phpstan 0 and psalm 0 on f1c7754 (sig-strict3.log).

  • phpunit --no-coverage on c3fd250 (sig-strict2.log): 8615 tests, 1 failure, Service/Money/PayoutServiceTest::testWithdrawAndRaiseAgain (two references equal, line 287); it passes alone on this branch and on refactor(bag): the location check asks integriq, and refuses when it is on and unsure #3903's, and touches nothing here. A time-based flake under load, inherited. The commits after c3fd250 touch the listener, one registrar and the settings controller; their suites pass.

  • test:l10n, check:l10n-js, check:schema-l10n (0 uncovered), prettier on the l10n js: exit 0. openspec validate signing-through-filinq --strict: valid. Gate 119: 179, baseline 179.

  • Merges clean into origin/development 1d3bd74 (git merge-tree); none of its 18 new commits touches these files.

  • Final head d81910f, stages once after the decision 242 change: phpcs 0, phpmd 2 (only the inherited TermLetters.php:401,425), phpstan 0 (sig-strict4.log); psalm 0 and phpunit --no-coverage exit 0, 8617 tests, 82 skipped (sig-strict5.log, rerun after the WSL restart). The PayoutService flake did not recur.

Not verified

  • No live pass with filinq. Recipe: filinq installed with its native provider; approve a beschikking, PATCH /api/beschikkingen/{id}/onderteken with {"tspProvider":"filinq"}: 202 and signing.state: requested; sign in filinq: the decision is signed. Set filinq's signing_request_expiry_days low and run its SigningExpirationJob: the decision shows signing.state: not-concluded, signing.outcome: expired.
  • The signed document is filinq's reference; whether it lands in the case dossier folder is filinq's storage, not checked here.

…ed end is shown

Decision 221. Dossiq raises filinq's DocumentSigningRequestedEvent through
the generic Service/Signing/DocumentSigningRequester (level from
signing.level, SES by default) and hears SigningConcludedEvent in
SigningConcludedListener. onderteken answers 202 and the decision waits with
signing.state = requested; signed moves it to signed, declined, cancelled
and expired (filinq's expiry, 30 days by default) mark it not-concluded and
let it be asked again. Without filinq the answer is 503 and nothing is
written. The audit packet says no validation report is available.

Deleted after git grep found no other caller: SigningAdapterInterface, the
four LibreSign classes, MockSigningAdapter, the signing binding in
BeschikkingAdapterRegistrar and three test files. beschikking schema 1.2.0
(signing), l10n, digests, app version moved. Gate 119 baseline 185 -> 179.
BeschikkingService passed phpmd's class complexity ceiling (50) with the
conclusion in it. Service/Signing/SigningConclusionRecorder now records
filinq's outcome on the decision; SigningConcludedListener calls it.
…at all

Decision 242: SigningConcludedListener records filinq's conclusion through
BackgroundServiceAccount::runAs(), never as the signed-in signer. With no
usable service account nothing is written (no _rbac false, no admin), the
decision still reads signing.state requested, and an error names the
decision and the signing request. Both paths tested.

ZgwDocumentService::storeRaw goes: git grep finds no caller in lib/, src/
or another app (its one caller was the deleted LibreSign assembler); its
storage tests use storeBase64.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ b031154

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 ❌ 8631/8631 · 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 08:04 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ 87a1d9f

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 ❌ 8634/8634 · 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 10:43 UTC

Download the full PDF report from the workflow artifacts.

This branch has not been deployed

No deployments
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