Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
23 changes: 23 additions & 0 deletions docs/spec/entities.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down