Approval
A capability marks which requests need a decision; the executor enforces that no such request executes without one.
Some actions need a human (or another authority) to sign off before they
run — a refund above a threshold, a destructive operation, a send to an
external party. A capability’s optional approval hook marks which
requests need that decision; the executor enforces it.
const refund = capability({
name: "payments.refund",
input: RefundInput,
authorize: async ({ principalId, input }) => canRefund(principalId, input.paymentId),
approval: ({ input }) => input.amount >= 500,
execute: async (input, context) => refundPayment(input, context),
});
const kaji = createKaji({
store: memoryStore(),
approve: async (request) => requestHumanApproval(request),
});
approval receives the validated input and principal, and returns a plain
boolean — it decides whether approval is required for this specific
request, not how the decision is made. approve, configured once on the
executor, decides how: it receives the capability name, principal ID,
validated input, and idempotency key, and returns (or rejects with) an
approval decision.
type ApprovalDecision = { approved: boolean; evidence?: unknown };
type ApproveHandler = (request: {
capability: string;
principalId: string;
input: unknown;
idempotencyKey: string;
}) => ApprovalDecision | Promise<ApprovalDecision>;
Fail-Closed
Approval is fail-closed: Kaji only proceeds past an approval: true request
on an explicit { approved: true }. Every other case settles rejected
instead of running execute:
- no
approvehandler was configured on the executor, approvereturned something other than{ approved: boolean },approvereturned{ approved: false },approvethrew or its promise rejected.
The optional evidence on an approved decision may be retained alongside
the execution record — use it to note what approval basis was used, such as
an approver ID or ticket reference.
Further Reading
- Execution for the full result and status contract
- Idempotency for how a rejected request still settles its idempotency key