if (invoice.amount < limit) next to the can() call. At that moment the product’s headline guarantee quietly halves: alfiz-verify proves a gate exists at every surface, but it cannot see the predicate, cannot check it is present, and cannot check it is consistent across forty call sites.
The condition seam closes that gap without becoming a rules engine. The catalog declares that a key requires a condition; the tooling enforces that one is present; the predicate itself stays your code, over your data, in your process.
Declaring
Gating
Every gate for the key must pass acondition, the final AND of the decision:
- A gate without the condition throws
MissingConditionError, a programming error on the same footing asUnknownPermissionError: map it to 500, never 403. The caller forgot the predicate; the principal was not denied. - The condition runs only after the rows allow. A principal the rows already deny never pays for (or triggers) your predicate.
- A false condition is a deny, observed as one in metrics, with the rows that would have allowed kept for attribution.
- Async is fine on the async surfaces (
can,require, the session). On the synchronous snapshot the predicate must return a plain boolean, so resolve your data before the check or useclient.can. A Promise there throws. - Visibility is unaffected.
canAnyandholdsignore the declaration: a button may exist because authority exists; the gate still decides. Keys without the declaration may still pass aconditionvoluntarily. - Conditions never cross a wire. The
/v1/checkoperation answersrequiresConditionkeys with an error rather than a half-checked yes, because a predicate over resource state is meaningful only where the resource lives.
Verified in CI
alfiz-verify gains the missing-condition rule: a literal gate call site for a requiresCondition key that visibly carries no condition fails the build.