{"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) => c.toLowerCase()); }\nexport function expandRoles(initial: string[], parents: any, inherit: boolean): Set<string> {\n  const seen = new Set<string>();\n  const stack = initial.slice();\n  while (stack.length) {\n    const r = stack.pop() as string;\n    if (seen.has(r)) continue;\n    seen.add(r);\n    if (inherit && parents && Object.prototype.hasOwnProperty.call(parents, r)) {\n      const ps = parents[r];\n      if (Array.isArray(ps)) for (const p of ps) if (!seen.has(p)) stack.push(p);\n    }\n  }\n  return seen;\n}\n", "core.ts": "import {lower, expandRoles} from './helpers.ts';\n\nfunction isDeny(g: any): boolean {\n  const e = g.effect !== undefined ? g.effect : (g.type !== undefined ? g.type : g.allow);\n  if (typeof e === 'boolean') return g.effect === undefined && g.type === undefined ? !e : false;\n  if (typeof e === 'string') return lower(e) === 'deny';\n  return false;\n}\n\nexport function run(payload: any, policy: any): any {\n  const subject = payload.subject || {};\n  const resource = payload.resource || {};\n  const early = (allowed: boolean, reason: string) => ({\n    allowed, reason, matched: [] as number[],\n    audit: !allowed && policy.audit_denials ? [reason] : []\n  });\n  if (subject.tenant !== resource.tenant) return early(false, 'tenant');\n  if (payload.now - payload.issued_at > policy.token_ttl) return early(false, 'token');\n  if (policy.suspended_denies && subject.suspended) return early(false, 'suspended');\n  if (policy.owner_bypass && subject.owner) return early(true, 'owner');\n\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\n  const grants: any[] = payload.grants || [];\n  const act = policy.action_casefold ? lower(payload.action) : String(payload.action);\n  const matched: number[] = [];\n  let anyAllow = false;\n  let anyDeny = false;\n  grants.forEach((g, i) => {\n    if (!roles.has(g.role)) return;\n    const ga = String(g.action);\n    const actOk = (policy.action_casefold ? lower(ga) : ga) === act || (policy.wildcard_action && ga === '*');\n    if (!actOk) return;\n    const scope = String(g.scope);\n    const rid = String(resource.id);\n    const scopeOk = policy.resource_scope === 'prefix' ? rid.startsWith(scope) : scope === rid;\n    if (!scopeOk) return;\n    const ex = g.expires;\n    if (!(ex === null || ex === undefined || (policy.expiry_inclusive ? payload.now <= ex : payload.now < ex))) return;\n    matched.push(i);\n    if (isDeny(g)) anyDeny = true; else anyAllow = true;\n  });\n\n  let allowed: boolean;\n  let reason: string;\n  if (matched.length === 0) {\n    allowed = !!policy.default_allow;\n    reason = 'default';\n  } else {\n    reason = 'grants';\n    allowed = policy.deny_wins ? (anyAllow && !anyDeny) : anyAllow;\n  }\n  return { allowed, reason, matched, audit: !allowed && policy.audit_denials ? [reason] : [] };\n}\n"}}