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

Grant fields are assumed to be role, action, effect, scope and expires. The effect is read from `effect` (or `allow`) and treated as allow unless it equals "deny".

The owner check is assumed to compare subject.owner with resource.id when owner is a string, and to use it directly when it is a boolean. It is applied to the early-return owner allow.

```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 = {\n  deny_wins: true,\n  expiry_inclusive: true,\n  inherit_roles: true,\n  owner_bypass: true,\n  action_casefold: true,\n  group_union: true,\n  resource_scope: 'exact',\n  suspended_denies: true,\n  token_ttl: 100,\n  wildcard_action: true,\n  audit_denials: true,\n  default_allow: true\n};\n","helpers.ts":"export function lower(s: any): string { return String(s).replace(/[A-Z]/g, (c) => String.fromCharCode(c.charCodeAt(0) + 32)); }\nexport function expandRoles(initial: string[], parents: any, inherit: boolean): Set<string> {\n  const seen = new Set<string>(initial);\n  if (!inherit) return seen;\n  const stack = [...initial];\n  while (stack.length) {\n    const r = stack.pop() as string;\n    const ps = parents && Array.isArray(parents[r]) ? parents[r] : [];\n    for (const p of ps) {\n      if (!seen.has(p)) { seen.add(p); stack.push(p); }\n    }\n  }\n  return seen;\n}\n","core.ts":"import {lower, expandRoles} from './helpers.ts';\n\nfunction fin(allowed: boolean, reason: string, matched: number[], policy: any): any {\n  return { allowed, reason, matched, audit: (!allowed && policy.audit_denials) ? [reason] : [] };\n}\n\nexport function run(payload: any, policy: any): any {\n  const subject = payload.subject || {};\n  const resource = payload.resource || {};\n  const now = payload.now;\n  if (subject.tenant !== resource.tenant) return fin(false, 'tenant', [], policy);\n  if (now - payload.issued_at > policy.token_ttl) return fin(false, 'token', [], policy);\n  if (policy.suspended_denies && subject.suspended) return fin(false, 'suspended', [], policy);\n  if (policy.owner_bypass) {\n    const o = subject.owner;\n    const isOwner = typeof o === 'boolean' ? o : (o !== undefined && o !== null && o === resource.id);\n    if (isOwner) return fin(true, 'owner', [], policy);\n  }\n  let initial: string[] = [...(subject.roles || [])];\n  if (policy.group_union) initial = initial.concat(subject.groups || []);\n  const roles = expandRoles(initial, payload.role_parents, policy.inherit_roles);\n  const grants: any[] = payload.grants || [];\n  const matched: number[] = [];\n  let anyAllow = false, anyDeny = false;\n  const act = policy.action_casefold ? lower(payload.action) : payload.action;\n  grants.forEach((g, i) => {\n    if (!roles.has(g.role)) return;\n    const ga = policy.action_casefold ? lower(g.action) : g.action;\n    const actionOk = ga === act || (policy.wildcard_action && g.action === '*');\n    if (!actionOk) return;\n    const sc = g.scope;\n    const scopeOk = policy.resource_scope === 'exact' ? sc === resource.id : (typeof sc === 'string' && String(resource.id).startsWith(sc));\n    if (!scopeOk) return;\n    const e = g.expires;\n    const expOk = e === null || e === undefined || (policy.expiry_inclusive ? now <= e : now < e);\n    if (!expOk) return;\n    matched.push(i);\n    const eff = g.effect !== undefined ? g.effect : g.allow;\n    const isDeny = eff === 'deny' || eff === false;\n    if (isDeny) anyDeny = true; else anyAllow = true;\n  });\n  if (matched.length === 0) return fin(!!policy.default_allow, 'default', [], policy);\n  const allowed = policy.deny_wins ? (anyAllow && !anyDeny) : anyAllow;\n  return fin(allowed, 'grants', matched, policy);\n}\n"}}
```