{"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, expandRoles, getExpires, getScope, isDeny, 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  if (subject.tenant !== resource.tenant) { return makeResult(policy, false, 'tenant', []); }\n  if (payload.now - payload.issued_at > 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 initial: string[] = [];\n  const baseRoles: any[] = Array.isArray(subject.roles) ? subject.roles : [];\n  for (const r of baseRoles) { initial.push(r); }\n  if (policy.group_union) {\n    const groups: any[] = Array.isArray(subject.groups) ? subject.groups : [];\n    for (const g of groups) { initial.push(g); }\n  }\n  const roles: Set<string> = expandRoles(initial, payload.role_parents || {}, !!policy.inherit_roles);\n  const grants: any[] = Array.isArray(payload.grants) ? payload.grants : [];\n  const matched: number[] = [];\n  let anyAllow = false;\n  let anyDeny = false;\n  for (let i = 0; i < grants.length; i++) {\n    const grant: any = grants[i] || {};\n    if (!roles.has(grant.role)) { continue; }\n    let actionOk = false;\n    if (policy.wildcard_action && grant.action === '*') { actionOk = true; } else if (typeof grant.action === 'string' && typeof payload.action === 'string') {\n      if (policy.action_casefold) { actionOk = asciiLower(grant.action) === asciiLower(payload.action); } else { actionOk = grant.action === payload.action; }\n    }\n    if (!actionOk) { continue; }\n    const scope: any = getScope(grant);\n    let scopeOk = false;\n    if (scope === null || scope === undefined) { scopeOk = true; } else if (policy.resource_scope === 'prefix') { scopeOk = typeof scope === 'string' && typeof resource.id === 'string' && resource.id.startsWith(scope); } else { scopeOk = scope === resource.id; }\n    if (!scopeOk) { continue; }\n    const expires: any = getExpires(grant);\n    let expiryOk = false;\n    if (expires === null || expires === undefined) { expiryOk = true; } else if (policy.expiry_inclusive) { expiryOk = payload.now <= expires; } else { expiryOk = payload.now < expires; }\n    if (!expiryOk) { continue; }\n    matched.push(i);\n    if (isDeny(grant)) { anyDeny = true; } else { anyAllow = true; }\n  }\n  if (matched.length === 0) { return makeResult(policy, !!policy.default_allow, 'default', []); }\n  const allowed: boolean = policy.deny_wins ? (anyAllow && !anyDeny) : anyAllow;\n  return makeResult(policy, allowed, 'grants', matched);\n}\n", "policy.ts": "export const POLICY: any = {\n  \"action_casefold\": true,\n  \"audit_denials\": true,\n  \"default_allow\": true,\n  \"deny_wins\": true,\n  \"expiry_inclusive\": true,\n  \"group_union\": true,\n  \"inherit_roles\": true,\n  \"owner_bypass\": true,\n  \"resource_scope\": \"exact\",\n  \"suspended_denies\": true,\n  \"token_ttl\": 100,\n  \"wildcard_action\": true\n};\n", "helpers.ts": "export function asciiLower(s: string): string {\n  let out = '';\n  for (let i = 0; i < s.length; i++) {\n    const c = s.charCodeAt(i);\n    if (c >= 65 && c <= 90) { out += String.fromCharCode(c + 32); } else { out += s.charAt(i); }\n  }\n  return out;\n}\nexport function makeResult(policy: any, allowed: boolean, reason: string, matched: number[]): any {\n  const audit: string[] = (!allowed && policy && policy.audit_denials) ? [reason] : [];\n  return {allowed: allowed, reason: reason, matched: matched.slice(), audit: audit};\n}\nexport function expandRoles(initial: string[], roleParents: any, inherit: boolean): Set<string> {\n  const result = new Set<string>();\n  const queue: string[] = [];\n  for (const r of initial) { if (!result.has(r)) { result.add(r); queue.push(r); } }\n  if (!inherit) { return result; }\n  let idx = 0;\n  while (idx < queue.length) {\n    const cur = queue[idx];\n    idx++;\n    const parents: any = roleParents ? roleParents[cur] : null;\n    if (Array.isArray(parents)) {\n      for (const p of parents) { if (!result.has(p)) { result.add(p); queue.push(p); } }\n    }\n  }\n  return result;\n}\nexport function getScope(grant: any): any {\n  if (grant.scope !== undefined) { return grant.scope; }\n  if (grant.resource !== undefined) { return grant.resource; }\n  if (grant.resource_id !== undefined) { return grant.resource_id; }\n  if (grant.resourceId !== undefined) { return grant.resourceId; }\n  if (grant.target !== undefined) { return grant.target; }\n  return undefined;\n}\nexport function getExpires(grant: any): any {\n  if (grant.expires !== undefined) { return grant.expires; }\n  if (grant.expires_at !== undefined) { return grant.expires_at; }\n  if (grant.expiry !== undefined) { return grant.expiry; }\n  if (grant.exp !== undefined) { return grant.exp; }\n  return null;\n}\nexport function isDeny(grant: any): boolean {\n  if (typeof grant.effect === 'string') { return asciiLower(grant.effect) === 'deny'; }\n  if (typeof grant.decision === 'string') { return asciiLower(grant.decision) === 'deny'; }\n  if (typeof grant.action_effect === 'string') { return asciiLower(grant.action_effect) === 'deny'; }\n  if (grant.deny === true) { return true; }\n  if (grant.allow === false) { return true; }\n  if (grant.allowed === false) { return true; }\n  if (typeof grant.type === 'string' && asciiLower(grant.type) === 'deny') { return true; }\n  if (typeof grant.kind === 'string' && asciiLower(grant.kind) === 'deny') { return true; }\n  return false;\n}\n"}}