{"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: false,\n  audit_denials: false,\n  default_allow: false,\n  deny_wins: false,\n  expiry_inclusive: false,\n  group_union: false,\n  inherit_roles: false,\n  owner_bypass: false,\n  resource_scope: 'prefix',\n  suspended_denies: false,\n  token_ttl: 200,\n  wildcard_action: false\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\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.slice();\n  while (stack.length) {\n    const r = stack.pop() as string;\n    const ps = parents && Object.prototype.hasOwnProperty.call(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\nexport function run(payload: any, policy: any): any {\n  const subject = payload.subject;\n  const resource = payload.resource;\n  const now = payload.now;\n  const finish = (allowed: boolean, reason: string, matched: number[]) => ({\n    allowed,\n    reason,\n    matched,\n    audit: !allowed && policy.audit_denials ? [reason] : []\n  });\n  if (subject.tenant !== resource.tenant) return finish(false, 'tenant', []);\n  if (now - payload.issued_at > policy.token_ttl) return finish(false, 'token', []);\n  if (policy.suspended_denies && subject.suspended) return finish(false, 'suspended', []);\n  if (policy.owner_bypass && subject.owner) return finish(true, 'owner', []);\n  let initial: string[] = (subject.roles || []).slice();\n  if (policy.group_union) initial = initial.concat(subject.groups || []);\n  const roles = expandRoles(initial, payload.role_parents || {}, !!policy.inherit_roles);\n  const matched: number[] = [];\n  const grants = payload.grants || [];\n  const req = payload.action;\n  for (let i = 0; i < grants.length; i++) {\n    const g = grants[i];\n    if (!roles.has(g.role)) continue;\n    let actionOk = false;\n    if (policy.action_casefold) actionOk = lower(g.action) === lower(req);\n    else actionOk = g.action === req;\n    if (!actionOk && policy.wildcard_action && g.action === '*') actionOk = true;\n    if (!actionOk) continue;\n    const scopeOk = policy.resource_scope === 'exact'\n      ? g.scope === resource.id\n      : resource.id.startsWith(g.scope);\n    if (!scopeOk) continue;\n    if (g.expires !== null && g.expires !== undefined) {\n      const ok = policy.expiry_inclusive ? now <= g.expires : now < g.expires;\n      if (!ok) continue;\n    }\n    matched.push(i);\n  }\n  if (matched.length === 0) return finish(!!policy.default_allow, 'default', []);\n  const anyAllow = matched.some((i) => grants[i].allow === true);\n  const anyDeny = matched.some((i) => grants[i].allow !== true);\n  const allowed = policy.deny_wins ? anyAllow && !anyDeny : anyAllow;\n  return finish(allowed, 'grants', matched);\n}\n"}}