Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions lib/Settings/softwarecatalogus_register.json
Original file line number Diff line number Diff line change
Expand Up @@ -1535,6 +1535,21 @@
"kwetsbaarheid": {
"uri": null,
"slug": "kwetsbaarheid",
"x-openregister-notifications": {
"vulnerability-reported": {
"trigger": {"type": "created"},
"enabled": true,
"channels": ["nc-notification", "email"],
"recipients": [
{"kind": "groups", "groups": ["softwarecatalog-admins"]},
{"kind": "object-acl", "permission": "manage"}
],
"subject": {
"nl": "Kwetsbaarheid gemeld: {{naam}} ({{cveCode}}, CVSS {{cvssScore}})",
"en": "Vulnerability reported: {{naam}} ({{cveCode}}, CVSS {{cvssScore}})"
}
}
},
"title": "Kwetsbaarheid",
"description": "Schema voor kwetsbaarheden. Dit schema is onderdeel van het vastgestelde datamodel maar wordt niet daadwerkelijk in de applicatie gebruikt.",
"version": "1.0.19",
Expand Down Expand Up @@ -3119,6 +3134,21 @@
"contract": {
"uri": null,
"slug": "contract",
"x-openregister-notifications": {
"contract-expiry": {
"trigger": {"type": "scheduled", "intervalSec": 86400, "filter": {"status": {"op": "equals", "value": "Actief"}}},
"enabled": false,
"channels": ["nc-notification", "email"],
"recipients": [
{"kind": "groups", "groups": ["softwarecatalog-admins"]},
{"kind": "object-acl", "permission": "manage"}
],
"subject": {
"nl": "Contract verloopt: {{contractNummer}} (einddatum {{eindDatum}})",
"en": "Contract expiring: {{contractNummer}} (end date {{eindDatum}})"
}
}
},
"title": "Contract",
"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.",
"version": "0.1.0",
Expand Down Expand Up @@ -3751,6 +3781,21 @@
"beoordeeling": {
"uri": null,
"slug": "beoordeeling",
"x-openregister-notifications": {
"review-submitted": {
"trigger": {"type": "created"},
"enabled": true,
"channels": ["nc-notification"],
"recipients": [
{"kind": "object-acl", "permission": "manage"},
{"kind": "groups", "groups": ["softwarecatalog-admins"]}
],
"subject": {
"nl": "Nieuwe beoordeling: {{naam}} (waardering {{waardering}})",
"en": "New review: {{naam}} (rating {{waardering}})"
}
}
},
"title": "Beoordeeling",
"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.",
"version": "0.1.2",
Expand Down Expand Up @@ -7227,6 +7272,21 @@
"moduleVersie": {
"uri": null,
"slug": "moduleVersie",
"x-openregister-notifications": {
"module-version-published": {
"trigger": {"type": "created"},
"enabled": true,
"channels": ["nc-notification"],
"recipients": [
{"kind": "object-acl", "permission": "manage"},
{"kind": "groups", "groups": ["softwarecatalog-admins"]}
],
"subject": {
"nl": "Nieuwe moduleversie: {{versie}}",
"en": "New module version: {{versie}}"
}
}
},
"title": "Applicatieversie",
"description": "Schema voor applicatieversies",
"version": "0.1.0",
Expand Down
2 changes: 2 additions & 0 deletions openspec/changes/softwarecatalog-notifications/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-26
181 changes: 181 additions & 0 deletions openspec/changes/softwarecatalog-notifications/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
---
kind: config
depends_on: []
---

# softwarecatalog — schema-declared notifications

## Why

Softwarecatalog is the GEMMA software catalogue used by municipal architects,
VNG, and suppliers. The headline notification needs are: **a reported
vulnerability** (urgent — suppliers + catalogue admins), **contract expiry**
(scheduled reminder), **a new module version**, and **a new review**. None of
softwarecatalog's schemas currently declare `x-openregister-notifications`, so the
OpenRegister notification engine has nothing to dispatch on. This change declares
schema-level notification rules for those four events.

All rules use trigger types that work **today** (`created`, `scheduled`). No rule
depends on the unshipped `updated`-field-change engine condition, so this change
carries **no** `depends_on`.

> The register file is `lib/Settings/softwarecatalogus_register.json` (note the
> `-us` suffix — it does **not** match the app slug).

## What Changes

Add a top-level `x-openregister-notifications` key to the relevant schemas in
`lib/Settings/softwarecatalogus_register.json`, using the verified engine dialect.

### `kwetsbaarheid` (vulnerability) — reported (created) — URGENT

`kwetsbaarheid` carries `naam`, `cveCode`, `cvssScore`, and `modules` (array of
module references). There is **no** direct supplier/owner field on the schema
(see Caveats), so recipients use a catalogue-admin `groups` recipient plus the
record's manage-ACL.

```jsonc
"x-openregister-notifications": {
"vulnerability-reported": {
"trigger": {"type": "created"},
"enabled": true,
"channels": ["nc-notification", "email"],
"recipients": [
{"kind": "groups", "groups": ["softwarecatalog-admins"]},
{"kind": "object-acl", "permission": "manage"}
],
"subject": {
"nl": "Kwetsbaarheid gemeld: {{naam}} ({{cveCode}}, CVSS {{cvssScore}})",
"en": "Vulnerability reported: {{naam}} ({{cveCode}}, CVSS {{cvssScore}})"
}
}
}
```

### `contract` — expiry reminder (scheduled)

`contract` carries `eindDatum` (end date), `status` (enum: `Actief` / `Verlopen` /
`In onderhandeling`), and nested `contactpersoonAanbieder` /
`contactpersoonGebruiker` objects. A `scheduled` rule periodically checks active
contracts whose `eindDatum` is approaching.

```jsonc
"x-openregister-notifications": {
"contract-expiry": {
"trigger": {"type": "scheduled", "intervalSec": 86400, "filter": {"status": {"op": "equals", "value": "Actief"}}},
"enabled": false,
"channels": ["nc-notification", "email"],
"recipients": [
{"kind": "groups", "groups": ["softwarecatalog-admins"]},
{"kind": "object-acl", "permission": "manage"}
],
"subject": {
"nl": "Contract verloopt: {{contractNummer}} (einddatum {{eindDatum}})",
"en": "Contract expiring: {{contractNummer}} (end date {{eindDatum}})"
}
}
}
```

Ships **disabled by default** because the `scheduled` filter needs a
date-window comparison on `eindDatum` (e.g. "within 30 days") whose engine
support must be confirmed; see Caveats.

### `moduleVersie` (module version) — published (created)

`moduleVersie` carries `module`, `versie`, `status`, and `geregistreerdDoor`. A
new version row is a `created` event.

```jsonc
"x-openregister-notifications": {
"module-version-published": {
"trigger": {"type": "created"},
"enabled": true,
"channels": ["nc-notification"],
"recipients": [
{"kind": "object-acl", "permission": "manage"},
{"kind": "groups", "groups": ["softwarecatalog-admins"]}
],
"subject": {
"nl": "Nieuwe moduleversie: {{versie}}",
"en": "New module version: {{versie}}"
}
}
}
```

### `beoordeeling` (review) — submitted (created)

`beoordeeling` carries `naam`, `waardering` (rating), and `modules`. A new review
is a `created` event.

```jsonc
"x-openregister-notifications": {
"review-submitted": {
"trigger": {"type": "created"},
"enabled": true,
"channels": ["nc-notification"],
"recipients": [
{"kind": "object-acl", "permission": "manage"},
{"kind": "groups", "groups": ["softwarecatalog-admins"]}
],
"subject": {
"nl": "Nieuwe beoordeling: {{naam}} (waardering {{waardering}})",
"en": "New review: {{naam}} (rating {{waardering}})"
}
}
}
```

## Capabilities

### New Capabilities
- `softwarecatalog-notifications`: declarative schema-level notification rules on
`kwetsbaarheid`, `contract`, `moduleVersie`, and `beoordeeling`, consumed by the
OpenRegister notification engine, surfacing reported vulnerabilities (urgent),
approaching contract expiry, newly published module versions, and submitted
reviews to catalogue admins and record managers.

## Impact

- **File:** `lib/Settings/softwarecatalogus_register.json` — adds
`x-openregister-notifications` blocks to `kwetsbaarheid`, `contract`,
`moduleVersie`, `beoordeeling`.
- The OpenRegister notification engine (shipped in OR change
`notification-schema-rules-and-userconfig-prefs`) consumes these blocks at
runtime. No PHP/Vue changes in softwarecatalog.
- Users **opt out** per `(schema, rule)` via override-only user-config prefs;
schema `enabled` is only the default.
- `contract-expiry` ships **disabled by default** — see Caveats.

## Caveats

- **Recipient fields are inferred, not confirmed (plan flag).** `kwetsbaarheid`
has **no** supplier/owner uid field — only `modules` (module references). To
reach the actual supplier you would have to traverse `kwetsbaarheid → module →
aanbieder (organisatie) → contactpersoon`, which the engine's `field`/`relation`
resolver cannot do in one hop today. So vulnerability + module-version + review
rules fall back to an `object-acl` `manage` recipient plus a `groups` recipient
pointed at a `softwarecatalog-admins` group. **That group must exist** (or be
remapped to a real NC group) for the `groups` recipients to resolve. The plan's
"urgent → suppliers + admins" reaches **admins** reliably; **supplier** delivery
needs either a relation-traversal recipient resolver or a structured supplier-uid
field on the schemas.
- **`contract` contactpersoon fields are nested objects, not uid strings.**
`contactpersoonAanbieder` / `contactpersoonGebruiker` are nested `object`
properties, so a `field:` recipient will not resolve an NC uid from them. The
contract rule therefore uses `object-acl` + `groups` instead of those fields.
- **`scheduled` date-window filtering** ("eindDatum within 30 days") engine
support must be confirmed before `contract-expiry` is enabled; it ships disabled.
- **No named lifecycle/`transition` actions and no `updated`-field-change support.**
A "vulnerability status changed" or "contract status → Verlopen" rule is not
expressible today; it would need transition actions on the schema or the
unshipped `notification-updated-field-change-condition` engine change. The
vulnerability rule therefore fires on **creation** of the kwetsbaarheid record
(the report event), which matches the plan's "vulnerability reported" headline.
- **External-recipient email** (suppliers held as email strings / external
contacts) is out of scope — the `field` resolver resolves NC uids only. The
`contactpersoon` schema's `e-mailadres` is an email string, not a uid.

See `hydra/openspec/fleet-notification-plan.md` (softwarecatalog row + cross-cutting
engine-gap section) for the full analysis.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# softwarecatalog-notifications

## ADDED Requirements

### Requirement: Catalogue schemas declare notification rules

The `kwetsbaarheid`, `contract`, `moduleVersie`, and `beoordeeling` schemas SHALL
declare `x-openregister-notifications` rules so the OpenRegister
notification engine dispatches notifications on reported vulnerabilities,
approaching contract expiry, newly published module versions, and submitted
reviews. Every rule SHALL use a trigger type that works today (`created` or
`scheduled`), reference only an existing schema property, the record manage-ACL,
or a named group (never a `field:` recipient pointing at a nested-object or
non-existent property), and provide both `nl` and `en` subject strings.

#### Scenario: Reported vulnerability urgently notifies admins and record managers

- **WHEN** a `kwetsbaarheid` record is created
- **THEN** the engine dispatches `nc-notification` + `email` to the `softwarecatalog-admins` group and the record's manage-ACL holders
- **AND** the subject includes the vulnerability name, CVE code, and CVSS score in the recipient's locale (nl/en)

#### Scenario: New review notifies record managers

- **WHEN** a `beoordeeling` record is created
- **THEN** the engine dispatches an `nc-notification` to the record's manage-ACL holders and the `softwarecatalog-admins` group

#### Scenario: Disabled-by-default contract expiry does not fire until confirmed

- **WHEN** the `contract` `contract-expiry` rule (`enabled: false`) would match
- **THEN** no notification is dispatched until the scheduled date-window filter is confirmed and an admin enables the rule
19 changes: 19 additions & 0 deletions openspec/changes/softwarecatalog-notifications/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Tasks — softwarecatalog notifications

- [x] Add `x-openregister-notifications` (rule `vulnerability-reported`, created, urgent) to `kwetsbaarheid` in lib/Settings/softwarecatalogus_register.json
- [x] Add `x-openregister-notifications` (rule `contract-expiry`, scheduled, enabled:false) to `contract` in lib/Settings/softwarecatalogus_register.json
- [x] Add `x-openregister-notifications` (rule `module-version-published`, created) to `moduleVersie` in lib/Settings/softwarecatalogus_register.json
- [x] Add `x-openregister-notifications` (rule `review-submitted`, created) to `beoordeeling` in lib/Settings/softwarecatalogus_register.json
- [x] Add nl + en `subject` strings to every rule (already specified in proposal.md)
- [x] Validate the register JSON still parses (e.g. `python3 -c "import json;json.load(open('lib/Settings/softwarecatalogus_register.json'))"`)
- [ ] Confirm the `softwarecatalog-admins` group exists or remap `groups` recipients to a real NC group before enabling
- [ ] Confirm engine support for a `scheduled` date-window filter on `eindDatum` before enabling `contract-expiry`
- [ ] Decide whether supplier delivery requires a relation-traversal recipient resolver or a structured supplier-uid field (file follow-up issue if so)

## Acceptance criteria

- The register JSON parses and every touched schema keeps its existing keys intact.
- Each rule uses only trigger types that work today (`created`, `scheduled`) — no dependency on the unshipped `updated`-field-change condition.
- 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).
- Every rule has both `nl` and `en` subject strings.
- `vulnerability-reported`, `module-version-published`, `review-submitted` ship enabled; `contract-expiry` ships disabled by default.
Loading