From 4f1c50e3ad6f12e0eae33d675e96f1dbc95d52be Mon Sep 17 00:00:00 2001 From: delchev Date: Thu, 13 Aug 2026 10:17:03 +0300 Subject: [PATCH] spec: a child collection can outlive its master's lock Site mirror: adds locksWithMaster (default true) and the normative boundary, including the half that is easy to miss - the prohibition covers the affordances a generator renders, not only the writes it accepts. Co-Authored-By: Claude Opus 5 --- docs/reference.md | 1 + docs/spec/entities.md | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/docs/reference.md b/docs/reference.md index e052dc5..bdea3e0 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -19,6 +19,7 @@ The quick lookup surface: one line and a minimal snippet per construct. For rule | [`checks`](/spec/entities#checks-declarative-validations) | cross-field / cross-line validations | | [`checks: kind: guard`](/spec/entities#kind-guard-a-precondition-over-an-aggregate) | a precondition over an aggregate: block, mark for a task, or reject | | [`immutableWhen` / `immutable`](/spec/entities#immutablewhen-immutable-user-write-immutability) | reject user writes in a status / append-only | +| [`locksWithMaster`](/spec/entities#lockswithmaster-a-child-collection-that-outlives-its-masters-lock) | a child collection that stays writable while its master is locked | | [`hierarchy` / `leafOnly`](/spec/entities#hierarchy-leafonly-tree-entities) | tree entities, leaf-only references | | [calculated fields](/spec/entities#calculated-fields) | server + UI-evaluated expressions, date helpers, call-outs | | [`relations` / `composition`](/spec/relations#relations) | associations and master-detail compositions | diff --git a/docs/spec/entities.md b/docs/spec/entities.md index 5c8166a..8f75b20 100644 --- a/docs/spec/entities.md +++ b/docs/spec/entities.md @@ -260,6 +260,29 @@ 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. +## 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: + +```yaml +- name: Invoice + immutableWhen: "Status == 3" # ISSUED: the document's own content freezes +- name: InvoiceAllocation + locksWithMaster: false # ...but money keeps being recorded against it + relations: + - { name: Invoice, kind: manyToOne, to: Invoice, composition: true, required: true } +``` + +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. + +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. + +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. +::: + ## hierarchy / leafOnly — tree entities ```yaml