Current policy after all updates:
- 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: false
- default_allow: false

Implementation notes:
- Tenant check: subject.tenant against resource.tenant.
- The token check uses now - issued_at > token_ttl.
- Prefix scope means resource.id starts with grant.scope.
- Grant fields are assumed to be role, action, scope and expires.
- With matches, the result is allowed if any matched grant has an allow effect. Deny_wins is false, so any allow suffices. The effect is assumed to be grant.effect, defaulting to allow when it is not "deny".


<invoke name="x">
</invoke>

```json
{"files":{"main.ts":"import {run} from './core.ts';\nimport {POLICY} from './policy.ts';\nexport function evaluate(payload: any): any { return run(payload, POLICY); }\n","policy.ts":"export const POLICY: any = {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:false,default_allow:false};\n","helpers.ts":"export function lower(s: any): string { return String(s).replace(/[A-Z]/g, (c) => c.toLowerCase()); }\n","core.ts":"import {lower} from './helpers.ts';\nfunction fin(p: any, allowed: boolean, reason: string, matched: number[]): any {\n  return {allowed, reason, matched, audit: (!allowed && p.audit_denials) ? [reason] : []};\n}\nexport function run(payload: any, policy: any): any {\n  const subj = payload.subject || {};\n  const res = payload.resource || {};\n  if (subj.tenant !== res.tenant) return fin(policy, false, 'tenant', []);\n  if (payload.now - payload.issued_at > policy.token_ttl) return fin(policy, false, 'token', []);\n  if (policy.suspended_denies && subj.suspended) return fin(policy, false, 'suspended', []);\n  if (policy.owner_bypass && subj.owner) return fin(policy, true, 'owner', []);\n  let roles: string[] = (subj.roles || []).slice();\n  if (policy.group_union) roles = roles.concat(subj.groups || []);\n  if (policy.inherit_roles) {\n    const parents = payload.role_parents || {};\n    const seen = new Set<string>(roles);\n    const stack = roles.slice();\n    while (stack.length) {\n      const r = stack.pop() as string;\n      for (const q of (parents[r] || [])) {\n        if (!seen.has(q)) { seen.add(q); stack.push(q); }\n      }\n    }\n    roles = Array.from(seen);\n  }\n  const rs = new Set<string>(roles);\n  const grants = payload.grants || [];\n  const matched: number[] = [];\n  let anyAllow = false, anyDeny = false;\n  grants.forEach((g: any, i: number) => {\n    if (!rs.has(g.role)) return;\n    let aok: boolean;\n    if (policy.wildcard_action && g.action === '*') aok = true;\n    else if (policy.action_casefold) aok = lower(g.action) === lower(payload.action);\n    else aok = g.action === payload.action;\n    if (!aok) return;\n    const sc = String(g.scope === undefined || g.scope === null ? '' : g.scope);\n    const rid = String(res.id);\n    const sok = policy.resource_scope === 'exact' ? sc === rid : rid.startsWith(sc);\n    if (!sok) return;\n    const e = g.expires;\n    if (!(e === null || e === undefined || (policy.expiry_inclusive ? payload.now <= e : payload.now < e))) return;\n    matched.push(i);\n    if (g.effect === 'deny') anyDeny = true; else anyAllow = true;\n  });\n  if (matched.length === 0) return fin(policy, !!policy.default_allow, 'default', []);\n  const allowed = policy.deny_wins ? (anyAllow && !anyDeny) : anyAllow;\n  return fin(policy, allowed, 'grants', matched);\n}\n"}}
```