Skip to content
Open
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
50 changes: 49 additions & 1 deletion versions/1.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ Generators map each logical type to a physical column type. `text` is a large-ob
| Attribute | Effect |
| --- | --- |
| `audit: true` | adds the four standard audit columns, populated automatically |
| `history: true` | records every write as field-level deltas in a shadow history table (see [history](#history--the-change-trail)) |
| `multilingual: true` | makes string properties translatable (see [multilingual data](#multilingual-data)) |
| `label:` | a stored, read-only display name (see [label](#label--a-stored-display-name)) |
| `function:` | an explicit presentation role (see [function](#function--the-presentation-role)) |
Expand Down Expand Up @@ -522,6 +523,52 @@ different lifecycles on the same document.
> 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.

### history — the change trail

`audit: true` records only the **last** writer and time, in four columns of the row itself. Where a
domain has to answer *what changed, from what to what, by whom, when* — for every write, for years —
declare a history:

```yaml
- name: Contract
audit: true
history: true # every write is recorded as field-level deltas
fields:
- { name: id, type: integer, primaryKey: true }
- { name: amount, type: decimal }
```

The entity gains a **shadow history table** — a sibling of its own table, like the
[multilingual](#multilingual-data) language table — carrying one entry per property whose value
actually changed on a write: the property, its old value, its new value, who wrote it, when, and
whether the write came from a **user** or from the **system** (a roll-up total, a workflow
write-back, a recomputed document total). A create is recorded as `null -> value` and a delete as
`value -> null`, so the trail alone reconstructs the row at any point in its life. The record's own
form shows it as a read-only **History** panel.

The source matters as much as the delta. Once a total the application recomputed and an amount a
person typed sit in the same column, nothing downstream can tell them apart — and "who changed this"
is the first question asked of a trail.

> **Normative.**
> The shadow table is **append-only by construction**: a generator MUST NOT emit any create, update
> or delete path to it — not a service, not an endpoint, not a UI affordance. Append-only enforced by
> policy is not append-only.
> Every write path the generated data-access layer offers MUST append, including the targeted
> single-column and multi-column writes the system uses; a path that writes silently is worse than
> no trail, because the trail then reads as complete.
> An entry MUST record whether the write was a user write or a system write.
> Only properties whose value actually changed are recorded. Values that differ solely in
> representation (a decimal of a different scale, a translated overlay of a stored value) are NOT
> changes, and a generator MUST NOT record them as such.
> The primary key and the audit columns are NOT tracked — the key never changes and the audit columns
> restate what the entry already carries.
> A [scoped surface](#personal-and-partner-surfaces) that hides `sensitive:` fields MUST NOT be given
> a history it cannot filter: either the trail it exposes excludes those properties, or it exposes
> none. Leaking a hidden field's old and new values defeats the scoping exactly.
> Rows written outside the generated data-access layer — [seeds](#seeds), direct database writes —
> have no history, and a conforming tool documents that rather than implying completeness.

### hierarchy / leafOnly — tree entities

```yaml
Expand Down Expand Up @@ -1626,6 +1673,7 @@ One line per construct, linking into the chapters above.
| [`checks`](#checks--declarative-validations) | cross-field / cross-line validations |
| [`immutableWhen` / `immutable`](#immutablewhen--immutable--user-write-immutability) | reject user writes in a status / append-only |
| [`locksWithMaster`](#lockswithmaster--a-child-collection-that-outlives-its-masters-lock) | a child collection that stays writable while its master is locked |
| [`history`](#history--the-change-trail) | a shadow, append-only trail of every write: property, old and new value, who, when, user or system |
| [`hierarchy` / `leafOnly`](#hierarchy--leafonly--tree-entities) | tree entities, leaf-only references |
| [calculated fields](#calculated-fields) | server + UI-evaluated expressions, date helpers, call-outs |
| [`relations` / `composition`](#relations) | associations and master-detail compositions |
Expand Down Expand Up @@ -1671,7 +1719,7 @@ The following are parsed (or reserved) but not yet materialised by a generator;
- Reserved `function` values for upcoming presentations (`Board`, `Gantt`, `Timeline`).
- **`manyToMany`** — parsed but never materialised; the supported shape is the [explicit intermediate entity](#many-to-many).
- **Cross-model status names and stage scopes** — a nomenclature owned by another model is seeded there, so its stages and names cannot be resolved from the referencing file; such references are rejected with the numeric-id fallback named.
- Event-driven **document** generation (produce a whole document, rather than the mapped rows of [`posts`](#posts--derived-rows-on-an-event), on an event), a declarative state machine, and shadow audit-history entities (audit *columns* via `audit: true` ship today).
- Event-driven **document** generation (produce a whole document, rather than the mapped rows of [`posts`](#posts--derived-rows-on-an-event), on an event) and a declarative state machine.
- Arbitrary resolver-path task assignment beyond `assignee: personal`.
- **A fan-out that attaches the RECORD's document rather than the row's.** `forEach` + `attach: print`
attaches each row's own document, which is what a per-row document (a payslip) needs. The mirror
Expand Down