|
| 1 | +# Design: bio-compliance-assessment |
| 2 | + |
| 3 | +## Architecture Overview |
| 4 | +This change extends the existing `module-compliance-assessment` |
| 5 | +architecture rather than adding a new one: |
| 6 | + |
| 7 | +``` |
| 8 | +module ──────────────┬── compliancy ──── standaardversie (element, gemmaType=standaardversie) |
| 9 | + (bbnLevel, │ (existing) |
| 10 | + dpiaStatus, └── compliancy ──── bioMaatregel (NEW: parallel relation) |
| 11 | + dpiaDate, (evidence: bewijs/bewijsReferentie/url — reused) |
| 12 | + dpiaVolgendeBeoordeling, |
| 13 | + dpiaDocumentRef, |
| 14 | + verwerkingsregisterRef) |
| 15 | +``` |
| 16 | + |
| 17 | +`compliancy` keeps its single-record shape and gains one optional |
| 18 | +relation (`bioMaatregel`) alongside the existing `standaardversie` / |
| 19 | +`standaardGemma` pair. A record links a module to a standard version OR |
| 20 | +a BIO measure — never a third parallel schema. The matrix/coverage code |
| 21 | +(`src/utils/complianceMatrix.js`) already treats "column key" as a |
| 22 | +parameter; this change generalises it to accept either relation family |
| 23 | +instead of forking a second mapper. |
| 24 | + |
| 25 | +BBN level, DPIA status, and the verwerkingsregister reference are |
| 26 | +application-level attributes (they describe the module as a whole, not a |
| 27 | +specific measure), so they live directly on `module`, following the same |
| 28 | +placement as other application-level fields (`hostingJurisdictie`, |
| 29 | +`licentietype`). |
| 30 | + |
| 31 | +## Goals / Non-Goals |
| 32 | + |
| 33 | +**Goals:** |
| 34 | +- Reuse the `compliancy` verified/claimed/evidence model for BIO measure |
| 35 | + compliance instead of forking a parallel assessment object. |
| 36 | +- Make BBN level, DPIA status, and DPIA review due-dates filterable and |
| 37 | + reportable per organisation. |
| 38 | +- Ship a working, declarative overdue-DPIA notification using the |
| 39 | + canonical `x-openregister-notifications` dialect. |
| 40 | + |
| 41 | +**Non-Goals:** |
| 42 | +- Computing BBN level or DPIA requirement automatically from other data |
| 43 | + (e.g. deriving BBN from `hostingJurisdictie`) — both are user-entered. |
| 44 | +- A generic "review cadence" engine — `dpiaVolgendeBeoordeling` is a |
| 45 | + single user-set date field, not a recurring-schedule primitive. |
| 46 | +- Modelling the register van verwerkingen itself. |
| 47 | + |
| 48 | +## Decisions |
| 49 | + |
| 50 | +### Decision 1: Extend `compliancy` with a `bioMaatregel` relation, not a new `bioCompliancy` schema |
| 51 | +**Why:** The context brief and the `module-compliance-assessment` spec |
| 52 | +are explicit: this change extends that model, it does not fork a |
| 53 | +parallel one. `compliancy` already carries the verified/claimed logic, |
| 54 | +the evidence fields (`bewijs`, `bewijsReferentie`, `url`), and the |
| 55 | +matrix/coverage machinery. Adding a `bioMaatregel` relation (mirroring |
| 56 | +`standaardversie`) reuses all of it for zero new UI code paths beyond |
| 57 | +column-source selection. |
| 58 | +**Alternatives considered:** a dedicated `bioAssessment` schema — |
| 59 | +rejected, duplicates `compliancy`'s evidence/verified-claimed shape and |
| 60 | +would require a second matrix mapper, directly contradicting the "do not |
| 61 | +fork a parallel model" instruction. |
| 62 | + |
| 63 | +### Decision 2: BBN/DPIA fields live on `module`, not on a per-measure record |
| 64 | +**Why:** BBN level and DPIA status describe the application as a whole |
| 65 | +("this application is classified BBN2 and had a DPIA executed on |
| 66 | +2026-03-01"), not a per-standard or per-measure claim. Placing them on |
| 67 | +`compliancy` would force one BBN/DPIA value per compliance record, which |
| 68 | +is meaningless — an application has exactly one BBN level and one DPIA |
| 69 | +status, independent of how many BIO measures or standards it claims. |
| 70 | +**Alternatives considered:** a `bioBeoordeling` header record wrapping |
| 71 | +BBN/DPIA plus a list of measure claims — rejected as unnecessary |
| 72 | +indirection; `module` already is the one-per-application anchor object |
| 73 | +every other per-application attribute (licence, hosting) hangs off. |
| 74 | + |
| 75 | +### Decision 3: DPIA overdue notification uses a stored `dpiaVolgendeBeoordeling` date + `scheduled`/`withinNext`, not a computed field |
| 76 | +**Why:** Per ADR-031, "overdue" detection should default to declarative |
| 77 | +schema metadata. `x-openregister-calculations` can derive an `isOverdue` |
| 78 | +boolean at read time, but `x-openregister-notifications`' `scheduled` |
| 79 | +trigger filters on **stored** object data — a calculated field computed |
| 80 | +at read time is not evaluated during a scheduled sweep. A stored |
| 81 | +`dpiaVolgendeBeoordeling` date (set by the user/vendor when a DPIA is |
| 82 | +executed) lets the rule reuse the exact filter shape already proven |
| 83 | +working in this register: `contract`'s `contract-expiry` and |
| 84 | +`gebruik`'s `phaseout-approaching` both use `scheduled` + |
| 85 | +`{ "operator": "withinNext", "value": "P<n>D" }`. `withinNext` with a |
| 86 | +0-day window (`"P0D"`) reads as "due on or before today" — i.e. due |
| 87 | +today or already overdue — using the exact same operator family instead |
| 88 | +of introducing an unproven "before"/"past-due" operator this register |
| 89 | +has never used. |
| 90 | +**Alternatives considered:** (a) an `x-openregister-calculations` |
| 91 | +`dpiaOverdue` boolean + `calculatedChange` trigger — rejected because |
| 92 | +`calculatedChange` only watches NUMERIC calculated fields for boundary |
| 93 | +crossings, and a boolean derived at read time has no natural "change |
| 94 | +event" to watch under a scheduled sweep; (b) a bespoke PHP background |
| 95 | +job walking modules daily — rejected per ADR-031 (a scheduled sweep over |
| 96 | +stored fields is exactly what the declarative engine already does; a |
| 97 | +custom job would be the anti-pattern the ADR calls out). |
| 98 | +**Declarative-vs-imperative decision (ADR-031):** declarative — no new |
| 99 | +PHP service or job is introduced by this change. |
| 100 | + |
| 101 | +### Decision 4: BIO coverage report extends `ComplianceMatrixView`, not a new page |
| 102 | +**Why:** The context brief frames this explicitly as "extends the |
| 103 | +existing compliance matrix." `ComplianceMatrixView` is already a |
| 104 | +`type: custom` manifest page whose whole reason for existing is that no |
| 105 | +index/detail archetype can express a runtime-selected two-dimensional |
| 106 | +grid. Adding a BIO scope/tab to the same component (module rows × |
| 107 | +BIO-measure columns, plus a BBN/DPIA summary strip) reuses the filter- |
| 108 | +first, URL-shareable-selection pattern instead of duplicating it. |
| 109 | +**Alternatives considered:** a standalone `BioCoverageReportView` — |
| 110 | +rejected as an unnecessary fork of the same filter-first |
| 111 | +matrix/shareable-URL pattern; a toggle within the existing view is a |
| 112 | +smaller diff and keeps one canonical "compliance view" surface. |
| 113 | + |
| 114 | +## Risks / Trade-offs |
| 115 | +- [Risk] `dpiaVolgendeBeoordeling` is user-entered, not computed from a |
| 116 | + fixed BIO/AVG review interval → some vendors may leave it blank, |
| 117 | + silencing the overdue notification for that application. → |
| 118 | + **Mitigation:** the "applications without a DPIA at BBN2+" filter |
| 119 | + (in scope) surfaces missing DPIA data independently of the |
| 120 | + notification, so the gap stays visible in the catalog UI even when |
| 121 | + the notification is silent. |
| 122 | +- [Risk] `withinNext P0D` semantics assume the OpenRegister notification |
| 123 | + engine's `withinNext` operator is boundary-inclusive of past dates |
| 124 | + (i.e. `date <= now`), matching how `contract-expiry` and |
| 125 | + `phaseout-approaching` are already deployed in this register. → |
| 126 | + **Mitigation:** this reuses the exact operator already live in |
| 127 | + production rules in this same file; no new engine behaviour is |
| 128 | + assumed. If verified otherwise during implementation, the fallback is |
| 129 | + a small positive window (e.g. `"P1D"`) — documented here so the |
| 130 | + builder does not need to guess. |
| 131 | +- [Risk] Extending `compliancy` with a second optional relation |
| 132 | + (`bioMaatregel`) means a record could theoretically carry both |
| 133 | + `standaardversie` and `bioMaatregel` set. → **Mitigation:** the spec |
| 134 | + requires records to link to exactly one of the two; UI form |
| 135 | + validation and the matrix mapper both treat "both set" as a data |
| 136 | + -quality issue to flag, not a new dual-purpose record type. |
| 137 | + |
| 138 | +## Migration Plan |
| 139 | +No Nextcloud DB migration class is introduced — per ADR-001 this app |
| 140 | +owns no custom database tables. Schema changes are register-JSON patches |
| 141 | +to `lib/Settings/softwarecatalogus_register.json`, applied the same way |
| 142 | +every prior schema change in this app was: `ConfigurationService::importFromApp()` |
| 143 | +re-imports the register on the existing `InitializeSettings` repair step |
| 144 | +(see `repair-init`), so the change ships and self-applies on the next |
| 145 | +app upgrade — no separate `migration.md` artefact is produced for this |
| 146 | +change (see Notes below). |
| 147 | + |
| 148 | +## Open Questions |
| 149 | +- Should `bbnLevel` be `facetable: true` from day one (needed for the |
| 150 | + "applications without a DPIA at BBN2+" filter) — yes, confirmed in |
| 151 | + the spec; noted here so the register patch does not miss it. |
| 152 | +- Fixed BIO/AVG review interval for a default `dpiaVolgendeBeoordeling` |
| 153 | + — deferred to DEFERRED_QUESTIONS; out of scope for this change. |
| 154 | + |
| 155 | +## Nextcloud Integration |
| 156 | +- Controllers: none new — this app queries OpenRegister's object API |
| 157 | + directly from the frontend (no bespoke CRUD controller), per the |
| 158 | + existing `settings-admin-controller` pattern. |
| 159 | +- Services: none new — see Decision 3; the overdue-DPIA behaviour is |
| 160 | + entirely declarative schema metadata, not a service class. |
| 161 | +- Mappers/Entities: none — OpenRegister owns storage; `compliancy` and |
| 162 | + `module` remain the only entities involved. |
| 163 | +- Events/Hooks: the existing `InitializeSettings` repair step (see |
| 164 | + `repair-init`) re-imports the extended register on upgrade; no new |
| 165 | + hook is added. |
| 166 | + |
| 167 | +## Security Considerations |
| 168 | +No new authorization surface. `bioMaatregel` is a read-mostly reference |
| 169 | +catalog (`authorization.read: ["public"]`, matching `element`); write |
| 170 | +access follows the existing `softwarecatalog-admins`/manage-ACL pattern |
| 171 | +already used by `compliancy` and `module`. The new `module` fields |
| 172 | +(`bbnLevel`, `dpiaStatus`, `dpiaDate`, `dpiaVolgendeBeoordeling`, |
| 173 | +`dpiaDocumentRef`, `verwerkingsregisterRef`) inherit `module`'s existing |
| 174 | +field-level authorization — no new read/write scope is introduced. |
| 175 | +`dpiaDocumentRef` follows the established `bewijsReferentie` pattern |
| 176 | +(link via NC Files, not store the file) so no new file-handling |
| 177 | +authorization path is created. |
| 178 | + |
| 179 | +## NL Design System |
| 180 | +New fields render through the existing `CnFormDialog`/`data`-widget |
| 181 | +patterns already used for `module` and `compliancy` (see the manifest |
| 182 | +`_note`s on `KompliantieDetail` / `ModuleDetail`); no new form or table |
| 183 | +component is introduced. The BIO coverage report reuses |
| 184 | +`ComplianceMatrixView`'s existing tri-state cell styling (verified / |
| 185 | +claimed / none) via NL Design System CSS variables — no hardcoded |
| 186 | +colors. |
| 187 | + |
| 188 | +## File Structure |
| 189 | +``` |
| 190 | +lib/ |
| 191 | + Settings/ |
| 192 | + softwarecatalogus_register.json # bioMaatregel schema; compliancy + module extensions; notification rule |
| 193 | +src/ |
| 194 | + manifest.json # bioMaatregel catalog pages; module form fields; matrix BIO scope; filters |
| 195 | + views/ |
| 196 | + ComplianceMatrixView.vue # extended with a BIO scope/tab |
| 197 | + utils/ |
| 198 | + complianceMatrix.js # generalised to key on bioMaatregel alongside standaardversie |
| 199 | + l10n/ |
| 200 | + nl.json / en.json (or equivalent) # new field labels, filter labels, notification subjects |
| 201 | +tests/ |
| 202 | + Unit/ # register import / schema validation, matrix mapper unit tests |
| 203 | +``` |
| 204 | + |
| 205 | +## Seed Data |
| 206 | + |
| 207 | +### Schema: `bioMaatregel` |
| 208 | +| Field | Object 1 | Object 2 | Object 3 | Object 4 | Object 5 | |
| 209 | +|-------|----------|----------|----------|----------|----------| |
| 210 | +| slug | `bio-5-1-1` | `bio-9-2-1` | `bio-10-1-1` | `bio-12-1-1` | `bio-13-2-1` | |
| 211 | +| code | 5.1.1 | 9.2.1 | 10.1.1 | 12.1.1 | 13.2.1 | |
| 212 | +| naam | Toegangsbeveiligingsbeleid | Beheer van gebruikerstoegang | Cryptografisch beleid | Netwerkbeveiligingsbeheer | Gegevensoverdrachtbeleid | |
| 213 | +| thema | Toegangsbeveiliging | Toegangsbeveiliging | Cryptografie | Communicatiebeveiliging | Communicatiebeveiliging | |
| 214 | +| bioVersie | BIO 2.0 | BIO 2.0 | BIO 2.0 | BIO 2.0 | BIO 2.0 | |
| 215 | +| bbnNiveau | [BBN1, BBN2, BBN3] | [BBN2, BBN3] | [BBN2, BBN3] | [BBN1, BBN2, BBN3] | [BBN2, BBN3] | |
| 216 | +| bron | baselineinformatiebeveiligingoverheid.nl | (same) | (same) | (same) | (same) | |
| 217 | + |
| 218 | +**Related items per object:** none (reference catalog entries; no |
| 219 | +files/notes/tasks/contacts attached). |
| 220 | + |
| 221 | +### Schema: `module` (existing objects gain new field values — no new seed objects) |
| 222 | +Existing seed `module` objects (from `module-compliance-assessment`) |
| 223 | +gain example values for the new fields on 3 of the existing records: |
| 224 | +one BBN2 application with an executed DPIA and evidence document, one |
| 225 | +BBN3 application with a required-but-not-yet-executed DPIA (for the |
| 226 | +"without a DPIA at BBN2+" filter demo), and one BBN1 application with |
| 227 | +DPIA not applicable. |
| 228 | + |
| 229 | +## Trade-offs |
| 230 | +Reusing `compliancy` for BIO measures (Decision 1) means the schema now |
| 231 | +serves two conceptually distinct catalogs (GEMMA standards, BIO |
| 232 | +measures) through one relation-pair shape. This is a deliberate |
| 233 | +trade-off: it costs a small amount of schema ambiguity (two optional |
| 234 | +relations, "exactly one populated") in exchange for zero duplicated |
| 235 | +evidence/verified-claimed logic and one matrix mapper instead of two — |
| 236 | +judged worthwhile given the explicit "do not fork a parallel model" |
| 237 | +constraint. |
0 commit comments