Approvals — how it works
Approvals are node-native: two NodeTypes the mesh compiles live from Source/, plus a node
that contributes the menu entry. Nothing here is a compiled module, so installing or removing the
package changes the mesh immediately — no image, no restart.
The three moving parts
| Node | What it is |
|---|---|
Approvals/Approval |
The satellite TYPE. One approval = one {document}/_Approval/{id} node: who asked whom, what for, by when, and the decision. Its Overview shows the record (with Approve / Reject for the named approver of a pending request); its Thumbnail is the card the inline list renders. |
Approvals/Desk + Approvals/Workspace |
The document-anchored surfaces — RequestApproval (the form) and Approvals (the live list). The ONE Approvals/Workspace instance serves every document; the target arrives per render. |
Approvals/RequestApprovalMenu |
A UiContribution node: the Request Approval entry on every node's menu, contributed as DATA. |
Why one desk instead of every hub
The retired module registered its form, its list and its menu entry onto every per-node hub of
the mesh (ConfigureDefaultNodeHub) — something no in-mesh type can do, and something that put
a mesh-wide feature in the platform's build graph.
The desk needs none of it. Each area resolves its document per render:
- an explicit
?doc=Space/Path, - else the layout-area reference id an embedding page passes —
Controls.LayoutArea("Approvals/Workspace", "Approvals", documentPath), - else the desk node's own
documentPath.
So a document page embeds the approvals list, and the menu entry links to the form with the
current node in ?doc=. Precedence and normalization are pinned by ApprovalDeskTests.
Embedding the list on another type
Controls.LayoutArea(ApprovalDocs.DeskPath, ApprovalDeskAreas.ApprovalsArea, documentPath)
.WithShowProgress(false)
Probe first if the package may be absent — a query-index existence check on Approvals/Workspace,
never a hub probe of a missing node (which costs the full activation timeout). The platform's
markdown overview does exactly this.
What stays platform-level
The Approval record and the _Approval → annotations satellite mapping live in the
platform (MeshWeaver.Mesh.Contract). Storage placement is resolved from the path SEGMENT, not
from the node type, so approvals land in the right table and keep deserializing on a mesh where
this package is not installed. Access is the platform's ordinary path-based check: an approval sits
inside its document's partition and is readable exactly where the document is.
Invariants worth knowing
- A decision is terminal, and only the named approver makes it.
Decidere-resolves the approval inside the owner's update lambda and refuses a second decision, so a stale tab cannot overwrite one that already happened. - The activity entry is
{document}/_Activity/{id}typedActivity. That exact shape is what the running-activities stripe queries and what the Activity views bind to. - Instants render in the viewer's zone; a due DATE never does. The 15th is the 15th in every zone — converting a due date shifts the deadline by a day for half the world.
Run the Tests area on either type to execute the suites (CI asserts them green).