Skip to content
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ manifesto — is published at **[intentfile.org](https://intentfile.org)** (sour
| --- | --- |
| [1.0](versions/1.0.md) | superseded |
| [1.1](versions/1.1.md) | superseded - added `aggregates`, `checks: kind: guard`, `posts`, the notify block (`attach: print`, `forEach`), `pattern` and header-mediated `dependsOn` ([proposal](proposals/0001-keyed-aggregates-guard-checks-and-row-posting.md)) |
| [1.2](versions/1.2.md) | current - adds lifecycle stages + report `scope` + status names, `locksWithMaster`, `generates` prompted input and computed lines, `defaultValue` row seeding, print-template row filtering ([proposal](proposals/0002-print-template-row-filtering.md)), the scope boundary and the authoring assistant's honesty rule ([proposal](proposals/0003-the-scope-boundary.md)), and aligns document numbering with deployed practice |
| [1.2](versions/1.2.md) | current - adds lifecycle stages + report `scope` + status names, `locksWithMaster`, `generates` prompted input and computed lines, `resolves` (effective-dated register lookup), `defaultValue` row seeding, print-template row filtering ([proposal](proposals/0002-print-template-row-filtering.md)), the scope boundary and the authoring assistant's honesty rule ([proposal](proposals/0003-the-scope-boundary.md)), and aligns document numbering with deployed practice |

## Structure

Expand Down
32 changes: 32 additions & 0 deletions versions/1.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This version adds to [1.1](1.1.md):
- **Lifecycle-aware aggregation** — [`stage`](#stage--what-a-status-means-to-the-lifecycle) classifies what a status seed *means* (draft / live / cancelled / void), a report's [`scope`](#lifecycle-scope) states which lifecycle rows it counts (an aggregating report over a stage-classified nomenclature defaults to the live rows), and [every site that names a status accepts the seeded name](#status-references--name-not-number) instead of its positional id.
- **[`locksWithMaster`](#lockswithmaster--a-child-collection-that-outlives-its-masters-lock)** — a composition child collection that stays writable while its master is locked (settlement against an issued invoice).
- **[`generates` prompted input](#prompted-input--prompt)** — a create-from action may collect the values the source cannot derive before the create; and [`generates.items`](#generates--create-from) gains a **computed list form** building synthetic lines from expressions over the source.
- **[`resolves`](#resolves--fill-a-relation-from-a-register-valid-on-a-date)** — fill a to-one from the register row valid on a date the record carries, with `found` / `notFound` / `ambiguous` as three first-class, observable outcomes.
- **[`defaultValue` semantics completed](#defaultvalue--field-defaults)** — a default is the column default, the reason a `required` field is not demanded from the caller, and the value a new row is seeded with in the UI.
- **[Print-template row filtering](#row-filtering--filter--match)** — `filter` / `match` render one items collection into several purpose-grouped tables (earnings beside deductions, debit beside credit).
- **[The scope boundary](#the-scope-boundary)** — what a conforming file deliberately never models (protocol, algorithm, statutory form), each category's designated hand-off, and the honesty a conforming authoring assistant owes when a requirement falls outside the format.
Expand Down Expand Up @@ -1458,6 +1459,36 @@ posts:

`posts` differs from [`generates`](#generates--create-from) in intent: `generates` creates ONE document from a user action, while `posts` emits N mapped rows automatically and idempotently on an event.

### resolves — fill a relation from a register valid on a date

Set a to-one from the row of a **register** whose validity period covers a date the record carries. The register says "X applied to Y from A to B" — a vehicle assignment, a price list, a contract in force, an org assignment — and the record carries the match key(s) and the date:

```yaml
resolves:
- name: identifyDriver
event: { onCreate: Fine } # onCreate or onUpdate, optional `when` guard
set: driver # the to-one of Fine this fills
from: VehicleAssignment # the register
match: { vehicle: vehicle } # register property <- record property (one or more)
between: { start: validFrom, end: validTo, value: violationAt }
outcome: resolution # optional string field: found / notFound / ambiguous
found: { setStatus: IDENTIFIED }
notFound: { setStatus: UNRESOLVED }
ambiguous: { setStatus: UNRESOLVED }
```

Nothing else in the format reaches this shape: [`dependsOn`](#relations) is an authoring-time copy matched by equality, a [`decision`](#processes) condition is a single comparison, and a `setField` step writes a constant. Without it every application hand-writes the same query-and-classify code.

**Normative.** A lookup MUST declare exactly one of `onCreate` / `onUpdate` naming a declared entity; `onDelete` MUST be rejected, since there is no record left to fill. `set` MUST name a to-one relation of that entity, `from` a declared register entity, and `match` at least one pair whose left side is a property of the register and whose right side a property of the record. `between.value` MUST name a date field of the record; `between.start` and `between.end` name date fields of the register and MAY each be omitted, in which case that side of the period is open. The end of a period is **inclusive**, and a bound expressed as a date (rather than an instant) covers its whole day.

**Normative.** The register MUST carry exactly one to-one relation to the entity `set` points at; that relation is the value the lookup copies. Zero or more than one MUST be rejected — a register offering a choice of columns to copy is a modelling ambiguity, and guessing one would defeat the construct's purpose.

**Normative.** All three outcomes are first-class and MUST be distinguished. Exactly one covering row fills the relation. No covering row (`notFound`) and more than one covering row (`ambiguous`) MUST both leave the relation unset: a conforming generator MUST NOT choose between candidate rows. Each outcome MAY carry a `setStatus` routing the record, which requires the record to declare a `function: EntityStatus` relation and accepts a [status name](#status-references--name-not-number) as well as an id.

**Normative.** The attempt MUST be observable. When `outcome` names a `string` field of the record, that field MUST be stamped with `found`, `notFound` or `ambiguous`, so unresolved records form a filterable worklist a person can finish and a process [`decision`](#processes) can branch on the result. A conforming generator SHOULD additionally log the keys and the date it checked.

**Normative.** A record that already carries the relation MUST be skipped, so a manual correction is never overwritten and a re-delivered event is a no-op. The resolved relation, the outcome and the status MUST be written as one targeted update of those columns only, leaving every other column of the record — and any concurrent write to it — untouched.

### Guardrails

- **Curated vocabulary, not a general DSL.** Real logic is a `script` step or a hand-written hook — the escape hatch is non-negotiable.
Expand Down Expand Up @@ -1656,6 +1687,7 @@ One line per construct, linking into the chapters above.
| [`postings`](#postings--source-document-to-ledger) | declarative source-document to balanced-document posting |
| [`aggregates`](#aggregates--keyed-cross-entity-totals) | keyed cross-entity totals materialised into their own entity |
| [`checks: kind: guard`](#kind-guard--a-precondition-over-an-aggregate) | a precondition over an aggregate: block, mark for a task, or reject |
| [`resolves`](#resolves--fill-a-relation-from-a-register-valid-on-a-date) | fill a to-one from the register row valid on the record's date |
| [`posts`](#posts--derived-rows-on-an-event) | derived ledger rows emitted idempotently on an event |
| [`personal` / `partner`](#personal-and-partner-surfaces) | per-user and per-partner row-scoped surfaces |
| [`seeds`](#seeds) | initial data, CSV-backed sets, translations |
Expand Down