The ApprovalPolicyInput type
ApprovalPolicyInput wherever a requestability declaration expects a policy field, either on a scope type in the catalog or inside a role’s requestable block.
Stage types
Auto-approval predicates
Anauto stage evaluates a condition against the requester’s subject closure, using the same can() machinery that powers every check in Alfiz. There is no separate rules engine. If the predicate passes, the stage is recorded as auto-approved and the workflow immediately advances to the next stage. If it fails, the stage abstains without recording a decision, and the workflow advances to the next stage. Auto stages accelerate workflows; they never deny.
{ type: "in_group"; groupId: string }
Passes when the requester is a member of the given group (directly or through group inheritance).
{ type: "in_org"; orgId: string }
Passes when the requester belongs to the given organization in the directory.
{ type: "member_of"; subject: SubjectId }
Passes when the requester’s subject closure contains the given subject. Works with any valid subject including implicit groups like
directs:<uid> or orgof:<uid>, for example { type: "member_of", subject: "directs:team-lead-uid" } auto-approves everyone who reports directly to a particular manager.{ type: "holds_pattern"; pattern: PermissionPattern }
Passes when the requester’s effective access already intersects the given pattern. Grounded in the catalog’s concrete keys; a fully-revoked requester never passes, and a pattern matching no catalog key never passes. Negative-always-wins holds here exactly as it does in
can().Named approvers
Anamed_approvers stage requires approval from any subject who holds an unexpired grant of the designated role, either directly or through any closure member.
To approve a
named_approvers stage, a user must hold an unexpired grant of the designated role, either on their own subject or through a group or org they belong to. Expired grants do not confer approval authority.Management layers
Amanagement stage routes the request to the requester’s manager, or layers transitive managers upward in the reporting hierarchy. The chain is resolved by walking reporting edges at evaluation time.
layers defaults to 1, which means the requester’s direct manager. layers: 2 means the manager’s manager, and so on. Where the reporting chain is shorter than layers, the topmost manager approves.
"2 layers up" still means exactly that after a hierarchy reorganization: the new two-layers-up manager becomes the approver at their next evaluation.
Policy attachment
- On a role
- On a scope type
Attach an approval policy directly to a role’s
requestable block using createRole. The policy applies to every request for that role, regardless of scope.A complete three-stage policy example
This policy uses all three stage types in order: first try to auto-approve, then fall through to a named application owner, and finally escalate to a skip-level manager.- Stage 0 runs immediately at submission. If the requester’s effective access already includes
projects.admin.*, the stage auto-approves and the workflow advances to stage 1. If it does not, the stage abstains and the workflow still advances to stage 1, because the auto stage never blocks. - Stage 1 shows the request to any user holding the
project-ownerrole, who sees this request in their approver queue and may approve or deny it. - Stage 2 starts only after stage 1 approves, when the skip-level manager receive the request. Their approval writes the grant row.
Requestability does not inherit
requestable exists in exactly two places: on a scope type (ScopeTypeInput.requestable, whose policy lives under a policy key) and on a role (RoleInput.requestable, whose stages sit directly on the block). There is no third place, and there is no inheritance of any kind.
A policy on docs.folder does not propagate to docs.doc, even though documents nest inside folders. Each scope type declares its own requestable block or is not requestable at all. Nothing is requestable by default.
A requestable scope type must declare at least one approval stage. A
policy with an empty stages array is a validation error, surfaced by lintCatalog at build time and by assertPolicyResolvable at request submission.Just-in-time access
The canonical just-in-time (JIT) access pattern combines three features:requireExpiry: trueon the requestable role or scope type, so every request must propose an expiry; open-ended requests are rejected.proposedExpiresAtin the request, where the requester proposes how long they need the access; themaxDurationMscap enforces an upper bound.can.fresh()at enforcement points, which bypasses the subject-side cache so the grant expiry is evaluated at exactly the moment of the check, not against a potentially stale snapshot.