{"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, makeResult} from './helpers.ts';\nexport function run(payload: any, policy: any): any {\n  const subject: any = payload.subject;\n  const resource: any = payload.resource;\n  if (subject.tenant !== resource.tenant) { return makeResult(false, 'tenant', [], policy); }\n  if (payload.now - payload.issued_at > policy.token_ttl) { return makeResult(false, 'token', [], policy); }\n  if (policy.suspended_denies && subject.suspended) { return makeResult(false, 'suspended', [], policy); }\n  if (policy.owner_bypass && subject.owner) { return makeResult(true, 'owner', [], policy); }\n  const initial: string[] = [...subject.roles];\n  if (policy.group_union) { initial.push(...subject.groups); }\n  const roles: Set<string> = expandRoles(initial, payload.role_parents, !!policy.inherit_roles);\n  const requested: string = policy.action_casefold ? asciiLower(payload.action) : payload.action;\n  const matched: number[] = [];\n  let hasAllow: boolean = false;\n  let hasDeny: boolean = false;\n  const grants: any[] = payload.grants || [];\n  for (let i: number = 0; i < grants.length; i++) {\n    const grant: any = grants[i];\n    if (!roles.has(grant.role)) { continue; }\n    const grantAction: string = policy.action_casefold ? asciiLower(grant.action) : grant.action;\n    const actionOk: boolean = grantAction === requested || (!!policy.wildcard_action && grant.action === '*');\n    if (!actionOk) { continue; }\n    let scopeOk: boolean;\n    if (policy.resource_scope === 'exact') { scopeOk = grant.scope === resource.id; } else { scopeOk = resource.id.startsWith(grant.scope); }\n    if (!scopeOk) { continue; }\n    let expiryOk: boolean;\n    if (grant.expires === null) { expiryOk = true; } else if (policy.expiry_inclusive) { expiryOk = payload.now <= grant.expires; } else { expiryOk = payload.now < grant.expires; }\n    if (!expiryOk) { continue; }\n    matched.push(i);\n    if (grant.allow) { hasAllow = true; } else { hasDeny = true; }\n  }\n  if (matched.length === 0) { return makeResult(!!policy.default_allow, 'default', [], policy); }\n  const allowed: boolean = policy.deny_wins ? (hasAllow && !hasDeny) : hasAllow;\n  return makeResult(allowed, 'grants', matched, policy);\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 asciiLower(value: string): string {\n  return value.replace(/[A-Z]/g, (ch: string): string => String.fromCharCode(ch.charCodeAt(0) + 32));\n}\nexport function makeResult(allowed: boolean, reason: string, matched: number[], policy: any): any {\n  return {allowed: allowed, reason: reason, matched: [...matched], audit: (!allowed && policy.audit_denials) ? [reason] : []};\n}\nexport function expandRoles(initial: string[], roleParents: any, inherit: boolean): Set<string> {\n  const seen: Set<string> = new Set<string>();\n  const queue: string[] = [];\n  for (const role of initial) {\n    if (!seen.has(role)) { seen.add(role); queue.push(role); }\n  }\n  if (inherit) {\n    let index: number = 0;\n    while (index < queue.length) {\n      const current: string = queue[index];\n      index++;\n      const parents: any = roleParents ? roleParents[current] : undefined;\n      if (Array.isArray(parents)) {\n        for (const parent of parents) {\n          if (!seen.has(parent)) { seen.add(parent); queue.push(parent); }\n        }\n      }\n    }\n  }\n  return seen;\n}\n"}}