HANDOFF: permissions

Authority and update handling
- Only target project permissions is authoritative. Ignore all archived records for other projects.
- Current choices below incorporate target Updates 1–5. Discard superseded settings.
- Future target updates replace only their named keys; other choices remain unchanged.
- No next update or later questions have been supplied.

CURRENT CONFIGURATION
deny_wins = false
inherit_roles = false
expiry_inclusive = false
owner_bypass = false
suspended_denies = false
resource_scope = "prefix"
group_union = false
action_casefold = false
default_allow = true
audit_denials = true
token_ttl = 200
wildcard_action = 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 UNDER CURRENT CHOICES
1. If subject.tenant differs from resource.tenant, deny with reason "tenant".
2. Otherwise, if now - issued_at > 200, deny with reason "token".
   Token age exactly 200 passes this check.
3. Suspension does not independently deny: suspended_denies is false.
4. Ownership does not independently allow: owner_bypass is false.
5. Effective roles are subject.roles only. Ignore groups and role_parents under current choices.
6. Evaluate every grant. A grant matches only when:
   - Its role belongs to the effective roles.
   - Its action equals the requested action, case-sensitively.
   - Its scope is a prefix of resource.id.
   - Its expires is null, or now < expires.
   Equality with expires is expired.
   "*" has no wildcard behavior, but can match a literal requested "*" through ordinary action equality.
7. Gather every matching grant's original index, in original grant order; do not stop at the first match.
8. No matches: allow with reason "default", matched=[].
9. Matches: reason "grants"; allow iff at least one matching grant allows. Matching denies do not override an allow. If no matching grant allows, deny.
10. Every denial has audit=[reason]; every allowance has audit=[].
    Early returns have matched=[].

FIXED CONTRACT TO RETAIN IF FUTURE UPDATES RE-ENABLE FEATURES
- Early-check order is tenant, token, suspension denial, owner allowance.
- If group_union becomes true, add subject.groups to initial roles.
- If inherit_roles becomes true, follow role_parents transitively and tolerate cycles.
- If action_casefold becomes true, use ASCII case-insensitive action equality.
- If wildcard_action becomes true, grant action "*" also matches any requested action.
- resource_scope="exact" requires scope equality; "prefix" requires prefix matching.
- expiry_inclusive=true permits now <= expires; otherwise require now < expires.
- deny_wins=true requires any matching allow and no matching deny; otherwise any matching allow suffices.
- With no matches, use default_allow and reason "default".
- audit_denials controls whether denied results receive [reason]; allowed results always receive [].

UNRESOLVED ITEMS
- No unresolved configuration choices were supplied.
- No implementation language, existing code, test results, or implementation progress was supplied.
- The concrete grant field encoding for allow/deny is not explicitly specified; do not invent an authoritative schema.