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