{"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, isAllowGrant, 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  const requestedAction: any = payload ? payload.action : undefined;\n  if (subject.tenant !== resource.tenant) { return makeResult(false, 'tenant', [], policy); }\n  if (now - issuedAt > 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 initialRoles: any[] = [];\n  const subjectRoles: any[] = Array.isArray(subject.roles) ? subject.roles : [];\n  for (const r of subjectRoles) { initialRoles.push(r); }\n  if (policy.group_union) {\n    const groups: any[] = Array.isArray(subject.groups) ? subject.groups : [];\n    for (const g of groups) { initialRoles.push(g); }\n  }\n  const roleParents: any = (payload && payload.role_parents) || {};\n  const expanded: Set<any> = expandRoles(initialRoles, roleParents, !!policy.inherit_roles);\n  const grants: any[] = payload && Array.isArray(payload.grants) ? payload.grants : [];\n  const matched: number[] = [];\n  let anyAllow: boolean = false;\n  let anyDeny: boolean = false;\n  for (let i = 0; i < grants.length; i++) {\n    const grant: any = grants[i] || {};\n    if (!expanded.has(grant.role)) { continue; }\n    const grantAction: any = grant.action;\n    let actionMatches: boolean = false;\n    if (policy.wildcard_action && grantAction === '*') { actionMatches = true; }\n    else if (typeof grantAction === 'string' && typeof requestedAction === 'string') {\n      if (policy.action_casefold) { actionMatches = asciiLower(grantAction) === asciiLower(requestedAction); }\n      else { actionMatches = grantAction === requestedAction; }\n    } else { actionMatches = grantAction === requestedAction; }\n    if (!actionMatches) { continue; }\n    const scope: any = grant.scope;\n    let scopeMatches: boolean = false;\n    if (policy.resource_scope === 'prefix') { scopeMatches = typeof scope === 'string' && typeof resource.id === 'string' && resource.id.startsWith(scope); }\n    else { scopeMatches = scope === resource.id; }\n    if (!scopeMatches) { continue; }\n    const expires: any = grant.expires;\n    let unexpired: boolean = false;\n    if (expires === null || expires === undefined) { unexpired = true; }\n    else if (policy.expiry_inclusive) { unexpired = now <= expires; }\n    else { unexpired = now < expires; }\n    if (!unexpired) { continue; }\n    matched.push(i);\n    if (isAllowGrant(grant)) { anyAllow = true; } else { anyDeny = true; }\n  }\n  if (matched.length === 0) { return makeResult(!!policy.default_allow, 'default', [], policy); }\n  let allowed: boolean = false;\n  if (policy.deny_wins) { allowed = anyAllow && !anyDeny; } else { allowed = anyAllow; }\n  return makeResult(allowed, 'grants', matched, policy);\n}\n", "policy.ts": "export const POLICY: any = {\n  \"deny_wins\": true,\n  \"expiry_inclusive\": true,\n  \"inherit_roles\": true,\n  \"owner_bypass\": true,\n  \"action_casefold\": true,\n  \"group_union\": true,\n  \"resource_scope\": \"exact\",\n  \"suspended_denies\": true,\n  \"token_ttl\": 100,\n  \"wildcard_action\": true,\n  \"audit_denials\": true,\n  \"default_allow\": true\n};\n", "helpers.ts": "export function asciiLower(value: string): string {\n  let out: string = '';\n  for (let i = 0; i < value.length; i++) {\n    const code: number = value.charCodeAt(i);\n    if (code >= 65 && code <= 90) { out += String.fromCharCode(code + 32); } else { out += value.charAt(i); }\n  }\n  return out;\n}\nexport function expandRoles(initial: any[], roleParents: any, inherit: boolean): Set<any> {\n  const seen: Set<any> = new Set<any>();\n  const queue: any[] = [];\n  for (const role of initial) { if (!seen.has(role)) { seen.add(role); queue.push(role); } }\n  if (!inherit) { return seen; }\n  let index: number = 0;\n  while (index < queue.length) {\n    const current: any = queue[index];\n    index++;\n    const parents: any = roleParents ? roleParents[current] : undefined;\n    if (Array.isArray(parents)) {\n      for (const parent of parents) { if (!seen.has(parent)) { seen.add(parent); queue.push(parent); } }\n    }\n  }\n  return seen;\n}\nexport function isAllowGrant(grant: any): boolean {\n  if (!grant || typeof grant !== 'object') { return true; }\n  const textual: any = grant.effect !== undefined ? grant.effect : (grant.type !== undefined ? grant.type : (grant.decision !== undefined ? grant.decision : (grant.kind !== undefined ? grant.kind : grant.outcome)));\n  if (typeof textual === 'string') { const lowered: string = asciiLower(textual); if (lowered === 'deny' || lowered === 'denied') { return false; } return true; }\n  if (typeof grant.allow === 'boolean') { return grant.allow; }\n  if (typeof grant.allowed === 'boolean') { return grant.allowed; }\n  if (grant.deny === true) { return false; }\n  return true;\n}\nexport function makeResult(allowed: boolean, reason: string, matched: number[], policy: any): any {\n  const audit: string[] = !allowed && policy && policy.audit_denials ? [reason] : [];\n  const matchedCopy: number[] = [];\n  for (const item of matched) { matchedCopy.push(item); }\n  return {allowed: allowed, reason: reason, matched: matchedCopy, audit: audit};\n}\n"}}