Approval workflows
Approval workflows
Approval workflows gate sensitive create, update, and delete operations. Norbital uses a write-then-lock model: the change is applied immediately, the affected records are locked , and an approval request tracks the review. On approval the change stands and the locks clear; on rejection the records stay — locked, with the values the mutation wrote — and the workspace’s own liveness predicate (`approval_id is null`) keeps them out of view until revised.
approval item whose `flow` returns `approveBy(…).thenBy(…)` or `noApproval`, plus `superceded_by`. See Policies.Request lifecycle
gated mutation
│
▼
write applies + records locked + approval request created
│
├── approved ────────► change stands, locks cleared
├── rejected ────────► request settled; row stays locked
│ until a revision replaces it
├── request changes ─► still active, locks stay
│
└── supersede ───────► approved, as though the superseding
team had decided every remaining step
└── withdraw ────────► requestor withdraws, request settled Statuses
The review moves in stages — one team per stage, alternatives in one stage — and the request follows its statuses:
ONGOINGAPPROVEDREJECTEDCHANGES_REQUESTEDCONFLICTEDWITHDRAWN
The request status vocabulary that reports filter on:
ONGOINGAPPROVEDREJECTED— the requestor may also withdraw a pending requestCHANGES_REQUESTEDCONFLICTEDWITHDRAWN
approve reject request_changes supersede — approve, reject, request_changes, or supersede — and only a pending request can be decided. `supersede` requires a reason; an approved request whose graph no longer applies is marked conflicted.Locks
To prevent conflicting edits while a request is active, the records the mutation touched stay locked with the request’s id:
- Record writes — the written row plus every row in the graph is held behind the request id (
approval_id) - Pending deletions — a delete is also currently a row; it is locked the same way (
approval_id)
The exact set the review is deciding on is recorded in the request’s locked_record_refs — the refs of every record the review holds, root and related rows alike.
Supersede and revision
- Staged review — ports alternate within one stage and stages run in sequence; authoring
approveBy('Team').thenBy('Other Team')appends the next stage on top of the current one. - Superseding teams — the approval can name
superceded_by: teams allowed to finish every remaining step at once. Administrators always hold that capability when the config allows it. - Conflict — approving re-runs the hooks and compares the rebuilt graph against the reviewed one; a changed graph is marked
CONFLICTEDinstead of silently committing.
On approval and rejection
Because the change is already live, approval performs no execution — the system clears the lock and the request is settled: the change stands. Rejection settles the request with the decision recorded; the row stays locked and a follow-up revision replaces it.
When to use approvals
- Financial changes above a threshold
- Changes to records with compliance implications
- Edits to sensitive master data
- Mutations that should clearly separate requester and approver roles
Recommended operating rules
- Use approvals selectively on high-risk mutations only.
- Pair approvals with clear notifications so reviewers know when action is needed.
- Test lock behavior on linked or nested records before rolling out a workflow widely.
- Remember that gated changes go live immediately; a rejection does not restore the previous values.