{"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 {asciiLower} from './helpers.ts';\nexport function run(payload: any, policy: any): any {\n  const subject: any = (payload && payload.subject) || {};\n  const resource: any = (payload && payload.resource) || {};\n  const make = (allowed: boolean, reason: string, matched: number[]): any => ({allowed: !!allowed, reason: reason, matched: matched.slice(), audit: (!allowed && policy.audit_denials) ? [reason] : []});\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 roleSet = new Set<any>();\n  const initial: any[] = [];\n  const baseRoles: any[] = Array.isArray(subject.roles) ? subject.roles : [];\n  for (const r of baseRoles) { if (!roleSet.has(r)) { roleSet.add(r); initial.push(r); } }\n  if (policy.group_union) {\n    const groups: any[] = Array.isArray(subject.groups) ? subject.groups : [];\n    for (const g of groups) { if (!roleSet.has(g)) { roleSet.add(g); initial.push(g); } }\n  }\n  if (policy.inherit_roles) {\n    const parents = payload.role_parents;\n    const parentsOf = (role: any): any[] => {\n      if (parents === null || parents === undefined) return [];\n      if (Array.isArray(parents)) {\n        const out: any[] = [];\n        for (const e of parents) {\n          if (Array.isArray(e) && e.length >= 2 && e[0] === role) out.push(e[1]);\n          else if (e && typeof e === 'object') {\n            const child = e.role !== undefined ? e.role : (e.child !== undefined ? e.child : e.from);\n            const par = e.parent !== undefined ? e.parent : (e.inherits !== undefined ? e.inherits : e.to);\n            if (child === role && par !== undefined) out.push(par);\n          }\n        }\n        return out;\n      }\n      if (typeof parents === 'object') {\n        const v = parents[role];\n        if (Array.isArray(v)) return v.slice();\n        if (v !== null && v !== undefined) return [v];\n      }\n      return [];\n    };\n    const queue: any[] = initial.slice();\n    let idx = 0;\n    while (idx < queue.length) {\n      const cur = queue[idx++];\n      for (const p of parentsOf(cur)) {\n        if (!roleSet.has(p)) { roleSet.add(p); queue.push(p); }\n      }\n    }\n  }\n  const grants: any[] = Array.isArray(payload.grants) ? payload.grants : [];\n  const resourceId: any = resource.id !== undefined ? resource.id : (resource.name !== undefined ? resource.name : '');\n  const matched: number[] = [];\n  let hasAllow = false;\n  let hasDeny = false;\n  for (let i = 0; i < grants.length; i++) {\n    const grant: any = grants[i] || {};\n    const grantRole = grant.role !== undefined ? grant.role : grant.role_name;\n    if (!roleSet.has(grantRole)) continue;\n    const grantAction: any = grant.action;\n    if (grantAction === null || grantAction === undefined) continue;\n    let actionOk = false;\n    if (String(grantAction) === '*' && policy.wildcard_action) actionOk = true;\n    else if (policy.action_casefold) actionOk = asciiLower(String(grantAction)) === asciiLower(String(payload.action));\n    else actionOk = String(grantAction) === String(payload.action);\n    if (!actionOk) continue;\n    let scope: any = grant.scope !== undefined ? grant.scope : (grant.resource_id !== undefined ? grant.resource_id : (grant.resourceId !== undefined ? grant.resourceId : (typeof grant.resource === 'string' ? grant.resource : (grant.target !== undefined ? grant.target : (grant.id !== undefined ? grant.id : undefined)))));\n    if (scope !== undefined && scope !== null) {\n      if (policy.resource_scope === 'prefix') { if (!String(resourceId).startsWith(String(scope))) continue; }\n      else { if (String(scope) !== String(resourceId)) continue; }\n    }\n    const expires: any = grant.expires !== undefined ? grant.expires : (grant.expiry !== undefined ? grant.expiry : (grant.expires_at !== undefined ? grant.expires_at : null));\n    if (expires !== null && expires !== undefined) {\n      if (policy.expiry_inclusive) { if (!(payload.now <= expires)) continue; }\n      else { if (!(payload.now < expires)) continue; }\n    }\n    matched.push(i);\n    let rawEff: any = grant.effect !== undefined ? grant.effect : (grant.decision !== undefined ? grant.decision : (grant.outcome !== undefined ? grant.outcome : (grant.type !== undefined ? grant.type : grant.kind)));\n    let isDeny = false;\n    if (rawEff !== undefined && rawEff !== null) isDeny = asciiLower(String(rawEff)) === 'deny';\n    else if (grant.deny === true) isDeny = true;\n    else if (grant.allow === false) isDeny = true;\n    else isDeny = false;\n    if (isDeny) hasDeny = true; else hasAllow = 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 = {\"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};\n", "helpers.ts": "export function asciiLower(s: string): string { return String(s).replace(/[A-Z]/g, (c: string) => c.toLowerCase()); }\n"}}