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 docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ The quick lookup surface: one line and a minimal snippet per construct. For rule
| [`relations` / `composition`](/spec/relations#relations) | associations and master-detail compositions |
| [`uses`](/spec/relations#multi-model-applications) | reuse entities owned by another intent model |
| [`processes`](/spec/processes#processes) | workflows: user tasks, decisions, waits, boundary timers |
| [task assignment](/spec/processes#task-assignment) | route a user task to a role, the record owner, or a relation walk |
| [`abortOn`](/spec/processes#aborton-cancel-the-instance-on-a-terminal-status) | cancel the running instance when the document reaches a terminal status |
| [`function: Attachment` / `Snapshot`](/spec/entities#attachments-and-snapshots) | a Files panel / immutable versioned printed copies |
| [`forms`](/spec/processes#forms) | task data-entry pages |
Expand Down Expand Up @@ -164,4 +165,3 @@ The following are parsed (or reserved) but not yet materialised by a generator;
- **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.
- **`manyToMany`** — parsed but never materialised; the supported shape is the [explicit intermediate entity](/spec/relations#many-to-many).
- Event-driven document generation (produce a document on an event), a declarative state machine, and shadow audit-history entities (audit *columns* via `audit: true` ship today).
- Arbitrary resolver-path task assignment beyond `assignee: personal`.
16 changes: 14 additions & 2 deletions docs/spec/processes.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,19 @@ trigger: { onCreate: Order, businessKey: orderNo, businessKeyStrategy: timestamp

### Task assignment

A user task's `assignee` is a role / candidate-group name, or the literal **`assignee: personal`** to route the task to the **record owner's** inbox (requires the trigger entity to declare a `personal:` relation — see [scoped surfaces](/spec/surfaces)).
A user task's `assignee` is a role / candidate-group name, or the literal **`assignee: personal`** to route the task to the **record owner's** inbox (requires the trigger entity to declare a `personal:` relation — see [scoped surfaces](/spec/surfaces)), or a **relation walk** off the trigger record:

```yaml
- name: approve
kind: userTask
args:
assignee: { path: employee.manager, fallback: manager }
form: ApproveRequest
```

Every segment of `path` is a **to-one relation** — the first of the trigger entity, each further one of the previous target — and the walk ends at an entity that declares `identity`, which is what maps a record to a login. A **cross-model** relation may only be the **last** segment: a projection carries the target's own properties but not its relations, so there is nothing to walk on from there. A conforming generator validates every hop when the file is read, so a dangling segment is reported then rather than when the process runs.

`fallback` is **required** and names the candidate group. The walk is resolved when the task is reached, not when the process starts — so a relation an earlier step of the same process set is visible — and when it resolves to nobody (a null hop, a missing record, a blank identity) the task is created **unassigned** and the fallback group can still claim it. That is what makes the unresolvable case total: a resolver path can never mint a task nobody can see.

## forms

Expand Down Expand Up @@ -175,4 +187,4 @@ actions:

- [Declarative glue](/spec/glue) — event-driven glue (`wait`, `timeout`, triggers) is generated as integration code alongside the process.
- [Presentation](/spec/presentation) — the document view a process's status pill and inline task list appear on.
- [Scoped surfaces & roles](/spec/surfaces) — `assignee: personal` and the roles a candidate group maps to.
- [Scoped surfaces & roles](/spec/surfaces) — `assignee: personal`, the `identity` a walk ends at, and the roles a candidate group maps to.
4 changes: 2 additions & 2 deletions docs/spec/surfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ entities:
A scope's safety is by **construction, not by a filter**: the scoped controller only ever queries the caller's own rows, and a sensitive field is on an allow-list the scoped serialiser never includes. A field hidden only in the UI is cosmetic; `sensitive` is a server-side guarantee.
:::

A user task can also be routed to the record owner's inbox with the literal `assignee: personal`, which resolves the owner through the `personal:` relation (see [processes](/spec/processes#task-assignment)).
A user task can also be routed to the record owner's inbox with the literal `assignee: personal`, which resolves the owner through the `personal:` relation, or to whoever a relation walk off the record names — `assignee: { path, fallback }`, whose walk likewise ends at an `identity`-declaring entity (see [processes](/spec/processes#task-assignment)).

## permissions

Expand All @@ -46,5 +46,5 @@ Generates a deduplicated set of **roles**. It deliberately does **not** emit URL
## See also

- [Entities & fields](/spec/entities) — `sensitive` as a field attribute, and the entity a scope attaches to.
- [Processes & forms](/spec/processes) — `assignee: personal` task routing.
- [Processes & forms](/spec/processes) — `assignee: personal` and resolver-path task routing.
- [Relations & multi-model](/spec/relations) — the to-one relation a scope is declared on.