HANDOFF: permissions

Authority: Only target project permissions requirements apply. All archived records concern unrelated projects and must be ignored. Current choices below incorporate Updates 1–5; superseded values are discarded.

CURRENT SETTINGS
{
  "deny_wins": true,
  "expiry_inclusive": true,
  "inherit_roles": true,
  "owner_bypass": true,
  "action_casefold": true,
  "group_union": true,
  "resource_scope": "exact",
  "suspended_denies": true,
  "token_ttl": 100,
  "wildcard_action": true,
  "audit_denials": false,
  "default_allow": false
}

FIXED INTERFACE
Implement evaluate(payload) returning:
{allowed: boolean, reason: string, matched: [grant_index], audit: [reason]}

Subject fields: tenant, roles[], groups[], owner, suspended.
Resource fields: tenant, id.
Payload includes subject, resource, now, issued_at, action, grants, role_parents.
Inputs are ordinary ASCII. Do not mutate inputs.

EVALUATION ORDER AND EARLY RETURNS
1. Different subject/resource tenants: deny, reason "tenant".
2. now - issued_at > 100: deny, reason "token". Age exactly 100 does not trigger this denial.
3. Suspended subject: deny, reason "suspended".
4. Owner subject: allow, reason "owner".

All early returns have matched=[].
Tenant and token denials take precedence over suspension and ownership. Suspension takes precedence over owner bypass; a suspended owner is denied.

ROLE RESOLUTION
Start with subject.roles union subject.groups.
Follow role_parents transitively to include inherited roles.
Tolerate cycles.

GRANT MATCHING
A grant matches only when all conditions hold:
- Its role is in the resolved roles.
- Its action equals the requested action ASCII case-insensitively, OR its action is "*", which matches any requested action.
- Its scope equals resource.id exactly; prefix matching is not enabled.
- Its expires is null, OR now <= expires. Equality at expiry is valid.

Gather EVERY matching grant's original index, preserving original grant order. Do not stop after the first allow or deny.

FINAL DECISION
- No matching grants: deny, reason "default", matched=[].
- Matching grants: reason "grants"; allow only if at least one matching grant is allow and no matching grant is deny.
- A matching deny defeats any matching allow.
- Matching grants without any allow do not allow access.

AUDIT
audit_denials=false: audit=[] on every result, including denials and early returns.

CONTINUATION
Future target-project updates replace only their explicitly listed keys and leave other current settings unchanged. Preserve the fixed interface and algorithm contract unless explicitly revised by authoritative target requirements.

UNRESOLVED / NOT SPECIFIED
- No pending target choice is recorded; all 12 settings have current values.
- Implementation language and concrete grant effect-field representation are not specified.
- Next update and later questions are not available; do not anticipate them.