Verification

Multi-step approval workflows & signatures

Verification turns a plain CRUD write into a governed change. Instead of a record updating instantly, the change can be parked as a pending verification that one or more approvers must accept before it takes effect — with an optional signature and a full before/after diff.

Approvals are modelled as a flow graph: a sequence (or branching set) of steps, each with its own approvers/verifiers. Nucleus advances the instance through the graph, recording every decision. This is how you implement four-eyes review, compliance sign-off, or editorial publishing.

Two route families are exposed: one for acting on verifications (approve/reject/status) and one for managing the flow definitions themselves.

Behaviour#

Global defaults for how verification behaves. Individual flows can refine these, but these set the baseline posture.

config.nucleus.json — verification
1{2  "verification": {3    "enabled": true,4    "autoResetOnRejection": true,5    "requireSignatureByDefault": false,6    "diffTrackingEnabled": true,7    "endpoints": { "enabled": true, "basePath": "/verifications" },8    "flowEndpoints": { "enabled": true, "basePath": "/verification-flows" }9  }10}
enabledbooleanOptional

Master switch. When true the verification service initialises and the approval middleware can intercept guarded entity writes.

Defaultfalse
autoResetOnRejectionbooleanOptional

When a flow is rejected, automatically restart it from step 1 rather than leaving it terminally rejected. Keeps a resubmission loop simple — the requester fixes the issue and the flow runs again from the top.

Defaulttrue
requireSignatureByDefaultbooleanOptional

Require an explicit signature on each approval decision by default. Use for compliance contexts where an approver must actively attest, not just click a button.

Defaultfalse
diffTrackingEnabledbooleanOptional

Capture a structured before/after diff of the proposed change so reviewers see exactly what will change, field by field, before approving.

Defaulttrue

Endpoints#

Verification exposes two independently-toggleable route families with configurable prefixes.

endpoints{ enabled?: boolean; basePath?: string }Optional

Routes for acting on verifications — list pending items, read status, approve and reject. This is what reviewer UIs talk to.

Default{ enabled: true, basePath: "/verifications" }
flowEndpoints{ enabled?: boolean; basePath?: string }Optional

Routes for managing flow definitions — create and edit the step graphs that drive approvals. Typically admin-only.

Default{ enabled: true, basePath: "/verification-flows" }

Under the hood — the flow engine#

A flow is a node graph persisted across several tables; an in-progress approval is an instance that materialises concrete requirements as it advances. This is what the VerificationFlowPage builder writes and the VerificationService runs.

flow graphsteps · verifiers · notifications · edgesOptional

A flow stores three node kinds — step, verifier and notification — plus edges, verifier configs and notification rules/recipients/channels (seven tables in total). saveFlow replaces the whole graph atomically; publishFlow flips is_draft off so only published flows can start.

starting an instanceverification_instancesOptional

startFlow creates one active instance per entity record (rejecting a second concurrent one), sets current_step_order = 1, then materialises requirements for step 1. Step order comes from the step nodes sorted by step_order.

requirement materialisationverification_requirementsOptional

For each verifier wired into the current step a pending requirement row is created. A role verifier with all_must_approve is expanded into one requirement per user holding that role (so everyone must sign off); otherwise it's a single requirement keyed by user or role.

decidingapprove / reject + signature + diffOptional

decide matches the caller to a pending requirement of the current step — directly by user id, or by membership of the required role — then records the decision with an optional signature_id (when require_signature is set) and the captured before/after diff. all_must_approve aggregates every requirement before the step advances; a rejection ends or (with autoResetOnRejection) restarts the flow.

notification triggerson_flow_started / on_step_reachedOptional

As an instance starts and reaches each step, the engine fires the notification nodes wired to that step (following edges one or two hops, through verifier nodes) — which is how an approval flow emails or notifies the right reviewers without you writing glue code.

From the frontend#

With the endpoints on, the client generates these type-safe actions. The first three power a reviewer's approval inbox; the FLOW_* actions power an admin flow-builder. See Ready-Made Actions for the catalog.

recipe — a reviewer approval inbox
1const actions = useApiActions();2 3// 1 · load everything awaiting my decision4actions.VERIFICATION_PENDING.start({5  onAfterHandle: (r) => setQueue(r.data),6});7 8// 2 · approve or reject the current step of one record9const decide = (entity: string, id: string, approved: boolean) =>10  actions.VERIFICATION_DECIDE.start({11    payload: {12      entity_name: entity,13      entity_id: id,14      decision: approved ? "approve" : "reject",15    },16    onAfterHandle: refreshQueue,17  });
VERIFICATION_PENDINGGET · /verification/pending

Every record awaiting the current user's decision.

VERIFICATION_STATUSGET · /verification/status/:entity_name/:entity_id

Where a specific record sits in its approval graph.

VERIFICATION_DECIDEPOST · /verification/decide/:entity_name/:entity_id

Approve or reject the current step, with an optional signature when required.

FLOW_SAVE / FLOW_PUBLISH / FLOW_DELETE·/verification/flows[/:flow_id]

Create or edit a flow graph, publish it live, or remove it — the building blocks of an admin flow editor.

Related sections