HANDOFF NOTE: project "permissions"

Scope: only the target project "permissions" is covered. All "Archived work record" entries belong to other projects (archive-*) and are discarded as unrelated.

FIXED CONTRACT (from the task statement)
evaluate(payload) -> {allowed: boolean, reason: string, matched: [grant_index], audit: [reason]}
- Subject: tenant, roles[], groups[], owner, suspended.
- Resource: tenant, id.
- Payload: now, issued_at, action, grants, role_parents.

Evaluation order:
1. Subject tenant differs from resource tenant: deny, reason "tenant".
2. now - issued_at > token_ttl: deny, reason "token".
3. suspended_denies true and subject suspended: deny, reason "suspended".
4. owner_bypass true and subject is owner: allow, reason "owner".
   Early returns (steps 1-4) have matched = [].
5. Initial roles = subject.roles, plus groups only if group_union is true.
6. If inherit_roles is true, expand roles transitively via role_parents, tolerating cycles.
7. A grant matches if all of the following hold:
   - Its role is in the role set.
   - Its action equals the requested action (ASCII case-insensitive iff action_casefold), or the grant action is "*" and wildcard_action is true.
   - Its scope equals resource.id (resource_scope = exact) or is a prefix of resource.id (resource_scope = prefix).
   - Its expires is null, or now <= expires when expiry_inclusive is true (otherwise now < expires).
8. matched = original indexes of all matching grants.
   - No matches: allowed = default_allow, reason "default".
   - Matches: reason "grants". With deny_wins, allowed only if at least one allow and no deny. Otherwise at least one allow suffices.
9. audit = [reason] only when the result is denied and audit_denials is true. Otherwise [].
10. Inputs are ordinary ASCII. The payload must not be mutated.

CURRENT CONFIG (after Updates 1-5)
- deny_wins = true
- expiry_inclusive = true
- inherit_roles = true
- owner_bypass = true
- action_casefold = true (Update 5 overrides Update 2's false)
- group_union = true (Update 5 overrides Update 2's false)
- resource_scope = "exact" (Update 4 overrides Update 2's "prefix")
- suspended_denies = true (Update 4 overrides Update 2's false)
- token_ttl = 100
- wildcard_action = true
- audit_denials = false (Update 5)
- default_allow = false (Update 5)

All 12 keys are set. No initial values for the keys appear to have been given before Update 1, but every key is now defined.

EXCEPTIONS / UNRESOLVED
- No project-specific exceptions were stated.
- Ambiguities the contract leaves open (implement conservatively and confirm if asked):
  - How a grant's allow/deny effect is represented. The grant field name is not given, so assume an effect-like field and confirm.
  - Which role field a grant uses, and the exact name of the scope and expires fields.
  - Whether the early-return token check uses a strict ">" (it does, per the contract: the token is denied only when the age exceeds the TTL).
  - What to do with matches that are all deny, or neither allow nor deny (treat as not allowed).
- A later update may change any keys. It replaces only the keys it names and leaves the others unchanged.

NEXT STEPS
- Apply future updates as key-level overrides on the config above.
- Ignore any archived records for other projects.
- Do not write code unless asked.