From 5065138e6cc0170cf81fedc19e8d45d7f030d903 Mon Sep 17 00:00:00 2001 From: delchev Date: Sat, 15 Aug 2026 10:54:08 +0300 Subject: [PATCH] docs: the lock a document holds covers the lines composed into it The entities page stated the default ("a child that says nothing keeps freezing with its master") and then spelled out only the opt-out half - that a generator must not extend the lock to a child declared `locksWithMaster: false`, affordances included. The default was left to be read as affordances alone, which is exactly how one implementation read it: the child's endpoint kept accepting writes against a locked master. A child write maintains the master's derived values, so it reaches what the lock protects - the totals a stamped number, a frozen copy and a posted ledger entry were all taken from. Now normative in both directions, with the reason next to it. Co-Authored-By: Claude Opus 5 --- docs/spec/entities.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/spec/entities.md b/docs/spec/entities.md index de31431..3873cb8 100644 --- a/docs/spec/entities.md +++ b/docs/spec/entities.md @@ -306,9 +306,15 @@ The total is recomputed from the guarded entity's own rows for the incoming reco `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. +The lock also covers the entity's **composition children**. A child declares no immutability of its own, but its writes maintain the master's derived values — a line resums the document's totals — so a line write on a locked document reaches exactly what the lock protects: the totals a stamped number, a frozen copy and a posted ledger entry were all taken from. [`locksWithMaster`](#lockswithmaster-a-child-collection-that-outlives-its-masters-lock) is how a collection opts out. + +::: info 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 — permitting the write through a different door undoes the lock as surely as removing it. 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 is wrong — a master that freezes its content says nothing about a collection recording what happens to the document afterwards: ```yaml - name: Invoice @@ -322,9 +328,9 @@ An entity's immutability covers **that entity**. A composition child is a differ The canonical case is settlement: an issued invoice's lines are frozen — that is the audit trail — while payment allocations against it go on being recorded for months. Content and settlement are different lifecycles on the same document. ::: info Normative -`locksWithMaster` defaults to **true**, so a child that says nothing keeps freezing with its master. +`locksWithMaster` defaults to **true**, so a child that says nothing keeps freezing with its master — in the affordances a generator renders for that collection AND in the writes it accepts for it. -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 defect as a refused write. +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 defect as a refused write. One declaration governs both halves, so a generator's screen and its server can never disagree about a given collection. The declaration is only meaningful on a composition child whose master actually declares immutability; a generator MUST reject it elsewhere rather than ignore it, since an inert declaration is indistinguishable from a working one until someone needs it. It does not apply to a document's own line items, which ARE the document's content. :::