{"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 {asciiEqual, collectRoles, grantActionMatches, grantEffectIsAllow} from './helpers.ts';\n\nexport function run(payload: any, policy: any): any {\n  const p: any = payload || {};\n  const subject: any = p.subject || {};\n  const resource: any = p.resource || {};\n  const now: any = p.now;\n  const issuedAt: any = p.issued_at;\n  const requestedAction: any = p.action;\n  const grants: any[] = Array.isArray(p.grants) ? p.grants : [];\n  const resourceId: any = resource.id !== undefined ? resource.id : resource.resource_id;\n  const finish = (allowed: boolean, reason: string, matched: number[]): any => ({allowed: allowed, reason: reason, matched: matched, audit: (!allowed && policy.audit_denials) ? [reason] : []});\n  if (subject.tenant !== resource.tenant) return finish(false, 'tenant', []);\n  if ((now - issuedAt) > 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  const roles: Set<string> = collectRoles(subject, policy, p.role_parents);\n  const matched: number[] = [];\n  let hasAllow = false;\n  let hasDeny = false;\n  for (let i = 0; i < grants.length; i++) {\n    const g: any = grants[i] || {};\n    const role: any = g.role;\n    if (!roles.has(role)) continue;\n    if (!grantActionMatches(g.action, requestedAction, policy)) continue;\n    const scope: any = g.scope !== undefined ? g.scope : (g.resource !== undefined ? g.resource : (g.resource_id !== undefined ? g.resource_id : g.resourceId));\n    if (policy.resource_scope === 'prefix') {\n      if (typeof scope !== 'string' || typeof resourceId !== 'string' || !resourceId.startsWith(scope)) continue;\n    } else {\n      if (scope !== resourceId) continue;\n    }\n    const expires: any = g.expires !== undefined ? g.expires : (g.expires_at !== undefined ? g.expires_at : (g.expiry !== undefined ? g.expiry : null));\n    if (expires !== null && expires !== undefined) {\n      if (policy.expiry_inclusive) { if (!(now <= expires)) continue; } else { if (!(now < expires)) continue; }\n    }\n    matched.push(i);\n    if (grantEffectIsAllow(g)) hasAllow = true; else hasDeny = true;\n  }\n  if (matched.length === 0) return finish(!!policy.default_allow, 'default', []);\n  const allowed: boolean = policy.deny_wins ? (hasAllow && !hasDeny) : hasAllow;\n  return finish(allowed, 'grants', matched);\n}\n", "policy.ts": "export const POLICY: any = {\"deny_wins\": false, \"expiry_inclusive\": false, \"inherit_roles\": false, \"owner_bypass\": false, \"action_casefold\": false, \"group_union\": false, \"resource_scope\": \"prefix\", \"suspended_denies\": false, \"token_ttl\": 200, \"wildcard_action\": false, \"audit_denials\": false, \"default_allow\": false};\n", "helpers.ts": "export function asciiLower(value: any): string {\n  const s = String(value);\n  let out = '';\n  for (let i = 0; i < s.length; i++) {\n    const c = s.charCodeAt(i);\n    out += (c >= 65 && c <= 90) ? String.fromCharCode(c + 32) : s[i];\n  }\n  return out;\n}\n\nexport function asciiEqual(a: any, b: any, casefold: boolean): boolean {\n  if (casefold) return asciiLower(a) === asciiLower(b);\n  return a === b;\n}\n\nexport function grantActionMatches(grantAction: any, requestedAction: any, policy: any): boolean {\n  if (asciiEqual(grantAction, requestedAction, !!policy.action_casefold)) return true;\n  if (grantAction === '*' && policy.wildcard_action) return true;\n  return false;\n}\n\nexport function grantEffectIsAllow(grant: any): boolean {\n  if (grant) {\n    const effect: any = grant.effect !== undefined ? grant.effect : (grant.decision !== undefined ? grant.decision : (grant.type !== undefined ? grant.type : grant.kind));\n    if (typeof effect === 'string') return asciiLower(effect) === 'allow';\n    if (typeof grant.allow === 'boolean') return grant.allow;\n    if (typeof grant.allowed === 'boolean') return grant.allowed;\n    if (typeof grant.deny === 'boolean') return !grant.deny;\n  }\n  return true;\n}\n\nfunction parentsOf(role: string, roleParents: any): string[] {\n  const out: string[] = [];\n  if (!roleParents) return out;\n  if (Array.isArray(roleParents)) {\n    for (const entry of roleParents) {\n      if (Array.isArray(entry) && entry.length >= 2 && entry[0] === role) {\n        const v: any = entry[1];\n        if (Array.isArray(v)) { for (const x of v) if (typeof x === 'string') out.push(x); } else if (typeof v === 'string') out.push(v);\n      } else if (entry && typeof entry === 'object') {\n        const child: any = entry.role !== undefined ? entry.role : (entry.child !== undefined ? entry.child : entry.from);\n        if (child === role) {\n          const v: any = entry.parents !== undefined ? entry.parents : (entry.parent !== undefined ? entry.parent : (entry.to !== undefined ? entry.to : entry.inherits));\n          if (Array.isArray(v)) { for (const x of v) if (typeof x === 'string') out.push(x); } else if (typeof v === 'string') out.push(v);\n        }\n      }\n    }\n    return out;\n  }\n  if (typeof roleParents === 'object') {\n    const v: any = roleParents[role];\n    if (Array.isArray(v)) { for (const x of v) if (typeof x === 'string') out.push(x); } else if (typeof v === 'string') out.push(v);\n  }\n  return out;\n}\n\nexport function collectRoles(subject: any, policy: any, roleParents: any): Set<string> {\n  const roles = new Set<string>();\n  const addValue = (v: any): void => {\n    if (typeof v === 'string') roles.add(v);\n    else if (v && typeof v === 'object') {\n      const name: any = v.role !== undefined ? v.role : (v.name !== undefined ? v.name : v.id);\n      if (typeof name === 'string') roles.add(name);\n      if (Array.isArray(v.roles)) for (const x of v.roles) addValue(x);\n    }\n  };\n  if (subject && Array.isArray(subject.roles)) for (const r of subject.roles) addValue(r);\n  if (policy.group_union && subject && Array.isArray(subject.groups)) for (const g of subject.groups) addValue(g);\n  if (policy.inherit_roles) {\n    const queue: string[] = Array.from(roles);\n    const seen = new Set<string>(roles);\n    let idx = 0;\n    while (idx < queue.length) {\n      const current: string = queue[idx++];\n      for (const parent of parentsOf(current, roleParents)) {\n        if (!seen.has(parent)) { seen.add(parent); roles.add(parent); queue.push(parent); }\n      }\n    }\n  }\n  return roles;\n}\n"}}