feat(beheer): stamdata deletion protection — CI referential gate + editor expire/warn
CI / frontend (push) Successful in 2m27s
CI / backend (push) Successful in 2m3s
CI / storybook-a11y (push) Failing after 6m30s
CI / semgrep (push) Successful in 1m5s
CI / e2e (push) Successful in 3m22s
CI / api-client-drift (push) Successful in 2m10s

CI gate (authoritative): generalize the dangling-reference test in StamdataValidationTests
into a declared, extensible reference list ("every declared reference into a stamdata key
resolves against the currently-valid stamdata"), starting with Diploma.Opleiding →
professions.program. Removing/renaming a referenced program, or expiring it while current
data still references it, fails the PR build (ADR-0004). Editor (fast feedback): confirm
before delete (warns a referenced row fails CI) and, for temporal tables, a "Sluiten per
vandaag" action that closes validity (geldigTot) — steering to expire over hard delete.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
eho
2026-07-23 22:35:17 +02:00
co-authored by Claude Opus 4.8
parent 67802c68b4
commit fbc4bf51d0
6 changed files with 122 additions and 22 deletions
@@ -145,10 +145,15 @@ interface DisplayRow {
}
<td>
@if (!previewing()) {
@if (table().temporal) {
<app-button variant="subtle" (click)="onExpire(item.index)">{{
expireLabel
}}</app-button>
}
<app-button
variant="subtle"
[attr.aria-label]="removeLabel"
(click)="rowRemoved.emit(item.index)"
(click)="onRemove(item.index)"
>{{ removeLabel }}</app-button
>
}
@@ -234,6 +239,21 @@ export class StamdataTableEditorComponent {
protected previewNote = $localize`:@@beheer.previewNote:Voorbeeld: alleen de rijen die op deze datum geldig zijn. Bewerken staat uit.`;
protected actionsLabel = $localize`:@@beheer.actions:Acties`;
protected removeLabel = $localize`:@@beheer.remove:Verwijderen`;
protected expireLabel = $localize`:@@beheer.expire:Sluiten per vandaag`;
private removeConfirm = $localize`:@@beheer.removeConfirm:Rij verwijderen? Als andere gegevens ernaar verwijzen, faalt de build-controle (CI). Bij een tabel met een geldigheidsperiode kunt u de rij beter sluiten (geldig tot) in plaats van verwijderen.`;
/** Deletions can orphan a reference (the CI gate catches it); confirm first (WP-48). */
protected onRemove(index: number) {
if (confirm(this.removeConfirm)) this.rowRemoved.emit(index);
}
/** Steer temporal tables toward expiring (close the validity per today) over hard delete —
preserves history and can't orphan a reference that was valid earlier (WP-48). */
protected onExpire(index: number) {
const col = this.table().columns.find((c) => /geldigtot/i.test(c.name));
if (col) this.cellEdited.emit({ row: index, column: col.name, value: this.today });
}
private today = new Date().toISOString().slice(0, 10);
protected undoLabel = $localize`:@@beheer.undo:Ongedaan maken`;
protected redoLabel = $localize`:@@beheer.redo:Opnieuw uitvoeren`;
protected addRowLabel = $localize`:@@beheer.addRow:Rij toevoegen`;