Human approvals
gate() records an approval request and waits for a matching decision. Your application decides what approval, rejection, and expiration mean.
Require an explicit approval#
The complete quickstart checks the review result with a schema requiring outcome: 'approve'. Every other result takes the unpublished branch. A rejected gate can complete normally, so a node dependency alone never authorizes an action.
// Fragment inside the node after a gate:const approved = z.object({ outcome: z.literal('approve') })if (!approved.safeParse(ctx.results.review).success) { return { published: false }}// Only this branch may request the publication effect.Deliver a decision#
import { decision, pendingApprovals } from '@swirls/looms/approval'const reviews = await looms.project(runId, pendingApprovals)const pending = reviews.items.find((review) => review.status === 'pending')if (!pending) throw new Error('No pending approval')await looms.signal(runId, [decision(pending.approvalId, 'reject')], { idempotencyKey: 'decision-request-from-your-application',})Authenticate the reviewer, authorize access to this run and approval, and validate the action on the server. Use a unique key per logical request and reuse it for retries of that request. A new key represents a new request and does not resolve conflicting decisions.
Concurrent and duplicate decisions#
Correlate waits by approvalId, not only the event type. Concurrent approvals can otherwise satisfy the wrong wait. Serialize decisions on the server and reject decisions for already resolved approvals. A read of pendingApprovals followed by a write is not an atomic compare-and-set across multiple clients.
The runtime validates event shape, but does not implement your reviewer identity, permission policy, or a complete approval lifecycle service. Do not allow clients to submit arbitrary approval.decided events.
Expiration and late replies#
gate() has no automatic timeout option. To expire approvals, define an application workflow or custom thread that records a deadline, waits for a correlated decision or timer, and fails closed on timeout. Record expiration explicitly and reject late decisions at the gateway. Emitting approval.timed_out does not satisfy a gate waiting for approval.decided.
Review UI#
Render only pending items from pendingApprovals. Show the proposed action and relevant inputs, offer approve and reject, disable controls while a request is in flight, and display failures. The server must still enforce the decision policy. Reconnect by reading the recorded state rather than assuming a click succeeded.