// 1. Tenant isolation. A forbid, so no permit written later can override it. @id("tenant-isolation") forbid (principal, action, resource) unless { resource.tenant == context.tenant && context.user in context.tenant }; // 2. Read vs write tools. Agent AND user AND tenant must hold the grant. @id("read-tools") permit (principal, action == Action::"read", resource) when { resource in principal.read_groups && resource in context.user.read_groups && resource in context.tenant.read_groups }; @id("write-tools") permit (principal, action in [Action::"write", Action::"destroy"], resource) when { resource in principal.write_groups && resource in context.user.write_groups && resource in context.tenant.write_groups }; // 3. Destroy needs an approver from the same tenant who is not the requester and // who holds the write grant for the tool: someone who could have done it alone. @id("destructive-needs-approval") forbid (principal, action == Action::"destroy", resource) unless { context has approved_by && context.approved_by != context.user && context.approved_by in context.tenant && resource in context.approved_by.write_groups }; // 4. An approval is for one call. The approver saw a hash of the exact call; the call // that runs must have the same hash. An approval presented without one binds nothing. @id("approval-matches-call") forbid (principal, action, resource) when { context has approved_by && (if context has approval_for then context.approval_for != context.request_hash else true) }; // 5. An approval expires. It is checked when the call runs, not only when it was // given. An approval with no expiry never started. @id("approval-is-fresh") forbid (principal, action, resource) when { context has approved_by && (if context has approval_expires then context.approval_expires <= context.now else true) }; // 6. Request scope. The tool must be inside the scope this hop holds. // A forbid, so no permit can widen it. Whatever is not a read needs the write // scope, so an action added later starts out strict. @id("request-scope") forbid (principal, action, resource) unless { if action == Action::"read" then resource in context.delegated_read_groups else resource in context.delegated_write_groups }; // 7. Delegation depth: at most two hand-offs. The relay beside each agent increments // depth; the agent cannot set it. The limit here is illustrative. @id("delegation-depth") forbid (principal, action, resource) when { context.depth > 2 };