Skip to content

feat(parafering-audit-trail): apply spec#396

Merged
rubenvdlinde merged 1 commit into
developmentfrom
feat/apply-parafering-audit-trail
May 11, 2026
Merged

feat(parafering-audit-trail): apply spec#396
rubenvdlinde merged 1 commit into
developmentfrom
feat/apply-parafering-audit-trail

Conversation

@rubenvdlinde
Copy link
Copy Markdown
Contributor

Summary

Applies the parafering-audit-trail spec: a regulator-grade, append-only audit trail for every parafeerroute lifecycle transition (started, paraferd, terugsturen, advised, route-changed, completed). Manifest-first — listing is served by OpenRegister's auto-exposed CRUD endpoint via a new x-pages index page; only the Archiefwet-aligned export is exposed as an action endpoint.

Changes

  • Schema paraferingAuditEntry in lib/Settings/procest_register.json with 9 properties + SHA-256 tamper-detection hash, plus a manifest x-pages index at /voorstellen/:voorstelId/audit-trail (sorted by timestamp desc).
  • Service lib/Service/Parafering/AuditTrailService.php — record() (with canonical hash) + export() (MDTO 1.0 envelope, 20yr retention for completed, 7yr otherwise) + assertAppendOnly() (rejects UPDATE/DELETE with OCSForbiddenException 'Audit entries are append-only').
  • Event + Listener — ParafeerTransitionEvent dispatched from ParafeerRouteService (startParafering, completeStep, skipStep, addAdhocStep, completed) and ParafeerActieService::recordAction; ParaferingAuditListener writes one entry per event, swallows failures so audit outages never block operational transitions.
  • Validator ParaferingAuditAppendOnlyValidator hooks OR ObjectCreatingEvent/ObjectUpdatingEvent/ObjectDeletingEvent to enforce append-only at the OR pre-save layer.
  • Endpoint GET /api/voorstellen/{id}/audit-trail/export (action, not CRUD) — RBAC: auditors, secretariaat, beheerders, or NC admin.
  • Registry new config key parafering_audit_entry_schema + slug map in SettingsService.
  • Application.php registers the listener + validator.
  • build.json records the apply, deferred tasks documented (i18n + V01-V05 verification per strict watchdog).

Strict watchdog

Validated with php -l (all 10 files clean) + jq (schema/registers/x-pages structure). No i18n keys, no composer check per the 25-minute budget.

Test plan

  • Reset env, install procest, verify paraferingAuditEntry schema appears in the register and config key parafering_audit_entry_schema is auto-populated.
  • Start a voorstel; verify 1 entry with action=started, actorRole=steller.
  • Parafeer two steps; verify 2 more entries with action=paraferd.
  • Terugsturen with reason; verify entry has action=terugsturen, reason populated.
  • Skip step with reason; verify entry has action=route-changed, actorRole=beheerder.
  • Accord final step; verify entry has action=completed, actorRole=accorderend.
  • PUT/DELETE on /api/objects/{register}/paraferingAuditEntry/{id} returns 403 'Audit entries are append-only'.
  • GET /api/voorstellen/{id}/audit-trail/export as auditor returns 200 with metadata.schema=MDTO 1.0 and retentionUntil = completed.timestamp + 20yr.
  • As non-auditor returns 403 'Audit export requires auditor role'.

Manifest-first implementation of the regulator-grade, append-only audit
trail for the parafeerroute lifecycle.

- Schema: add paraferingAuditEntry to procest_register.json (action,
  actor, actorRole, timestamp, reason, contentSnapshot, ipAddress,
  auditEntryHash) + register slug list + new x-pages index page at
  /voorstellen/:voorstelId/audit-trail (sorted by timestamp desc).
- Service: lib/Service/Parafering/AuditTrailService.php — record() builds
  the canonical entry + SHA-256 hash and persists via ObjectService;
  export() returns the MDTO 1.0 envelope with 20yr/7yr retention;
  assertAppendOnly() rejects UPDATE/DELETE with OCSForbiddenException
  "Audit entries are append-only".
- Domain event: lib/Event/ParafeerTransitionEvent.php — emitted by
  ParafeerRouteService (startParafering, completeStep, skipStep,
  addAdhocStep, completed) and ParafeerActieService.recordAction.
- Listener: lib/Listener/ParaferingAuditListener.php subscribes to the
  event and persists one entry per transition; failures are swallowed
  so audit outages never block operational transitions.
- Validator: lib/Validator/ParaferingAuditAppendOnlyValidator.php hooks
  OR pre-save (ObjectCreating/Updating/Deleting) events to enforce
  append-only on the audit schema.
- Endpoint: GET /api/voorstellen/{id}/audit-trail/export (action, not
  CRUD) — RBAC via auditors/secretariaat/beheerders groups; returns
  metadata + entries envelope.
- Registry: SettingsService gets the parafering_audit_entry_schema
  config key and slug→key mapping.

CRUD listing is served by OpenRegister's auto-exposed
/api/objects/<register>/<schema> endpoint via the manifest index page;
no bespoke CRUD controller introduced.
@rubenvdlinde rubenvdlinde force-pushed the feat/apply-parafering-audit-trail branch from 7dce54d to 44cb3b5 Compare May 11, 2026 12:17
@rubenvdlinde rubenvdlinde merged commit 7ac5a98 into development May 11, 2026
12 of 15 checks passed
@rubenvdlinde rubenvdlinde deleted the feat/apply-parafering-audit-trail branch May 11, 2026 12:18
@github-actions
Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/procest @ e29a3ea

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 100/100
npm ✅ 419/419
PHPUnit ⏭️
Newman ⏭️
Playwright

Spec coverage: 3% (21 tests / 673 specs)


Quality workflow — 2026-05-11 12:22 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