Skip to content

Commit a55fe64

Browse files
fix(newman): decidesk#443 was a cookie jar, and it hid five real defects (#447)
* fix(newman): decidesk#443 was a cookie jar, and it hid five real defects All 13 Newman collections now run 310 assertions with 0 failures, measured on a disposable NC 32.0.12 + openregister instance that first reproduced CI's 22 failures exactly (same collections, same counts). decidesk#443 — NOT an authorization hole The four "non-admin gets 200 on an AuthorizedAdminSetting endpoint" assertions were true observations of a false premise. Newman keeps ONE cookie jar per host; Nextcloud resolves a logged-in session from that jar BEFORE it reads the Basic auth header, so a request carrying both runs as whoever authenticated last. Measured, not inferred: an inserted GET /ocs/v2.php/cloud/user on the same request chain answered id="admin", groups=["admin"]. Clearing the jar first turns the same request into 403 and the identity read into "decidesk-admset-nonadmin". The attributes were always enforced. Fixed by a collection-level jar clear in the four collections that assert on WHO is calling, plus an "Identity guard" request in each that asserts the probe really is the non-admin. Removing the clear turns the guard red first, so this cannot silently come back. The same jar also disarmed the IDOR guard decidesk-security-flow-e2e exists to prove: "register on behalf of an unrelated grantor" and "revoke by an unrelated participant" were running as the GRANTOR. They now genuinely 403. Five product defects the false green was sitting on 1. board-proxy schema never existed. ProxyVoteService wrote proxy rows into the `vote` schema, whose required value/castAt are not part of a delegation, so EVERY POST /api/proxies returned 422 "Failed to register proxy." Added the BoardProxy schema the service and both collections were already written against. 2. governance-report schema never existed. persistReport() swallowed "Schema not found", so generate() returned 201 with no id and /api/governance-reports/{id}/export could never be addressed. Added GovernanceReport. 3. The per-holder proxy cap never counted anything. ObjectService reads its register/schema context from filters.register / filters.schema; a TOP-LEVEL 'register'/'schema' key is silently ignored and findAll() returns [] without throwing. forMeeting() used the top-level shape, so a third proxy was accepted at a cap of 2. Live: now 422 with the maximum-reached message, and a different holder still registers. 4. Anonymous ORI show returned 500, not 404. OpenRegister's published-predicate RBAC hides a future-dated payload by making find() THROW, so the not-live 404 branch was unreachable and the blanket Throwable catch produced a 500 — itself a disclosure, since it separates "exists but hidden" from "unknown". 5. Meeting close could never complete. openedAt/closedAt/meetingCost were declared readOnly, which OpenRegister enforces as immutable-after-create for every writer including MeetingService, so opened -> closed failed with "Cannot modify readOnly property: closedAt" (422). A meeting is created before it is opened, so a readOnly stamp can never be written. Seed data: 8 objects were silently skipped on every install Five seed slugs exceeded 36 characters and were referenced by other objects; the relation column is varchar(36), so the write truncated and ImportHandler skipped the object. Shortened the five slugs and their references (and the openspec design/tasks tables that quote them). Locally: 8 skipped -> 0. Test-side changes - decidesk-motion-amendment now seeds a real governance body + chair participant + meeting. VotingController::open() authorizes before it validates (correct order), so the placeholder meetingId 'meeting-x' meant the subjectType contract was never reached — the test only ever saw the 403. - ProxyVoteServiceTest's ObjectService mock treated every filter key as a row field, so a caller using the broken top-level shape still got rows: the mock could not tell a working call from one that returns [] in production. It now models the real contract and returns [] without register/schema context. Reverting forMeeting() to the old shape turns 3 tests red. - RegisterJsonTest schema-count ratchet 37 -> 39 with the reason. Both directions proven per fix, one mutation at a time: swapping AuthorizedAdminSetting for NoAdminRequired on MemberImportController::groups and on ProcessTemplateController::index each turned exactly one assertion red; removing the security-flow jar clear turned the identity guard red; reverting forMeeting() turned the unit suite red. (Note: opcache.revalidate_freq=60 made one mutation run look green — the probe instance now runs revalidate_freq=0.) Left red and not touched here: 17 Playwright specs (95 pass) and the systemic findAll config shape — ~50 call sites across lib/ still pass register/schema at the top level and therefore read nothing. Filed separately; only the call site covered by a red assertion is fixed here. * fix(gates): close gate-54 and the coverage ratchet this branch opened Two jobs went red on #447 that the first commit caused, both real: quality / PHPUnit — coverage ratchet The new `catch (DoesNotExistException)` branch in OriController::show() added 4 statements with no test, so coverage fell against the merge base (8866/15168 -> 8868/15172). Added the missing unit test: find() throwing DoesNotExistException must produce 404, not 500. It is the case the existing testShowFutureDatedPayloadIs404 never covered — that one hands the controller a row and exercises the not-live branch, while on a real instance the anonymous caller never gets the row at all. Swapping the catch to \LogicException turns the new test red with "500 is identical to 404". quality / Hydra Gates — gate-54 relation-dialect, 4 findings Renaming the over-36-character seed slugs pulled two register fragments into the gate's diff scope and exposed four properties that declare `format: uuid` + `$ref` at a schema that does not exist in this register: Fractie and SchriftelijkeVraag are owned by the unlanded fractievoorzitter-fractie-koppeling change, and ArchivalDossier by records-management-archiving. The pair is unsatisfiable while the target is absent — keeping the $ref is a dangling target, dropping it while keeping format:uuid is a relation-shaped property with no $ref. Until the owning change lands these are plain string placeholders (the descriptions already say they are seeded as nil-UUID placeholders), so they are now declared as plain strings, each carrying the instruction to restore format:uuid + $ref in the same commit that introduces the schema. No waiver, no baseline: gate-54 goes from 4 findings to 0 in the files this branch touches. (One finding remains repo-wide: ConsultationRequest.achterbanraadpleging in 47-works-council-consultation.json, a file this branch does not touch and which the gate's ADR-020 diff scoping therefore excludes.) Re-verified after the register edits: all 13 Newman collections, 310 assertions, 0 failures; unit suite 831 tests green. --------- Co-authored-by: Ruben van der Linde <juan.claude@conduction.nl>
1 parent 6fb701d commit a55fe64

