{"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, grantIsDeny} 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 !== undefined ? p.issued_at : p.issuedAt;\n  const action: any = p.action;\n  const grants: any[] = Array.isArray(p.grants) ? p.grants : [];\n  const roleParents: any = p.role_parents || p.roleParents || {};\n  const result = (allowed: boolean, reason: string, matched: number[]): any => ({allowed: !!allowed, reason, matched: matched.slice(), audit: (!allowed && policy.audit_denials) ? [reason] : []});\n  if (subject.tenant !== resource.tenant) return result(false, 'tenant', []);\n  if (now - issuedAt > policy.token_ttl) return result(false, 'token', []);\n  if (policy.suspended_denies && subject.suspended) return result(false, 'suspended', []);\n  if (policy.owner_bypass && subject.owner) return result(true, 'owner', []);\n  const roleSet = new Set<string>();\n  const initial: any[] = [];\n  if (Array.isArray(subject.roles)) for (const r of subject.roles) initial.push(r);\n  if (policy.group_union && Array.isArray(subject.groups)) for (const g of subject.groups) initial.push(g);\n  for (const r of initial) roleSet.add(r);\n  if (policy.inherit_roles) {\n    const queue: any[] = Array.from(roleSet);\n    const seen = new Set<string>(roleSet);\n    while (queue.length > 0) {\n      const cur: any = queue.shift();\n      const parents: any = roleParents ? roleParents[cur] : undefined;\n      if (Array.isArray(parents)) {\n        for (const par of parents) {\n          if (!seen.has(par)) { seen.add(par); roleSet.add(par); queue.push(par); }\n        }\n      }\n    }\n  }\n  const resourceId: string = resource.id === undefined || resource.id === null ? '' : String(resource.id);\n  const reqAction: string = action === undefined || action === null ? '' : String(action);\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    let roleOk = false;\n    if (typeof g.role === 'string') roleOk = roleSet.has(g.role);\n    else if (Array.isArray(g.roles)) roleOk = g.roles.some((r: any) => roleSet.has(r));\n    else if (typeof g.role_name === 'string') roleOk = roleSet.has(g.role_name);\n    if (!roleOk) continue;\n    const gActionRaw: any = g.action;\n    if (gActionRaw === undefined || gActionRaw === null) continue;\n    const gAction: string = String(gActionRaw);\n    const actionsEqual: boolean = policy.action_casefold ? asciiLower(gAction) === asciiLower(reqAction) : gAction === reqAction;\n    const wildcardOk: boolean = !!policy.wildcard_action && gAction === '*';\n    if (!actionsEqual && !wildcardOk) continue;\n    const scopeRaw: any = g.scope !== undefined ? g.scope : (g.resource !== undefined ? g.resource : (g.resource_id !== undefined ? g.resource_id : g.resourceId));\n    if (scopeRaw !== undefined && scopeRaw !== null) {\n      const scope: string = String(scopeRaw);\n      if (policy.resource_scope === 'prefix') { if (!resourceId.startsWith(scope)) continue; }\n      else { if (scope !== resourceId) continue; }\n    }\n    const expiresRaw: any = g.expires !== undefined ? g.expires : (g.expires_at !== undefined ? g.expires_at : (g.expiry !== undefined ? g.expiry : g.expiresAt));\n    if (expiresRaw !== undefined && expiresRaw !== null) {\n      if (policy.expiry_inclusive) { if (!(now <= expiresRaw)) continue; }\n      else { if (!(now < expiresRaw)) continue; }\n    }\n    matched.push(i);\n    if (grantIsDeny(g)) hasDeny = true; else hasAllow = true;\n  }\n  if (matched.length === 0) return result(!!policy.default_allow, 'default', []);\n  const allowed: boolean = policy.deny_wins ? (hasAllow && !hasDeny) : hasAllow;\n  return result(allowed, 'grants', matched);\n}\n", "policy.ts": "export const POLICY: any = {\"token_ttl\": 200, \"suspended_denies\": false, \"owner_bypass\": false, \"group_union\": false, \"inherit_roles\": false, \"action_casefold\": false, \"wildcard_action\": false, \"resource_scope\": \"prefix\", \"expiry_inclusive\": false, \"default_allow\": false, \"deny_wins\": false, \"audit_denials\": false};\n", "helpers.ts": "export function asciiLower(s: string): string { let out = ''; for (let i = 0; i < s.length; i++) { const c = s.charCodeAt(i); out += (c >= 65 && c <= 90) ? String.fromCharCode(c + 32) : s[i]; } return out; }\nexport function grantIsDeny(g: any): boolean { if (!g) return false; if (typeof g.deny === 'boolean') return g.deny; if (typeof g.allow === 'boolean') return !g.allow; const v: any = g.effect !== undefined ? g.effect : (g.type !== undefined ? g.type : (g.decision !== undefined ? g.decision : g.kind)); if (typeof v === 'string') return asciiLower(v) === 'deny'; if (typeof v === 'boolean') return !v; return false; }\n"}}