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
2 changes: 2 additions & 0 deletions .claude/docs/intent-layer.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ A single `app.intent` YAML file at a project root is the source of truth one alt

**Effective-dated register lookup (`resolves:`, [#6712](https://github.com/eclipse-dirigible/dirigible/issues/6712)):** a to-one filled from the register row whose validity period covers a date the record carries — the driver from a vehicle-assignment register on the violation date, the price from the list in force on the order date, the approver from the org assignment on the request date. `resolves: - { name, event: { onCreate|onUpdate: <Record>, when? }, set: <to-one>, from: <Register>, match: { <registerProperty>: <recordProperty> }, between: { start?, end?, value }, outcome?: <string field>, found?/notFound?/ambiguous?: { setStatus } }` → a `resolves` glue descriptor → a generated `@Component MessageHandler` on the record's event topic. **All three outcomes are first-class:** exactly one covering row fills the relation, while zero and more-than-one both leave it unset — an automation that silently picks one of two candidates is worse than none, so an ambiguous register goes back to a human. Each outcome may route by `setStatus` (id or seeded name) and the attempt is observable: `outcome:` stamps `found`/`notFound`/`ambiguous` into a string field a list filter or a process `decision` can read. The copied value is derived, not authored — the register must carry exactly ONE to-one to the same target as `set:`, and zero or two is a validation error; a record that already carries the relation is skipped (a manual correction is never overwritten); the relation + outcome + status go out in ONE targeted `updateProperties`; a bound may be omitted on either side (open-ended), the end is inclusive, and a date-only bound covers its whole day. v1 is same-model and `onCreate`/`onUpdate` only. **The register may be queried by the DOCUMENT and may hand back its scalars ([#7025](https://github.com/eclipse-dirigible/dirigible/issues/7025)):** a `match` value and `between.value` may be a **to-one path off the record** (`salesInvoice.customer.priceList`), which is what makes "price this line from the list in force on the header's date" expressible at all - the alternative, copying the header's key and date down with `dependsOn`, is a UI-time copy that a REST create, a `generates:` create-from or a schedule fan-out never runs, so the lines produced by exactly the automated paths stayed unpriced while the interactive path looked correct. Every segment but the last is a to-one, the last is a field or a to-one whose FK is compared, hops load once per distinct path prefix, and a cross-model relation may only be the last hop. Alongside it **`copy: { <register field>: <record field> }`** writes the scalars the covering row NAMES (the price, the rate) - on `found` only, per field, skipping a field the record already carries a value in, riding the result write - and `set:` may point at the **register itself** for a value-bearing register, where the resolved value is the covering row's own key. Details in the engine-intent guide's `resolves:` bullet.

**A status the flow writes is the flow's column ([#7339](https://github.com/eclipse-dirigible/dirigible/issues/7339)):** an entity whose `function: EntityStatus` relation is moved by a `processes:` `setRelationField` step has that column refused on every generated REST surface - a create or update that sets or changes it answers **409** `'Status' changes through the workflow, not a direct edit`. Until then the column was an ordinary writable property, so a plain `PUT {"Status": 3}` moved a document straight into APPROVED with the flow bypassed end to end: no check ran, no task was ever raised, nothing the flow charges was charged, and the record read approved while the accounts knew nothing about it. `immutableWhen:` cannot close it - it locks the way OUT of a final status, while this is the way IN, from a DRAFT that is mutable by definition - and a `transitions[]` button is an ADDITIONAL guarded endpoint beside the plain PUT, not instead of it. The column is derived state owned by the flow, the same class as an `aggregate:`/roll-up target, and the flow's own writers lose nothing: a `setRelationField` step and a `transitions[]` endpoint reach the repository through the targeted `updateProperty`/`updateProperties` primitives, never through a controller. Two things are deliberately NOT refused: an **absent** value (a caller PUTs the fields its form edits, so the stored status is kept rather than erased) and a create carrying exactly the declared `init:`. A `transitions:`-only status stays writable on purpose - the button is a hand move over a status a person may also hold otherwise, and the construct that guards the other hand writes is `lifecycle:`, whose refusal would be observable from nowhere if the plain write were closed.

**The declarative state machine (`lifecycle:`, [#6714](https://github.com/eclipse-dirigible/dirigible/issues/6714)):** an entity may declare the WHOLE set of legal status edges over its `function: EntityStatus` nomenclature — `lifecycle: { edges: [{ from: DRAFT, to: [ISSUED, CANCELLED] }, ...] }`, either side a seeded name or an id — and every status write is validated against it. Until then the status machinery was point constructs (`init:`, a `transitions:` button's own guard, a workflow `setRelationField`, a check's rejection) with nothing stating which moves were legal at all, so any writer that was not a transition button could move a document from any status to any other. Enforcement is in the generated **repository** — the one choke point every writer passes through (`update`, `updateWithoutEvent`, and `updateProperties`, which the transition controller's `updateProperty` and the workflow setters route through) — rejecting an unmodeled move with 400 and a message naming both statuses; with `init:` declared, a record cannot be CREATED mid-lifecycle either. At parse time the graph is what the other status sites are held to: a `transitions:` entry's `from`→`setStatus` pair must be a declared edge (a button is presentation over the graph), and a status written by a workflow step or forced by a check must be one some edge reaches — so a reject path transiting through an approved status fails when the intent is read. There is no `on:` key (the graph is always over the EntityStatus relation, and YAML reads a bare `on` as `true`, so it is refused rather than silently dropped), and a cross-model nomenclature is declared where it is seeded. Details in the engine-intent guide's state-machine bullet.

**A field's label, and a label the TENANT'S COUNTRY resolves (`label:` / `countryLabels:`, [#6424](https://github.com/eclipse-dirigible/dirigible/issues/6424)):** a field may now declare its display `label:` - emitted as the property's own `widgetLabel`, which every generated surface renders and the en-US catalog is seeded from, so an acronym or a unit (`nationalId` as "National ID", not the humanized "National Id") is expressed in the intent instead of hand-edited into a catalog the next Generate overwrites. Alongside it, `countryLabels: { BG: ЕГН, DE: Steuer-ID }` declares variants resolved from the **tenant's country** (`DIRIGIBLE_APPLICATION_COUNTRY`, ISO 3166-1 alpha-2, tenant-overridable in the application shell's Tenant Configuration) rather than from the UI language - which term a national identifier goes by is a property of the company, so keying it off the language catalogs is wrong in both directions at once (the Bulgarian-reading user of a German company gets the local term; the English-reading accountant of a Bulgarian one gets the generic one). It also cannot live in a catalog mechanically: the shared `i18n.js` does not load catalogs at all in the default language. So the variants travel as a language-independent overlay - a structured `widgetCountryLabels` on the property (Map, hence `.model`-only), flattened at UI generation into the `countryLabels` object `config.js` carries, keyed by the very translation key the views bind, which `T()` consults ahead of both i18next and the baked fallback in every language. An app declaring no variant issues no extra request and generates byte-identically. A key that is not a country is refused at parse time (it could never match a tenant); report column labels are deliberately out of scope, a column alias being its SQL alias too.
Expand Down
Loading
Loading