TARGET PROJECT: permissions — HANDOFF NOTE

Current authoritative configuration (from target Updates 1-5 only; later updates supersede earlier values for the same key; archived projects are unrelated and excluded):
{
  "deny_wins": false,
  "expiry_inclusive": false,
  "inherit_roles": false,
  "owner_bypass": false,
  "action_casefold": false,
  "group_union": false,
  "resource_scope": "prefix",
  "suspended_denies": false,
  "token_ttl": 200,
  "wildcard_action": false,
  "audit_denials": true,
  "default_allow": true
}

Fixed interface:
evaluate(payload) returns {allowed:boolean, reason:string, matched:[grant_index], audit:[reason]}.
Subject has tenant, roles[], groups[], owner, suspended. Resource has tenant, id. Payload has now, issued_at, action, grants, role_parents. Inputs are ordinary ASCII. Do not mutate inputs.

Required evaluation order and rules:
1. If subject and resource tenants differ: deny, reason "tenant", matched=[].
2. Else if now - issued_at > token_ttl (now - issued_at > 200): deny, reason "token", matched=[].
3. Else if suspended_denies is enabled and subject is suspended: deny, reason "suspended", matched=[]. With current setting suspended_denies=false, this check does not deny.
4. Else if owner_bypass is enabled and subject is owner: allow, reason "owner", matched=[]. With current setting owner_bypass=false, this bypass does not apply.
These early returns have matched=[].

Role and grant matching:
- Initial roles are subject.roles. Add subject.groups only if group_union is enabled; currently group_union=false, so groups are not added.
- If inherit_roles is enabled, follow role_parents transitively and tolerate cycles; currently inherit_roles=false, so no inheritance.
- A grant matches only if: its role is in the resolved roles; its action equals the requested action (ASCII case-insensitive only if action_casefold is enabled; currently false, so case-sensitive) OR its action is "*" when wildcard_action is enabled (currently false, so "*" has no wildcard effect); its scope equals resource.id for resource_scope="exact" or is a prefix of it for resource_scope="prefix" (currently "prefix"); and it is unexpired: expires is null, or now <= expires if expiry_inclusive is enabled, otherwise now < expires (currently expiry_inclusive=false, so require now < expires).
- Collect every matching grant's original index in matched.

Decision:
- If no grants match: allowed = default_allow (currently true), reason "default".
- If grants match: reason "grants". If deny_wins is enabled, allow only if at least one allow and no deny; currently deny_wins=false, so any allow among matches suffices.
- audit is [reason] only on denied results when audit_denials is enabled (currently true), otherwise [] (including all allowed results).

Exceptions / unresolved items:
- No target-project exceptions or unresolved configuration keys remain in the provided updates. All 12 keys above are resolved.
- Superseded values (e.g., earlier action_casefold=true, group_union=true, resource_scope="exact", suspended_denies=true) and all archive-* project settings are discarded and must not be applied to permissions.