Skip to content

feat(call-log): a call can be recorded without any body, and a BRP relation read is (decision 229) - #2669

Merged
rubenvdlinde merged 1 commit into
developmentfrom
feat/r2-call-log-without-bodies
Oct 12, 2026
Merged

rubenvdlinde merged 1 commit into
developmentfrom
feat/r2-call-log-without-bodies

Conversation

@rubenvdlinde

@rubenvdlinde rubenvdlinde commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

What this does

Decision 229 (Ruben, 12 Oct): a BRP relation read leaves a call log record without bodies. A municipality must be able to show who consulted the BRP. This follows #2667, where the read stayed out of the call log completely.

  • CallService::call takes withholdBodies. It is an option on the one call, set in code by the caller. There is no app setting, source field or request key that switches it off.
  • A call made with it is written as one call log record. It names the source, the direction, the status, the moment and the Nextcloud user in whose session the call ran. Of the request it keeps the method and the URL without its query string. Of the response it keeps the status, the time, the size and the encoding.
  • It holds no request body, no response body, no headers, no query and no replay request. That holds inside an open investigation window, and when the same call also passes logBody.
  • The execution trace step of the call gets the same stripped request and response.
  • The caller still receives the whole answer.
  • RegistrySourceGateway::readUnrecorded() is now readWithoutBodies(). BrpRelationReader reads through it, so a relation read shows on the source's call log page.

How it is tested

  • CallServiceWithheldBodiesTest: the source has an open investigation window and carries a number in its request body and its query. The stored record is encoded as one string and searched for the asked number, the answered number and the word burgerservicenummer. A success, a failure, a buffered record, logBody on the same call and the trace step all hold nothing. A control test makes the same call without the option and finds both numbers, so the search can fail.
  • BrpRelationReadCallLogTest: the real reader over the real gateway over the real call service. Only the HTTP client, the object store and the session are doubles. The answer is the mock answer the seeded source ships. Exactly one row is written, to call_log. It names the user and the source, and the string search finds none of the numbers in the shipped answer, on an answered read and on a refused one.
  • The value cache: it lives in PropertySourceResolver. A test asserts that no class on the read's path (reader, gateway, call service, body policy) is handed the resolver, a cache or a cache factory, with the resolver itself as the control.
  • The session reaches the call service through typed constructor parameters (BodyCapturePolicy takes IUserSession). A test asserts both types, because a missing session would write the record without a user, in silence.

What an administrator sees

  • A relation read is one row on the BRP source's call log page: who, when, status. No body, also during an investigation.
  • A failed relation read cannot be replayed from the call log. Nothing of its request is kept.
  • A read that runs outside a user session, such as a background job, is written without a user.
  • A read refused before it is sent (source disabled, rate limit) is written as before, without a user. It never held a body.

Limits to know

  • The URL path is recorded. A caller that uses the option must send its identifiers in the body, as the relation read does. The query string is not recorded.
  • userId already existed on call_log. This is its first writer, and only on a record that withholds its bodies.
  • No schema, route or version change.

The BRP authorisation text, corrected

#2667 named the BRP elements from memory. They are checked now against an autorisatietabel RvIG publishes (https://publicaties.rvig.nl/media/13550/download, read 12 October 2026). It lists Burgerservicenummer as 02.01.20 under category 02 Ouder1, 03.01.20 under category 03 Ouder2, 05.01.20 under category 05 Huwelijk/geregistreerd partnerschap and 09.01.20 under category 09 Kind. The numbers in #2667 were right. openspec/changes/brp-relations-read/proposal.md and the body of #2667 now carry the full rubric numbers and the source. The municipality's own autorisatiebesluit stays the authority on what it may ask.

Verified

  • Red first: refactor/r2/logs/c1-red.log (exit 2, 6 errors, the option did not exist; the control passed).
  • Touched suites, 262 tests, exit 0 (c1-green3.log): tests/Unit/PropertySource, tests/Unit/Outbound, the three CallService test files.
  • composer check:strict, run stage by stage in the foreground because no build slot came free in 12 minutes (c1-strict.log): check:no-legacy-types, check:routes, lint, phpcs, phpmd and phpstan exit 0 each. psalm exit 0. The full PHPUnit suite exits 2: 5,475 tests, 11 errors, all in tests/Unit/AppInfo and all the same TypeError at tests/Helpers/AppContainerInjection.php:93, known on origin/development on this machine since 11 October and untouched by this pull request. CI read once on e89c3f2: 39 pass, 8 skipping, 0 fail.
  • phpmd found two new findings on the first run (a tenth constructor parameter and a method over 100 lines). Both are fixed in this pull request, not baselined.

Not verified

  • No live call. The live pass is a task in openspec/changes/call-log-without-bodies/tasks.md: one relation read on an instance, then open the source's call log page and check the row.

Rollout

Nothing to do on upgrade. Dossiq's check (ConductionNL/dossiq#3844) calls BrpRelationReader::read by name and needs no change. After both are on an instance, do the live pass above.

…lation read is (decision 229)

CallService::call takes withholdBodies, a per-call option set in code. The
record names the source, the asking user, the moment and the outcome, and
holds no request body, response body, headers, query or replay request, also
inside an open investigation window. BrpRelationReader reads through it.

The BRP element numbers in brp-relations-read are checked against an
autorisatietabel RvIG publishes and now carry the full rubric numbers.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/integriq @ b7a7c12

Check PHP Vue Security License Tests
lint ✅
phpcs ✅
phpmd ✅
psalm ✅
phpstan ✅
phpmetrics ✅
eslint ✅
stylelint ✅
build ✅
build ✅
check-specs ✅
test-l10n ✅
format ✅
check-schema-l10n ✅
check-l10n-js ✅
composer ✅ ✅ 143/143
npm ✅ ✅ 647/647
app:check-code ⏭️
info.xml ✅
REUSE ❌
lockfile sync ✅
PHPUnit ✅ 5474/5474 · 1 skipped
Newman ✅ 130/130
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 ✅
Hydra gates (axe) ✅

Quality workflow — 2026-10-12 16:47 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit 3b20303 into development Oct 12, 2026
47 checks passed
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