{"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 {collectRoles, grantEffect, makeResult} 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 now: any = payload ? payload.now : undefined;\n  const issuedAt: any = payload ? payload.issued_at : undefined;\n  if (subject.tenant !== resource.tenant) return makeResult(policy, false, 'tenant', []);\n  if (now - issuedAt > policy.token_ttl) return makeResult(policy, false, 'token', []);\n  if (policy.suspended_denies && subject.suspended) return makeResult(policy, false, 'suspended', []);\n  if (policy.owner_bypass && subject.owner) return makeResult(policy, true, 'owner', []);\n  const roles: Set<string> = collectRoles(subject, policy, payload ? payload.role_parents : undefined);\n  const grants: any[] = payload && Array.isArray(payload.grants) ? payload.grants : [];\n  const requestedAction: any = payload ? payload.action : undefined;\n  const resourceId: string = resource.id === null || resource.id === undefined ? '' : String(resource.id);\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    if (!roles.has(grant.role)) continue;\n    const grantAction: any = grant.action;\n    let actionMatches = false;\n    if (policy.action_casefold) {\n      if (typeof grantAction === 'string' && typeof requestedAction === 'string' && grantAction.toLowerCase() === requestedAction.toLowerCase()) actionMatches = true;\n    } else if (grantAction === requestedAction) actionMatches = true;\n    if (!actionMatches && policy.wildcard_action && grantAction === '*') actionMatches = true;\n    if (!actionMatches) continue;\n    const scopeRaw: any = grant.scope !== undefined ? grant.scope : (grant.resource !== undefined ? grant.resource : (grant.resource_id !== undefined ? grant.resource_id : (grant.scope_id !== undefined ? grant.scope_id : '')));\n    const scope: string = scopeRaw === null || scopeRaw === undefined ? '' : String(scopeRaw);\n    if (policy.resource_scope === 'exact') {\n      if (scope !== resourceId) continue;\n    } else {\n      if (!resourceId.startsWith(scope)) 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) {\n        if (!(now <= expires)) continue;\n      } else {\n        if (!(now < expires)) continue;\n      }\n    }\n    matched.push(i);\n    const effect: string = grantEffect(grant);\n    if (effect === 'deny') hasDeny = true; else hasAllow = true;\n  }\n  if (matched.length === 0) return makeResult(policy, !!policy.default_allow, 'default', []);\n  const allowed: boolean = policy.deny_wins ? (hasAllow && !hasDeny) : hasAllow;\n  return makeResult(policy, allowed, 'grants', matched);\n}\n", "policy.ts": "export const POLICY: any = {\"action_casefold\": false, \"audit_denials\": false, \"default_allow\": false, \"deny_wins\": false, \"expiry_inclusive\": false, \"group_union\": false, \"inherit_roles\": false, \"owner_bypass\": false, \"resource_scope\": \"prefix\", \"suspended_denies\": false, \"token_ttl\": 200, \"wildcard_action\": false};\n", "helpers.ts": "export function makeResult(policy: any, allowed: boolean, reason: string, matched: number[]): any {\n  return {allowed: allowed, reason: reason, matched: matched.slice(), audit: (!allowed && policy && policy.audit_denials) ? [reason] : []};\n}\nexport function grantEffect(grant: any): string {\n  if (grant && typeof grant.effect === 'string') return grant.effect.toLowerCase() === 'deny' || grant.effect.toLowerCase() === 'denied' ? 'deny' : 'allow';\n  if (grant && typeof grant.type === 'string') return grant.type.toLowerCase() === 'deny' || grant.type.toLowerCase() === 'denied' ? 'deny' : 'allow';\n  if (grant && typeof grant.decision === 'string') return grant.decision.toLowerCase() === 'deny' || grant.decision.toLowerCase() === 'denied' ? 'deny' : 'allow';\n  if (grant && typeof grant.allow === 'boolean') return grant.allow ? 'allow' : 'deny';\n  if (grant && typeof grant.allowed === 'boolean') return grant.allowed ? 'allow' : 'deny';\n  return 'allow';\n}\nfunction parentsOf(roleParents: any, role: string): string[] {\n  if (!roleParents) return [];\n  if (Array.isArray(roleParents)) {\n    const out: string[] = [];\n    for (const entry of roleParents) {\n      if (Array.isArray(entry) && entry.length >= 2 && entry[0] === role) {\n        const p: any = entry[1];\n        if (Array.isArray(p)) { for (const x of p) if (typeof x === 'string') out.push(x); } else if (typeof p === 'string') out.push(p);\n      } else if (entry && typeof entry === 'object' && entry.role === role) {\n        const p: any = entry.parents !== undefined ? entry.parents : (entry.parent !== undefined ? entry.parent : entry.parents_list);\n        if (Array.isArray(p)) { for (const x of p) if (typeof x === 'string') out.push(x); } else if (typeof p === 'string') out.push(p);\n      }\n    }\n    return out;\n  }\n  if (typeof roleParents === 'object') {\n    const p: any = roleParents[role];\n    if (Array.isArray(p)) return p.filter((x: any) => typeof x === 'string');\n    if (typeof p === 'string') return [p];\n  }\n  return [];\n}\nexport function collectRoles(subject: any, policy: any, roleParents: any): Set<string> {\n  const roles: Set<string> = new Set<string>();\n  const initial: any[] = [];\n  if (subject && Array.isArray(subject.roles)) initial.push(...subject.roles);\n  if (policy && policy.group_union && subject && Array.isArray(subject.groups)) initial.push(...subject.groups);\n  for (const r of initial) if (typeof r === 'string') roles.add(r);\n  if (!policy || !policy.inherit_roles) return roles;\n  const queue: string[] = Array.from(roles);\n  let index = 0;\n  while (index < queue.length) {\n    const role: string = queue[index];\n    index++;\n    for (const parent of parentsOf(roleParents, role)) {\n      if (!roles.has(parent)) { roles.add(parent); queue.push(parent); }\n    }\n  }\n  return roles;\n}\n"}}