From 0f24462a6545b6ec9f8130c7c1c9c80b0b08a22c Mon Sep 17 00:00:00 2001 From: delchev Date: Sat, 15 Aug 2026 10:53:55 +0300 Subject: [PATCH] spec: an entity's immutability covers the collections composed into it 1.2 already said a composition child "keeps freezing with its master" by default, but only ever spelled out what that means for the child declared `locksWithMaster: false` - where it is normative that the affordances must stay alive, not merely the writes. The default was left to be read as the affordances alone, and one implementation read it exactly that way: the child's own endpoint went on accepting creates, edits and deletes against a locked master. That is not a cosmetic gap. A child write maintains the master's derived values - a line resums the document's totals - so it reaches precisely what the lock protects, after the number was stamped, the frozen copy taken and the ledger entry posted from those totals. The lock is undone through a different door, and the document's own page then shows a total that disagrees with the copy it printed. Says it in both directions instead: the default freezes the child's writes AND its affordances, the opt-out reopens both, and system / workflow writes stay possible throughout - they are what corrects an immutable record. Co-Authored-By: Claude Opus 5 --- versions/1.2.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/versions/1.2.md b/versions/1.2.md index f321557..2bd6c1c 100644 --- a/versions/1.2.md +++ b/versions/1.2.md @@ -492,10 +492,21 @@ A guard compares a keyed [aggregate](#aggregates--keyed-cross-entity-totals) aga `immutableWhen` requires a `function: EntityStatus` relation; `immutable: true` needs none and is mutually exclusive with it. System / workflow writes stay possible — corrections to an immutable record are flow-generated reversals, never edits. +An entity's immutability also covers its **composition children**, which declare none of their own and whose writes maintain the master's derived values — see [`locksWithMaster`](#lockswithmaster--a-child-collection-that-outlives-its-masters-lock) for the collection that must outlive the lock. + +> **Normative.** +> A generator MUST refuse a user create, update or delete of a composition child whose master is +> currently immutable, unless that child declares `locksWithMaster: false`. The refusal MUST cover +> every user surface it generates, not only the affordances it renders: a child write that +> maintains the master's derived values — a line that resums the document's totals — reaches +> exactly what the lock protects, and permitting it undoes the lock through a different door. It +> MUST NOT extend to system / workflow writes, which are what corrects an immutable record. + ### locksWithMaster — a child collection that outlives its master's lock -An entity's immutability covers **that entity**. A composition child is a different entity, so a -master that locks says nothing about whether its child collections should: +An entity's immutability covers that entity **and the collections composed into it**. For some +children that default is wrong — a master that freezes its own content says nothing about a +collection recording what happens to the document afterwards: ```yaml - name: Invoice @@ -512,7 +523,8 @@ different lifecycles on the same document. > **Normative.** > `locksWithMaster` defaults to **true**, so a child that says nothing keeps freezing with its -> master. +> master — in the affordances a generator renders for that collection AND in the writes it accepts +> for it (see [immutability](#immutablewhen--immutable--user-write-immutability)). > A generator MUST NOT extend a master's user-write immutability to a child collection declared > `locksWithMaster: false` — including the affordances it renders for that collection, not merely > the writes it accepts. A read-only rendering that the server would have permitted is the same