|
| 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. |
0 commit comments