Skip to content

Commit 5d4f911

Browse files
authored
[change request] softwarecatalog notifications (#302)
* docs(notifications): change request for softwarecatalog schema notifications * feat(notifications): add x-openregister-notifications to softwarecatalog schemas
1 parent 57f2c55 commit 5d4f911

5 files changed

Lines changed: 292 additions & 0 deletions

File tree

lib/Settings/softwarecatalogus_register.json

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,6 +1535,21 @@
15351535
"kwetsbaarheid": {
15361536
"uri": null,
15371537
"slug": "kwetsbaarheid",
1538+
"x-openregister-notifications": {
1539+
"vulnerability-reported": {
1540+
"trigger": {"type": "created"},
1541+
"enabled": true,
1542+
"channels": ["nc-notification", "email"],
1543+
"recipients": [
1544+
{"kind": "groups", "groups": ["softwarecatalog-admins"]},
1545+
{"kind": "object-acl", "permission": "manage"}
1546+
],
1547+
"subject": {
1548+
"nl": "Kwetsbaarheid gemeld: {{naam}} ({{cveCode}}, CVSS {{cvssScore}})",
1549+
"en": "Vulnerability reported: {{naam}} ({{cveCode}}, CVSS {{cvssScore}})"
1550+
}
1551+
}
1552+
},
15381553
"title": "Kwetsbaarheid",
15391554
"description": "Schema voor kwetsbaarheden. Dit schema is onderdeel van het vastgestelde datamodel maar wordt niet daadwerkelijk in de applicatie gebruikt.",
15401555
"version": "1.0.19",
@@ -3119,6 +3134,21 @@
31193134
"contract": {
31203135
"uri": null,
31213136
"slug": "contract",
3137+
"x-openregister-notifications": {
3138+
"contract-expiry": {
3139+
"trigger": {"type": "scheduled", "intervalSec": 86400, "filter": {"status": {"op": "equals", "value": "Actief"}}},
3140+
"enabled": false,
3141+
"channels": ["nc-notification", "email"],
3142+
"recipients": [
3143+
{"kind": "groups", "groups": ["softwarecatalog-admins"]},
3144+
{"kind": "object-acl", "permission": "manage"}
3145+
],
3146+
"subject": {
3147+
"nl": "Contract verloopt: {{contractNummer}} (einddatum {{eindDatum}})",
3148+
"en": "Contract expiring: {{contractNummer}} (end date {{eindDatum}})"
3149+
}
3150+
}
3151+
},
31223152
"title": "Contract",
31233153
"description": "Een formele overeenkomst voor het inzetten van een Dienst op een Gebruik. Dit schema is onderdeel van het vastgestelde datamodel maar wordt niet daadwerkelijk in de applicatie gebruikt.",
31243154
"version": "0.1.0",
@@ -3751,6 +3781,21 @@
37513781
"beoordeeling": {
37523782
"uri": null,
37533783
"slug": "beoordeeling",
3784+
"x-openregister-notifications": {
3785+
"review-submitted": {
3786+
"trigger": {"type": "created"},
3787+
"enabled": true,
3788+
"channels": ["nc-notification"],
3789+
"recipients": [
3790+
{"kind": "object-acl", "permission": "manage"},
3791+
{"kind": "groups", "groups": ["softwarecatalog-admins"]}
3792+
],
3793+
"subject": {
3794+
"nl": "Nieuwe beoordeling: {{naam}} (waardering {{waardering}})",
3795+
"en": "New review: {{naam}} (rating {{waardering}})"
3796+
}
3797+
}
3798+
},
37543799
"title": "Beoordeeling",
37553800
"description": "Schema voor beoordelingen en waarderingen van applicaties en diensten. Dit schema is onderdeel van het vastgestelde datamodel maar wordt niet daadwerkelijk in de applicatie gebruikt.",
37563801
"version": "0.1.2",
@@ -7227,6 +7272,21 @@
72277272
"moduleVersie": {
72287273
"uri": null,
72297274
"slug": "moduleVersie",
7275+
"x-openregister-notifications": {
7276+
"module-version-published": {
7277+
"trigger": {"type": "created"},
7278+
"enabled": true,
7279+
"channels": ["nc-notification"],
7280+
"recipients": [
7281+
{"kind": "object-acl", "permission": "manage"},
7282+
{"kind": "groups", "groups": ["softwarecatalog-admins"]}
7283+
],
7284+
"subject": {
7285+
"nl": "Nieuwe moduleversie: {{versie}}",
7286+
"en": "New module version: {{versie}}"
7287+
}
7288+
}
7289+
},
72307290
"title": "Applicatieversie",
72317291
"description": "Schema voor applicatieversies",
72327292
"version": "0.1.0",
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-05-26
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
---
2+
kind: config
3+
depends_on: []
4+
---
5+
6+
# softwarecatalog — schema-declared notifications
7+
8+
## Why
9+
10+
Softwarecatalog is the GEMMA software catalogue used by municipal architects,
11+
VNG, and suppliers. The headline notification needs are: **a reported
12+
vulnerability** (urgent — suppliers + catalogue admins), **contract expiry**
13+
(scheduled reminder), **a new module version**, and **a new review**. None of
14+
softwarecatalog's schemas currently declare `x-openregister-notifications`, so the
15+
OpenRegister notification engine has nothing to dispatch on. This change declares
16+
schema-level notification rules for those four events.
17+
18+
All rules use trigger types that work **today** (`created`, `scheduled`). No rule
19+
depends on the unshipped `updated`-field-change engine condition, so this change
20+
carries **no** `depends_on`.
21+
22+
> The register file is `lib/Settings/softwarecatalogus_register.json` (note the
23+
> `-us` suffix — it does **not** match the app slug).
24+
25+
## What Changes
26+
27+
Add a top-level `x-openregister-notifications` key to the relevant schemas in
28+
`lib/Settings/softwarecatalogus_register.json`, using the verified engine dialect.
29+
30+
### `kwetsbaarheid` (vulnerability) — reported (created) — URGENT
31+
32+
`kwetsbaarheid` carries `naam`, `cveCode`, `cvssScore`, and `modules` (array of
33+
module references). There is **no** direct supplier/owner field on the schema
34+
(see Caveats), so recipients use a catalogue-admin `groups` recipient plus the
35+
record's manage-ACL.
36+
37+
```jsonc
38+
"x-openregister-notifications": {
39+
"vulnerability-reported": {
40+
"trigger": {"type": "created"},
41+
"enabled": true,
42+
"channels": ["nc-notification", "email"],
43+
"recipients": [
44+
{"kind": "groups", "groups": ["softwarecatalog-admins"]},
45+
{"kind": "object-acl", "permission": "manage"}
46+
],
47+
"subject": {
48+
"nl": "Kwetsbaarheid gemeld: {{naam}} ({{cveCode}}, CVSS {{cvssScore}})",
49+
"en": "Vulnerability reported: {{naam}} ({{cveCode}}, CVSS {{cvssScore}})"
50+
}
51+
}
52+
}
53+
```
54+
55+
### `contract` — expiry reminder (scheduled)
56+
57+
`contract` carries `eindDatum` (end date), `status` (enum: `Actief` / `Verlopen` /
58+
`In onderhandeling`), and nested `contactpersoonAanbieder` /
59+
`contactpersoonGebruiker` objects. A `scheduled` rule periodically checks active
60+
contracts whose `eindDatum` is approaching.
61+
62+
```jsonc
63+
"x-openregister-notifications": {
64+
"contract-expiry": {
65+
"trigger": {"type": "scheduled", "intervalSec": 86400, "filter": {"status": {"op": "equals", "value": "Actief"}}},
66+
"enabled": false,
67+
"channels": ["nc-notification", "email"],
68+
"recipients": [
69+
{"kind": "groups", "groups": ["softwarecatalog-admins"]},
70+
{"kind": "object-acl", "permission": "manage"}
71+
],
72+
"subject": {
73+
"nl": "Contract verloopt: {{contractNummer}} (einddatum {{eindDatum}})",
74+
"en": "Contract expiring: {{contractNummer}} (end date {{eindDatum}})"
75+
}
76+
}
77+
}
78+
```
79+
80+
Ships **disabled by default** because the `scheduled` filter needs a
81+
date-window comparison on `eindDatum` (e.g. "within 30 days") whose engine
82+
support must be confirmed; see Caveats.
83+
84+
### `moduleVersie` (module version) — published (created)
85+
86+
`moduleVersie` carries `module`, `versie`, `status`, and `geregistreerdDoor`. A
87+
new version row is a `created` event.
88+
89+
```jsonc
90+
"x-openregister-notifications": {
91+
"module-version-published": {
92+
"trigger": {"type": "created"},
93+
"enabled": true,
94+
"channels": ["nc-notification"],
95+
"recipients": [
96+
{"kind": "object-acl", "permission": "manage"},
97+
{"kind": "groups", "groups": ["softwarecatalog-admins"]}
98+
],
99+
"subject": {
100+
"nl": "Nieuwe moduleversie: {{versie}}",
101+
"en": "New module version: {{versie}}"
102+
}
103+
}
104+
}
105+
```
106+
107+
### `beoordeeling` (review) — submitted (created)
108+
109+
`beoordeeling` carries `naam`, `waardering` (rating), and `modules`. A new review
110+
is a `created` event.
111+
112+
```jsonc
113+
"x-openregister-notifications": {
114+
"review-submitted": {
115+
"trigger": {"type": "created"},
116+
"enabled": true,
117+
"channels": ["nc-notification"],
118+
"recipients": [
119+
{"kind": "object-acl", "permission": "manage"},
120+
{"kind": "groups", "groups": ["softwarecatalog-admins"]}
121+
],
122+
"subject": {
123+
"nl": "Nieuwe beoordeling: {{naam}} (waardering {{waardering}})",
124+
"en": "New review: {{naam}} (rating {{waardering}})"
125+
}
126+
}
127+
}
128+
```
129+
130+
## Capabilities
131+
132+
### New Capabilities
133+
- `softwarecatalog-notifications`: declarative schema-level notification rules on
134+
`kwetsbaarheid`, `contract`, `moduleVersie`, and `beoordeeling`, consumed by the
135+
OpenRegister notification engine, surfacing reported vulnerabilities (urgent),
136+
approaching contract expiry, newly published module versions, and submitted
137+
reviews to catalogue admins and record managers.
138+
139+
## Impact
140+
141+
- **File:** `lib/Settings/softwarecatalogus_register.json` — adds
142+
`x-openregister-notifications` blocks to `kwetsbaarheid`, `contract`,
143+
`moduleVersie`, `beoordeeling`.
144+
- The OpenRegister notification engine (shipped in OR change
145+
`notification-schema-rules-and-userconfig-prefs`) consumes these blocks at
146+
runtime. No PHP/Vue changes in softwarecatalog.
147+
- Users **opt out** per `(schema, rule)` via override-only user-config prefs;
148+
schema `enabled` is only the default.
149+
- `contract-expiry` ships **disabled by default** — see Caveats.
150+
151+
## Caveats
152+
153+
- **Recipient fields are inferred, not confirmed (plan flag).** `kwetsbaarheid`
154+
has **no** supplier/owner uid field — only `modules` (module references). To
155+
reach the actual supplier you would have to traverse `kwetsbaarheid → module →
156+
aanbieder (organisatie) → contactpersoon`, which the engine's `field`/`relation`
157+
resolver cannot do in one hop today. So vulnerability + module-version + review
158+
rules fall back to an `object-acl` `manage` recipient plus a `groups` recipient
159+
pointed at a `softwarecatalog-admins` group. **That group must exist** (or be
160+
remapped to a real NC group) for the `groups` recipients to resolve. The plan's
161+
"urgent → suppliers + admins" reaches **admins** reliably; **supplier** delivery
162+
needs either a relation-traversal recipient resolver or a structured supplier-uid
163+
field on the schemas.
164+
- **`contract` contactpersoon fields are nested objects, not uid strings.**
165+
`contactpersoonAanbieder` / `contactpersoonGebruiker` are nested `object`
166+
properties, so a `field:` recipient will not resolve an NC uid from them. The
167+
contract rule therefore uses `object-acl` + `groups` instead of those fields.
168+
- **`scheduled` date-window filtering** ("eindDatum within 30 days") engine
169+
support must be confirmed before `contract-expiry` is enabled; it ships disabled.
170+
- **No named lifecycle/`transition` actions and no `updated`-field-change support.**
171+
A "vulnerability status changed" or "contract status → Verlopen" rule is not
172+
expressible today; it would need transition actions on the schema or the
173+
unshipped `notification-updated-field-change-condition` engine change. The
174+
vulnerability rule therefore fires on **creation** of the kwetsbaarheid record
175+
(the report event), which matches the plan's "vulnerability reported" headline.
176+
- **External-recipient email** (suppliers held as email strings / external
177+
contacts) is out of scope — the `field` resolver resolves NC uids only. The
178+
`contactpersoon` schema's `e-mailadres` is an email string, not a uid.
179+
180+
See `hydra/openspec/fleet-notification-plan.md` (softwarecatalog row + cross-cutting
181+
engine-gap section) for the full analysis.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# softwarecatalog-notifications
2+
3+
## ADDED Requirements
4+
5+
### Requirement: Catalogue schemas declare notification rules
6+
7+
The `kwetsbaarheid`, `contract`, `moduleVersie`, and `beoordeeling` schemas SHALL
8+
declare `x-openregister-notifications` rules so the OpenRegister
9+
notification engine dispatches notifications on reported vulnerabilities,
10+
approaching contract expiry, newly published module versions, and submitted
11+
reviews. Every rule SHALL use a trigger type that works today (`created` or
12+
`scheduled`), reference only an existing schema property, the record manage-ACL,
13+
or a named group (never a `field:` recipient pointing at a nested-object or
14+
non-existent property), and provide both `nl` and `en` subject strings.
15+
16+
#### Scenario: Reported vulnerability urgently notifies admins and record managers
17+
18+
- **WHEN** a `kwetsbaarheid` record is created
19+
- **THEN** the engine dispatches `nc-notification` + `email` to the `softwarecatalog-admins` group and the record's manage-ACL holders
20+
- **AND** the subject includes the vulnerability name, CVE code, and CVSS score in the recipient's locale (nl/en)
21+
22+
#### Scenario: New review notifies record managers
23+
24+
- **WHEN** a `beoordeeling` record is created
25+
- **THEN** the engine dispatches an `nc-notification` to the record's manage-ACL holders and the `softwarecatalog-admins` group
26+
27+
#### Scenario: Disabled-by-default contract expiry does not fire until confirmed
28+
29+
- **WHEN** the `contract` `contract-expiry` rule (`enabled: false`) would match
30+
- **THEN** no notification is dispatched until the scheduled date-window filter is confirmed and an admin enables the rule
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Tasks — softwarecatalog notifications
2+
3+
- [x] Add `x-openregister-notifications` (rule `vulnerability-reported`, created, urgent) to `kwetsbaarheid` in lib/Settings/softwarecatalogus_register.json
4+
- [x] Add `x-openregister-notifications` (rule `contract-expiry`, scheduled, enabled:false) to `contract` in lib/Settings/softwarecatalogus_register.json
5+
- [x] Add `x-openregister-notifications` (rule `module-version-published`, created) to `moduleVersie` in lib/Settings/softwarecatalogus_register.json
6+
- [x] Add `x-openregister-notifications` (rule `review-submitted`, created) to `beoordeeling` in lib/Settings/softwarecatalogus_register.json
7+
- [x] Add nl + en `subject` strings to every rule (already specified in proposal.md)
8+
- [x] Validate the register JSON still parses (e.g. `python3 -c "import json;json.load(open('lib/Settings/softwarecatalogus_register.json'))"`)
9+
- [ ] Confirm the `softwarecatalog-admins` group exists or remap `groups` recipients to a real NC group before enabling
10+
- [ ] Confirm engine support for a `scheduled` date-window filter on `eindDatum` before enabling `contract-expiry`
11+
- [ ] Decide whether supplier delivery requires a relation-traversal recipient resolver or a structured supplier-uid field (file follow-up issue if so)
12+
13+
## Acceptance criteria
14+
15+
- The register JSON parses and every touched schema keeps its existing keys intact.
16+
- Each rule uses only trigger types that work today (`created`, `scheduled`) — no dependency on the unshipped `updated`-field-change condition.
17+
- Every rule's recipient references either an existing schema property, the record's manage-ACL, or a named group (no `field:` recipient pointing at a non-existent or nested-object property).
18+
- Every rule has both `nl` and `en` subject strings.
19+
- `vulnerability-reported`, `module-version-published`, `review-submitted` ship enabled; `contract-expiry` ships disabled by default.

0 commit comments

Comments
 (0)