14 CookbookEN

14 · Cookbook — scenario-driven recipes

Normative source: the conformance corpora under capability-language-core-v1.md (Appendix B and the pinned vector files) This page is a readable guide. In case of conflict, the normative specification wins. Status: Preview — not for production use. Every recipe is a distilled corpus scenario; the vector id is cited so you can replay it.

TL;DR

A scenario-first summary of what the corpus pins: build the grant, write the operation, and reach the verdict the corpus asserts. Cross-reference the recipe's reason code with 11-reason-codes and the targeted page for the fine print.

1. Authorizing a database query

Grant: std/database-v1:query:SELECT with params: {"limit":100, "tables":["a","b"]}. Operation: std/database-v1:query:SELECT with params: {"limit":50, "tables":["a"]}. Verdict: allow — params are within bounds and a subset (combined-001, wildcard variant; positive entailment in params-001). Bump limit to 150 → deny("params_exceed_grant") (combined-002); request tables:["a","b","c"] → not_in_enum (params-004).

Quick checks to remember: a scalar grant number is an upper bound; a grant array is a membership set (06/07). Ask for tables:[] explicitly → deny("empty_bound_denies_class") is grant-side, not request-side.

2. Intersecting two sources

Source A: std/database-v1:query:SELECT, params: {"limit":100}. Source B: same id, params: {"limit":50}. Effective grant: {"limit":50} — the tighter bound wins (a minimum, not emptiness; 09-intersection) (intersect-004). allowlists intersect to their common members (intersect-001); disjoint allowlists → deny("no_overlap").

Delegation shape: three sources where one carries no grant → deny("no_overlap") (combined-008); zero sources → deny("absent_source") (intersect-007); a delegation hop declaring tables:[] propagates empty_bound_denies_class (combined-011).

3. Constraints and the residual channel

Grant + constraint: std/database-v1:query:SELECT with constraints: ["varwof/constraint-v1:max_rows:100"]; op params {"max_rows":500} → deny("max_rows:violated"); {"max_rows":100} → allow (violation is strict >; decide-027).

Recognized-but-unevaluated: grant with network:cidr:["192.0.2.0/24"] → allow_unresolved + unresolved:[<constraint>] (decide-020); same for the split cross-midnight time:window (decide-024). Two covering grants, one carrying network: and one time: → the union is carried (decide-035). The consumer must evaluate or confirm every entry or MUST deny (08-constraints §3).

Fail-closed constraint refusals: unknown (scheme,type) → deny("unknown_constraint") (decide-003; payments-002); out-of-grammar values → deny("invalid_constraint") (decide-019/-021/-022); out-of-domain op values → max_rows:violated (decide-031..034).

4. Multi-grant aggregation

Scenario: G1 {"limit":10}, G2 {"limit":100}, op {"limit":50} → allow — any-one-covers authorizes (decide-029). The reverse universe: G1 {"limit":10}, G2 {"limit":6}, op {"limit":50} → deny("params_exceed_grant") with the first covering grant in canonical order (deterministic; decide-030). Operation id model: absent/empty grants + absent op → capability_not_authorized (decide-016); valid grant + absent op id → missing_capability_id (decide-017).

5. Containment and chain authorization

Parent: query:SELECT, params:{"limit":100}. Child: query:SELECT, params:{"limit":50} → contains:true (contain-040); the reverse → params_not_narrower (contain-041); a different scheme → different_namespace (contain-004).

Fused chain: AuthorizeWithChain — empty chain → absent_source (ac-001); widening child hop → params_not_narrower (ac-006); broken hop that would hide an id-less operation → child_exceeds_parent (ac-007); ancestor constraint leaking only through Intersect → max_rows:violated (ac-009).

6. Evidence-side satisfaction (CLIP of the withheld CLC-E)

The evidence relations (§6.4/§10) are implemented and pinned by evidence-vectors.json (32) but the class is not claimed (13-conformance). If you write against them today, remember: Satisfy reports binary SATISFIED/UNSATISFIED; an internal unknown at the top level MUST be UNSATISFIED, never SATISFIED; and ActionId is the JCS digest of the declared material projection — undeclared fields must not affect it, missing declared material fields make the action non-matchable.

7. Recipe anti-patterns worth internalizing

Anti-pattern Why it fails Fix
Reading allow_unresolved as allow the enum values are distinct; residual obligations are additive evaluate/confirm, else deny (08)
Merging a string "1" with a number 1 in an enum intersection equality is JSON type-sensitive compare after §6.2 canonicalization (intersect-011/-012)
Letting a child drop an ancestor constraint constraints are outside Contains, composed by union chain Intersect brings ancestors into force (ac-009)
Normalizing a scalar number down to bool(1) boolean is exact, never numeric {"flag":1} vs grant {"flag":true} → deny (params-022)
Expecting an id-less op to be capability_not_authorized layer-1 op-ID codes propagate specifically missing_capability_id (decide-017)
Assuming verdict stability across minors on param_bounds meets the 1.10→1.15 history is not stable there declare the right revision, use the §12.1 gate (13)

← 13-conformance.md · → 15-glossary.md · related: 01-quickstart.md, 11-reason-codes.md