18 files changed

Lines changed: 580 additions & 61 deletions

‎lib/Controller/OriController.php‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use OCA\Decidesk\AppInfo\Application;
3131
use OCA\Decidesk\Service\OriSerializer;
3232
use OCP\AppFramework\Controller;
33+
use OCP\AppFramework\Db\DoesNotExistException;
3334
use OCP\AppFramework\Http;
3435
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
3536
use OCP\AppFramework\Http\Attribute\PublicPage;
@@ -323,6 +324,15 @@ public function show(string $resource, string $id): JSONResponse
323324
}
324325

325326
$object = $this->narrowToDecisionType(resource: $resource, object: $object);
327+
} catch (DoesNotExistException $e) {
328+
// OpenRegister's published-predicate RBAC hides a future-dated or
329+
// depublished payload from an anonymous caller by making find() THROW,
330+
// not by returning null — so the not-live 404 branch below was never
331+
// reached and the blanket Throwable catch turned it into a 500. A 500
332+
// is itself a disclosure (it separates "exists but hidden" from
333+
// "unknown id"), which is exactly what this endpoint must not do.
334+
// An id we cannot read is not-found, full stop.
335+
return $this->errorResponse(message: 'Not found', status: Http::STATUS_NOT_FOUND);
326336
} catch (Throwable $e) {
327337
$this->logger->error(message: 'OriController show failed', context: ['resource' => $resource, 'id' => $id, 'exception' => $e]);
328338
return $this->errorResponse(message: 'Internal server error', status: Http::STATUS_INTERNAL_SERVER_ERROR);

‎lib/Service/ProxyVoteService.php‎

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77
* suspended when the grantor joins the meeting remotely, and revoked either
88
* at meeting close or by secretary action.
99
*
10-
* Proxy rows are persisted on the unified `vote` schema
11-
* (`voteMethod=proxy`, `proxyHolder` set, additional `proxyStatus` field)
12-
* so the audit log always shows the cast trail (ADR-006).
10+
* Proxy rows are persisted on the `board-proxy` schema (meetingKoppeling /
11+
* grantorKoppeling / holderKoppeling / proxyStatus), which is what this service
12+
* has always written. It previously pointed at the `vote` schema, whose required
13+
* `value` + `castAt` are not part of a delegation, so every registration threw
14+
* and returned 422; `board-proxy` is the schema the row actually describes.
15+
* Every transition is mirrored to the audit log (ADR-006).
1316
*
1417
* @category Service
1518
* @package OCA\Decidesk\Service
@@ -54,7 +57,7 @@ class ProxyVoteService
5457
*
5558
* @var string
5659
*/
57-
public const SCHEMA = 'vote';
60+
public const SCHEMA = 'board-proxy';
5861

5962
/**
6063
* App config key holding the per-holder per-meeting ACTIVE-proxy cap.
@@ -397,12 +400,21 @@ public function forMeeting(string $meetingId, ?string $status=null): array
397400
{
398401
try {
399402
$objectService = $this->container->get('OCA\OpenRegister\Service\ObjectService');
400-
$rows = $objectService->findAll(
403+
// ObjectService::prepareFindAllConfig() only reads the register/schema
404+
// context from filters.register / filters.schema — a TOP-LEVEL
405+
// 'register'/'schema' key is silently ignored, findAll() then runs with
406+
// no register/schema context and returns an empty array. It does not
407+
// throw, so the caller cannot tell "no proxies" from "never looked",
408+
// and the per-holder cap below counted 0 every time (decidesk#443
409+
// follow-up: a third proxy was accepted at a cap of 2).
410+
$rows = $objectService->findAll(
401411
[
402-
'register' => 'decidesk',
403-
'schema' => self::SCHEMA,
404-
'filters' => ['meetingKoppeling' => $meetingId],
405-
'limit' => 1000,
412+
'filters' => [
413+
'register' => 'decidesk',
414+
'schema' => self::SCHEMA,
415+
'meetingKoppeling' => $meetingId,
416+
],
417+
'limit' => 1000,
406418
]
407419
);
408420
} catch (\Throwable $e) {

‎lib/Settings/decidesk_register.json‎

Lines changed: 151 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,7 +1158,7 @@
11581158
],
11591159
"evaluation-template": [
11601160
{
1161-
"slug": "template-bestuurlijke-effectiviteit-nl-cgc",
1161+
"slug": "template-bestuurlijke-effectiviteit",
11621162
"title": "Board effectiveness self-evaluation (NL Corporate Governance Code)",
11631163
"description": "Default reusable annual board self-evaluation (zelfevaluatie) covering the five standard effectiveness dimensions. Editable per body.",
11641164
"dimensions": [
@@ -1265,7 +1265,7 @@
12651265
"secretaryUserId": "jan.devries",
12661266
"scoreSummary": "{\"overallScore\":3.6,\"respondentCount\":2,\"invitedMemberCount\":3,\"minRespondentThreshold\":3,\"thresholdMet\":false,\"suppressed\":true,\"dimensionScores\":null,\"themes\":null,\"computedAt\":\"2025-12-20T17:05:00Z\"}",
12671267
"governanceBody": "rvc-waterschap-amstel",
1268-
"template": "template-bestuurlijke-effectiviteit-nl-cgc"
1268+
"template": "template-bestuurlijke-effectiviteit"
12691269
},
12701270
{
12711271
"slug": "evaluatie-acme-rvc-2026",
@@ -1289,7 +1289,7 @@
12891289
"chairUserId": "acme.chair",
12901290
"secretaryUserId": "acme.secretary",
12911291
"governanceBody": "raad-van-commissarissen-acme-bv",
1292-
"template": "template-bestuurlijke-effectiviteit-nl-cgc"
1292+
"template": "template-bestuurlijke-effectiviteit"
12931293
}
12941294
],
12951295
"evaluation-response": [
@@ -1394,6 +1394,7 @@
13941394
"agenda-item",
13951395
"bevoegdheidstoedeling",
13961396
"board-evaluation",
1397+
"board-proxy",
13971398
"body-participation",
13981399
"budget-proposal",
13991400
"citizen-panel",
@@ -1415,6 +1416,7 @@
14151416
"geheimhouding-grond",
14161417
"geschenk",
14171418
"governance-body",
1419+
"governance-report",
14181420
"governing-document",
14191421
"governing-document-versie",
14201422
"ingekomen-stuk",
@@ -2207,21 +2209,18 @@
22072209
"title": "Opened at",
22082210
"type": "string",
22092211
"format": "date-time",
2210-
"description": "Timestamp of the first 'open' lifecycle transition, stamped server-side by MeetingService (meeting-efficiency). Basis for the live cost calculator and duration analytics.",
2211-
"readOnly": true
2212+
"description": "Timestamp of the first 'open' lifecycle transition, stamped server-side by MeetingService (meeting-efficiency). Basis for the live cost calculator and duration analytics. NOT readOnly: OpenRegister reads readOnly as immutable-after-create and rejects the UPDATE for every writer, including MeetingService itself — a meeting is created before it is opened, so a readOnly stamp can never be written."
22122213
},
22132214
"closedAt": {
22142215
"title": "Closed at",
22152216
"type": "string",
22162217
"format": "date-time",
2217-
"description": "Timestamp of the 'close' lifecycle transition, stamped server-side by MeetingService (meeting-efficiency).",
2218-
"readOnly": true
2218+
"description": "Timestamp of the 'close' lifecycle transition, stamped server-side by MeetingService (meeting-efficiency). NOT readOnly — see openedAt: readOnly made every opened -> closed transition fail with 'Cannot modify readOnly property: closedAt' (422)."
22192219
},
22202220
"meetingCost": {
22212221
"title": "Meeting cost",
22222222
"type": "number",
2223-
"description": "Total meeting cost in EUR, computed server-side on close from elapsed time x attendee count x the governance body's hourlyRate (meeting-efficiency). Schema.org MonetaryAmount value.",
2224-
"readOnly": true
2223+
"description": "Total meeting cost in EUR, computed server-side on close from elapsed time x attendee count x the governance body's hourlyRate (meeting-efficiency). Schema.org MonetaryAmount value. NOT readOnly — see openedAt: it is stamped on close, i.e. always after create."
22252224
}
22262225
},
22272226
"x-openregister-aggregations": {
@@ -2704,6 +2703,149 @@
27042703
}
27052704
}
27062705
},
2706+
"GovernanceReport": {
2707+
"slug": "governance-report",
2708+
"icon": "ChartBoxOutline",
2709+
"version": "0.1.0",
2710+
"title": "GovernanceReport",
2711+
"description": "A generated annual governance report for one board and year (meeting/resolution counts, vote tally, independence ratio, attendance rate, conflict count and the compliance flags derived from them). This is the row GovernanceReportingService persists so /api/governance-reports/{id} and /{id}/export/{format} have something to address.",
2712+
"type": "object",
2713+
"x-openregister": {
2714+
"schemaType": "custom:GovernanceReport",
2715+
"active": true,
2716+
"hardDelete": false,
2717+
"searchable": false,
2718+
"mailEnabled": false
2719+
},
2720+
"required": [
2721+
"boardKoppeling",
2722+
"year"
2723+
],
2724+
"properties": {
2725+
"boardKoppeling": {
2726+
"title": "Board",
2727+
"type": "string",
2728+
"description": "Identifier of the governance body the report covers."
2729+
},
2730+
"year": {
2731+
"title": "Year",
2732+
"type": "integer",
2733+
"description": "Calendar year the report covers.",
2734+
"example": 2026
2735+
},
2736+
"meetingCount": {
2737+
"title": "Meeting count",
2738+
"type": "integer",
2739+
"description": "Number of meetings held in the reporting year."
2740+
},
2741+
"resolutionCount": {
2742+
"title": "Resolution count",
2743+
"type": "integer",
2744+
"description": "Number of resolutions taken in the reporting year."
2745+
},
2746+
"voteTally": {
2747+
"title": "Vote tally",
2748+
"type": "object",
2749+
"description": "Votes cast in the reporting year, counted per outcome (in-favor, against, abstain, absent, recused-due-to-conflict)."
2750+
},
2751+
"memberCount": {
2752+
"title": "Member count",
2753+
"type": "integer",
2754+
"description": "Number of board members in the reporting year."
2755+
},
2756+
"independenceRatio": {
2757+
"title": "Independence ratio",
2758+
"type": "number",
2759+
"description": "Share of board members classified as independent."
2760+
},
2761+
"attendanceRate": {
2762+
"title": "Attendance rate",
2763+
"type": "number",
2764+
"description": "Share of possible member-meeting attendances actually recorded."
2765+
},
2766+
"conflictCount": {
2767+
"title": "Conflict count",
2768+
"type": "integer",
2769+
"description": "Number of conflict-of-interest declarations in the reporting year."
2770+
},
2771+
"flags": {
2772+
"title": "Compliance flags",
2773+
"type": "object",
2774+
"description": "Compliance verdict for the report: whether every threshold passed, plus the list of thresholds that did not."
2775+
},
2776+
"generatedAt": {
2777+
"title": "Generated at",
2778+
"type": "string",
2779+
"description": "Moment the report was generated."
2780+
}
2781+
}
2782+
},
2783+
"BoardProxy": {
2784+
"slug": "board-proxy",
2785+
"icon": "AccountArrowRightOutline",
2786+
"version": "0.1.0",
2787+
"title": "BoardProxy",
2788+
"description": "A proxy (volmacht) granted by one board member to another for a single meeting. This is the row ProxyVoteService writes through /api/proxies and the row the per-holder proxy cap counts; the signed-document side of a volmacht lives on ProxyAuthorization.",
2789+
"type": "object",
2790+
"x-openregister": {
2791+
"schemaType": "custom:BoardProxy",
2792+
"active": true,
2793+
"hardDelete": false,
2794+
"searchable": false,
2795+
"mailEnabled": false
2796+
},
2797+
"required": [
2798+
"meetingKoppeling",
2799+
"grantorKoppeling",
2800+
"holderKoppeling",
2801+
"proxyStatus"
2802+
],
2803+
"properties": {
2804+
"meetingKoppeling": {
2805+
"title": "Meeting",
2806+
"type": "string",
2807+
"description": "Identifier of the meeting this proxy applies to. The per-holder cap is counted per meeting."
2808+
},
2809+
"grantorKoppeling": {
2810+
"title": "Grantor",
2811+
"type": "string",
2812+
"description": "Identifier of the participant delegating their vote (the volmachtgever)."
2813+
},
2814+
"holderKoppeling": {
2815+
"title": "Holder",
2816+
"type": "string",
2817+
"description": "Identifier of the participant receiving the delegation (the gevolmachtigde)."
2818+
},
2819+
"scope": {
2820+
"title": "Scope",
2821+
"type": "string",
2822+
"description": "Which resolutions the delegation covers.",
2823+
"example": "all-resolutions"
2824+
},
2825+
"expiresAt": {
2826+
"title": "Expires at",
2827+
"type": "string",
2828+
"description": "Optional moment the delegation lapses. Empty when the proxy runs for the whole meeting."
2829+
},
2830+
"proxyStatus": {
2831+
"title": "Proxy status",
2832+
"type": "string",
2833+
"description": "Lifecycle state of the delegation. A freshly registered proxy starts pending-approval; only active proxies count toward the per-holder cap.",
2834+
"enum": [
2835+
"pending-approval",
2836+
"active",
2837+
"suspended",
2838+
"revoked"
2839+
],
2840+
"example": "pending-approval"
2841+
},
2842+
"registeredAt": {
2843+
"title": "Registered at",
2844+
"type": "string",
2845+
"description": "Moment the delegation was registered."
2846+
}
2847+
}
2848+
},
27072849
"Vote": {
27082850
"slug": "vote",
27092851
"icon": "ThumbUpOutline",

‎lib/Settings/register.d/49-vragenuur-interpellatie.json‎

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
"lifecycle": "behandeld",
9292
"raadsbesluitDatum": "2026-05-12",
9393
"behandeldIn": "raadsvergadering-2025-01-15",
94-
"agendaItem": "interpellatie-stationsgebied-2025-01-15",
94+
"agendaItem": "interpellatie-stationsgebied-2025",
9595
"behandelingVerslag": "Interpellatiedebat gevoerd; wethouder zegt extern onderzoek toe naar de veiligheidssituatie en informeert de raad per raadsbrief voor het zomerreces."
9696
},
9797
{
@@ -144,7 +144,7 @@
144144
"meeting": "raadsvergadering-2025-01-15"
145145
},
146146
{
147-
"slug": "interpellatie-stationsgebied-2025-01-15",
147+
"slug": "interpellatie-stationsgebied-2025",
148148
"title": "Interpellatie: veiligheid stationsgebied",
149149
"itemType": "discussion",
150150
"orderNumber": 7,
@@ -388,9 +388,7 @@
388388
"fractie": {
389389
"title": "Fraction",
390390
"type": "string",
391-
"format": "uuid",
392-
"$ref": "Fractie",
393-
"description": "Reference to the submitter's Fractie (fractievoorzitter-fractie-koppeling — hard depends_on, that change owns the schema). Required. Seeded as a nil-UUID placeholder until that change lands (design D6)."
391+
"description": "Reference to the submitter's Fractie (fractievoorzitter-fractie-koppeling — hard depends_on, that change owns the schema). Required. Seeded as a nil-UUID placeholder until that change lands (design D6). Declared as a PLAIN STRING, not format:uuid + $ref: the Fractie schema does not exist in this register yet, so a $ref to it is a dangling target (ADR-062 rule f) while dropping the $ref but keeping format:uuid is a relation-shaped property with no $ref (rule 7) — the pair is unsatisfiable until that change lands. Restore format:uuid + $ref: Fractie in the same commit that introduces the schema."
394392
},
395393
"onderwerp": {
396394
"title": "Subject",
@@ -481,16 +479,12 @@
481479
"vervolgSchriftelijkeVraag": {
482480
"title": "Follow-up written question",
483481
"type": "string",
484-
"format": "uuid",
485-
"$ref": "SchriftelijkeVraag",
486-
"description": "Optional reference to a follow-up SchriftelijkeVraag (fractievoorzitter-fractie-koppeling) created from a niet-behandeld or beantwoord oral question. Its answering workflow stays in that change (REQ-VRI-006)."
482+
"description": "Optional reference to a follow-up SchriftelijkeVraag (fractievoorzitter-fractie-koppeling) created from a niet-behandeld or beantwoord oral question. Its answering workflow stays in that change (REQ-VRI-006). Declared as a PLAIN STRING — see the note on MondelingeVraag.fractie: the SchriftelijkeVraag schema does not exist in this register yet. Restore format:uuid + $ref in the commit that introduces it."
487483
},
488484
"bronSchriftelijkeVraag": {
489485
"title": "Source written question",
490486
"type": "string",
491-
"format": "uuid",
492-
"$ref": "SchriftelijkeVraag",
493-
"description": "Optional reference to the SchriftelijkeVraag this oral question escalates from (fractievoorzitter-fractie-koppeling). Set on written → oral escalation; when the oral question reaches beantwoord, OralQuestionService sets the linked SV to 'vervallen-door-mondelinge-beantwoording' via a PUT-semantic save carrying all other SV fields forward (design D6, REQ-VRI-006). No field mirroring or shared lifecycle."
487+
"description": "Declared as a PLAIN STRING — see the note on MondelingeVraag.fractie: the SchriftelijkeVraag schema does not exist in this register yet. Restore format:uuid + $ref in the commit that introduces it. Optional reference to the SchriftelijkeVraag this oral question escalates from (fractievoorzitter-fractie-koppeling). Set on written → oral escalation; when the oral question reaches beantwoord, OralQuestionService sets the linked SV to 'vervallen-door-mondelinge-beantwoording' via a PUT-semantic save carrying all other SV fields forward (design D6, REQ-VRI-006). No field mirroring or shared lifecycle."
494488
}
495489
}
496490
},
@@ -701,9 +695,7 @@
701695
"fractie": {
702696
"title": "Fraction",
703697
"type": "string",
704-
"format": "uuid",
705-
"$ref": "Fractie",
706-
"description": "Reference to the requester's Fractie (fractievoorzitter-fractie-koppeling — hard depends_on). Required. Seeded as a nil-UUID placeholder until that change lands (design D6)."
698+
"description": "Reference to the requester's Fractie (fractievoorzitter-fractie-koppeling — hard depends_on). Required. Seeded as a nil-UUID placeholder until that change lands (design D6). Declared as a PLAIN STRING — see the note on MondelingeVraag.fractie: the Fractie schema does not exist in this register yet. Restore format:uuid + $ref: Fractie in the commit that introduces it."
707699
},
708700
"onderwerp": {
709701
"title": "Subject",

0 commit comments

Comments
 (0)