{"files": {"main.ts": "import {run} from './core.ts';\nimport {POLICY} from './policy.ts';\nexport function evaluate(payload: any): any { return run(payload, POLICY); }\n", "core.ts": "import {actionMatches, scopeMatches, notExpired} from './helpers.ts';\nexport function run(payload: any, policy: any): any {\n  const make = (allowed: boolean, reason: string, matched: any[]): any => ({allowed: allowed, reason: reason, matched: matched, audit: (!allowed && policy.audit_denials) ? [reason] : []});\n  const subject = payload.subject;\n  const resource = payload.resource;\n  if (subject.tenant !== resource.tenant) return make(false, 'tenant', []);\n  if (payload.now - payload.issued_at > policy.token_ttl) return make(false, 'token', []);\n  if (policy.suspended_denies && subject.suspended) return make(false, 'suspended', []);\n  if (policy.owner_bypass && subject.owner) return make(true, 'owner', []);\n  const roles = new Set<any>();\n  const initialRoles = subject.roles || [];\n  for (const r of initialRoles) roles.add(r);\n  if (policy.group_union) { const groups = subject.groups || []; for (const g of groups) roles.add(g); }\n  if (policy.inherit_roles) {\n    const parentsMap = payload.role_parents || {};\n    const queue: any[] = Array.from(roles);\n    while (queue.length > 0) {\n      const cur = queue.shift();\n      const parents = parentsMap[cur] || [];\n      for (const p of parents) { if (!roles.has(p)) { roles.add(p); queue.push(p); } }\n    }\n  }\n  const matched: number[] = [];\n  let hasAllow = false;\n  let hasDeny = false;\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    if (!actionMatches(g.action, payload.action, policy)) continue;\n    if (!scopeMatches(g.scope, resource.id, policy)) continue;\n    if (!notExpired(g.expires, payload.now, policy)) continue;\n    matched.push(i);\n    if (g.allow) hasAllow = true; else hasDeny = true;\n  }\n  if (matched.length === 0) return make(!!policy.default_allow, 'default', []);\n  const allowed = policy.deny_wins ? (hasAllow && !hasDeny) : hasAllow;\n  return make(allowed, 'grants', matched);\n}\n", "policy.ts": "export const POLICY: any = {\"action_casefold\": true, \"audit_denials\": true, \"default_allow\": true, \"deny_wins\": true, \"expiry_inclusive\": true, \"group_union\": true, \"inherit_roles\": true, \"owner_bypass\": true, \"resource_scope\": \"exact\", \"suspended_denies\": true, \"token_ttl\": 100, \"wildcard_action\": true};\n", "helpers.ts": "export function actionMatches(grantAction: any, requested: any, policy: any): boolean {\n  if (policy.wildcard_action && grantAction === '*') return true;\n  if (policy.action_casefold) return String(grantAction).toLowerCase() === String(requested).toLowerCase();\n  return grantAction === requested;\n}\nexport function scopeMatches(scope: any, resourceId: any, policy: any): boolean {\n  if (policy.resource_scope === 'prefix') return String(resourceId).startsWith(String(scope));\n  return scope === resourceId;\n}\nexport function notExpired(expires: any, now: any, policy: any): boolean {\n  if (expires === null || expires === undefined) return true;\n  if (policy.expiry_inclusive) return now <= expires;\n  return now < expires;\n}\n"}}