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: 1 addition & 1 deletion .claude/docs/intent-layer.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ A single `app.intent` YAML file at a project root is the source of truth one alt

**Mailing a REPORT (`attach: { report, bind }`, [#6931](https://github.com/eclipse-dirigible/dirigible/issues/6931)):** the report-side sibling of `attach: print`. Where a dunning reminder carries the one invoice it is about, a **customer statement** carries a period of rows - a report, not a document - so a notify block may now name a declared report and bind its `parameters:` from the recipient row: `attach: { report: CustomerStatement, bind: { customer: name, fromDate: periodStart, toDate: periodEnd } }`. `bind:` values are a field or a one-hop `relation.field` of the record the message is about (the ROW inside a `forEach`), resolved through the same resolver a `{placeholder}` uses so the relation loads are shared locals. **Every parameter that declares an `initial` must be bound** - a parameter is bound on every call (#6911), so an unbound one rides its `initial`: one FIXED slice, identical for every recipient, which is the "whole ledger to one customer" failure mode (the mail goes out, the attachment IS a report, and nothing about it says whose). A parameter with no `initial` has a neutral any-value default (a date window bound, a `like` search), so omitting it means the whole range; a balance report's own `fromDate`/`toDate` are bindable and optional for the same reason. The render goes through a **`.print` template of the report's own** - seeded once at `doc/Templates/<Report>/Print/en/standard.print` for reports something actually mails, and developer-owned afterwards like the document scaffold. It is built from the columns the `.report` pass just resolved, so every `{{alias}}` is a key the query really SELECTs, and the **bound parameters are its header**: a table of rows never states which slice it is. Since `attach` now takes a map as well as a scalar, `NotificationIntent.attach` is typed `Object` and `getAttach()` reports only the KIND (`print` / `recordPrint` / `report`). **The schedule that mails it may live in the model that owns the REPORT** ([#7030](https://github.com/eclipse-dirigible/dirigible/issues/7030)): a schedule's cross-model source ([#6532](https://github.com/eclipse-dirigible/dirigible/pull/6532)) was `generate`-only, which left the statement mail with no legal home in a split suite - the invoices module reaches `Customer` only through `uses:`, and the customers module can neither name that report nor `uses:` back without a module cycle. The owner's resolved facts are now projected into the `EntityIntent` shape the notify machinery already consumes, so the recipient, the `{placeholder}`s and the `bind:` sources are fields of the cross-model row, checked at generation against the owner's `.model`. Refused at parse, each because only the owner can supply it: a `relation.field` hop off the source row (a foreign entity's relations are known only to its owner - the `generate map` rule), `{recordUrl}` (it routes into THIS application), and `attach: print`/`recordPrint` (the print feeder is generated where the document is). Details in the engine-intent guide's *mail a REPORT* and cross-model-schedule-notify sections.

**Lifecycle-aware aggregates (`stage:` + `scope:` + symbolic statuses, [#6645](https://github.com/eclipse-dirigible/dirigible/issues/6645)):** a status seed row classifies what it MEANS to the lifecycle — `stage: draft|live|cancelled|void`, metadata never emitted as a column — and a `reports:` entry declares `scope: all` or a stage name, emitted as `<alias>."<STATUS FK>" IN (<stage ids>)` ANDed onto its filter. With the nomenclature classified, an **aggregating** report **defaults to `live`** unless its dimensions/`filter` already reference the status, so a draft or voided document stops silently inflating every total (an existing model is byte-identical until it adopts `stage:`). Every site that names a status — `transitions[].from`/`setStatus`, a relation's `init:`, `setRelationField` `value:`, `abortOn.status`, a check's `status`/`setStatus`, `immutableWhen`, a posting's `event.when`, a report's `filter` — additionally accepts the **seeded name**, resolved to the id on the raw YAML tree before the typed mapping: an id is positional, so inserting a status mid-nomenclature used to silently retarget every guard authored against the old numbering (how a red-storno posting guarded on `Status == 8` stopped matching a Void that writes 9, leaving the ledger with a receivable for a document that no longer existed). A cross-model nomenclature can be neither stage-scoped nor named (its seeds live in the owner model) and says so; and when a lifecycle-carrying aggregate has neither a scope nor a status predicate nor a classified nomenclature, Generate emits a **warning** — now shown in the Intent Editor's own strip, not only the Builder shell's. Details in the engine-intent guide's lifecycle bullet.
**Lifecycle-aware aggregates (`stage:` + `scope:` + symbolic statuses, [#6645](https://github.com/eclipse-dirigible/dirigible/issues/6645)):** a status seed row classifies what it MEANS to the lifecycle — `stage: draft|live|cancelled|void`, metadata never emitted as a column — and a `reports:` entry declares `scope: all` or a stage name, emitted as `<alias>."<STATUS FK>" IN (<stage ids>)` ANDed onto its filter. With the nomenclature classified, an **aggregating** report **defaults to `live`** unless its dimensions/`filter` already reference the status, so a draft or voided document stops silently inflating every total (an existing model is byte-identical until it adopts `stage:`). Every site that names a status — `transitions[].from`/`setStatus`, a relation's `init:`, `setRelationField` `value:`, `abortOn.status`, a check's `status`/`setStatus`, `immutableWhen`, a posting's `event.when`, the `event.when` of a `notifications`/`integrations`/`outbound` entry ([#7289](https://github.com/eclipse-dirigible/dirigible/issues/7289)), a report's `filter` — additionally accepts the **seeded name**, resolved to the id on the raw YAML tree before the typed mapping: an id is positional, so inserting a status mid-nomenclature used to silently retarget every guard authored against the old numbering (how a red-storno posting guarded on `Status == 8` stopped matching a Void that writes 9, leaving the ledger with a receivable for a document that no longer existed). A cross-model nomenclature can be neither stage-scoped nor named (its seeds live in the owner model) and says so; and when a lifecycle-carrying aggregate has neither a scope nor a status predicate nor a classified nomenclature, Generate emits a **warning** — now shown in the Intent Editor's own strip, not only the Builder shell's. Details in the engine-intent guide's lifecycle bullet.

**The glue event axis: process-step events + non-HTTP inbound ([#6537](https://github.com/eclipse-dirigible/dirigible/issues/6537)):** a `notifications`/`integrations` `event:` binds either an entity lifecycle event or a **process step** — `onStepReached`/`onStepCompleted: { process, step }`. The step half adds no second delivery mechanism: `BpmnIntentGenerator` inserts a generated `JavaDelegate` at the step boundary (before the step for *reached*; after the writer/setter for *completed*, carrying the step's `next`) which loads the process's **trigger entity** by the id in the clear-D context and publishes its JSON on the entity's own topic plus a step suffix (`-step-<process>-<step>-reached|completed`), deferred to after commit — so the consumers are generated unchanged and every recipient path, `{placeholder}`, guard and forwarded body reads exactly as for a lifecycle event (`StepEventSupport` owns binding → entity → topic → emitter, deduplicated per moment). Rejected at parse: an unknown process/step, a step kind with no observable moment (only `userTask`/`serviceTask` have one), a process without a trigger. Alongside it, `inbound[]` declares **exactly one arrival** — `path:` (the `@Controller` webhook), `source: { queue | topic }` (a self-describing `MessageHandler`), or `source: { folder, cron }` (a `JobHandler` polling the drop folder: one record or an array per file, a file touched in the last few seconds is left for the next tick, every read file moves to `processed/`/`failed/`). All three save through the same generated repository — the arrival is a transport, not a second data path; a folder is polled, never watched, hence the mandatory cron there and its rejection elsewhere. **Mapping on arrival ([#6769](https://github.com/eclipse-dirigible/dirigible/issues/6769)):** deserializing the payload straight into the entity only works when it already *is* the entity, so an arrival may declare `accept: { type, version }` — a gate whose misses are **acknowledged and ignored** with a warning (202 on a webhook), never failed into redelivery — and `map:`, projecting envelope keys onto the record's own, where a value may be `{ lookup: <Entity>, by: <uniqueField>, from: <envelopeKey> }` resolving a **business key to a relation** (the requirement that alone forced a hand-written consumer). A non-unique `by:` is refused at Generate (it would silently pick one of several rows) and a lookup matching nothing **rejects** the arrival rather than storing a null FK; `ArrivalSupport` pre-renders the gate and each typed conversion, both keys work on all three arrivals, and omitting them generates byte-identically. **The departure half ([#6767](https://github.com/eclipse-dirigible/dirigible/issues/6767)):** `outbound[]` binds the same axis and emits on a queue or a topic — `to:` names exactly one of `queue`/`topic`, `payload:` is the same declared envelope `integrations:` takes, and the generated `<Name>Publisher.java` is a `MessageHandler` **subscribed to the record's own event topic** that re-publishes through `sdk.messaging.Producer`; because it is a subscriber the write is already committed, so a failed publish is logged and never fatal (no outbox, no exactly-once, no ordering — stated, not implied). The destination name is passed to the platform verbatim, so a departure inherits the `global:` external-contract marker ([#6766](https://github.com/eclipse-dirigible/dirigible/issues/6766)): unmarked names stay application-owned and tenant-prefixed, a `global:` one addresses another deployment.

Expand Down
Loading
Loading