Skip to content

Commit 5da3814

Browse files
authored
Merge pull request #376 from ConductionNL/wip/market-gap-specs-2026-07-23
docs(openspec): market-gap wave 2026-07-23 — 7 ff spec changes
2 parents 11fe62b + f785b88 commit 5da3814

48 files changed

Lines changed: 5012 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: conduction
2+
created: 2026-07-23
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Context Brief: bio-compliance-assessment
2+
3+
## What
4+
Extend the existing compliance model with the Dutch government security/privacy stack: **BIO maatregelen register** (measures from the Baseline Informatiebeveiliging Overheid), per-application **BBN level** (BBN1/BBN2/BBN3), **DPIA status** (required/executed/date/document ref), and a reference to the **register van verwerkingen** entry. Per-organisation BIO coverage report extending the existing compliance matrix.
5+
6+
## Why (evidence)
7+
- VNG Softwarecatalogus issues #44 (monitor BIO), #45/#47 (register measures), #46 (BBN), #67 (DPIA), #82 (register van verwerkingen) + 10 IBD-labelled issues.
8+
- 561 compliance requirements in the 301 mapped tenders — the single largest demand theme.
9+
- BIO 2.0 is mandatory for all Dutch government bodies; NIS2/BIO2 transition is active in 2026.
10+
- Specter canonical feature: `bio-compliance-assessment` (must, demand 16).
11+
12+
## Current state (read these specs first)
13+
- `openspec/specs/module-compliance-assessment` — compliance records with evidence files, verified-vs-claimed matrix, per-org coverage report. THIS change extends that model; do not fork a parallel one.
14+
- `openspec/specs/settings-admin-controller` / `repair-init` — schemas live in `lib/Settings/softwarecatalogus_register.json` (OpenAPI 3.0.0) imported via repair step.
15+
- Standards register pages already exist (standards-conformance).
16+
17+
## Scope
18+
IN: new/extended OR schemas (bioMaatregel catalog entries seedable from the published BIO measure list; per-application compliance fields: bbnLevel, dpiaStatus, dpiaDate, dpiaDocumentRef, verwerkingsregisterRef), CRUD via manifest pages where possible, BIO coverage report per organisation (extends compliance matrix), filters (e.g. applications without DPIA at BBN2+), notifications rule for overdue DPIA reviews (declarative OR notification dialect — see softwarecatalog-notifications spec), i18n, tests, docs.
19+
OUT: automated BIO evidence collection, ISMS workflows, NIS2 incident reporting, audit certification flows.
20+
21+
## Design constraints
22+
- ADR-001 OR storage only; schema additions to softwarecatalogus_register.json with schema.org type annotations where applicable.
23+
- Union-merge trap: when editing the register JSON, diff against merge base — naive union merges drop modifications.
24+
- Notification rules use the canonical x-openregister-notifications dialect (gate-18 checks; legacy dialect hard-fails).
25+
- ADR-012 Cn components; ADR-005 i18n NL+EN; ADR-009 tests; ADR-010 docs.
26+
- OpenSpec delta headers MUST be `### Requirement: <name>`.
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
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 | Gegevensoverdracht­beleid |
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 | baseline­informatie­beveiligingoverheid.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

Comments
 (0)