{"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 {actionMatches, expiryMatches, grantIsAllow, grantIsDeny, makeResult, scopeMatches} 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  const grants: any[] = (payload && Array.isArray(payload.grants)) ? payload.grants : [];\n  const roleParents: any = (payload && payload.role_parents) || {};\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 effectiveRoles = new Set<string>();\n  const initialRoles: any[] = Array.isArray(subject.roles) ? subject.roles : [];\n  for (const role of initialRoles) effectiveRoles.add(role);\n  if (policy.group_union) {\n    const groups: any[] = Array.isArray(subject.groups) ? subject.groups : [];\n    for (const group of groups) effectiveRoles.add(group);\n  }\n  if (policy.inherit_roles) {\n    const queue: string[] = Array.from(effectiveRoles);\n    let index = 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) {\n          if (!effectiveRoles.has(parent)) {\n            effectiveRoles.add(parent);\n            queue.push(parent);\n          }\n        }\n      }\n    }\n  }\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 (!effectiveRoles.has(grant.role)) continue;\n    if (!actionMatches(grant.action, requestedAction, policy)) continue;\n    if (!scopeMatches(grant.scope, resource.id, policy)) continue;\n    if (!expiryMatches(grant.expires, now, policy)) continue;\n    matched.push(i);\n    if (grantIsDeny(grant)) anyDeny = true;\n    if (grantIsAllow(grant)) anyAllow = true;\n  }\n  if (matched.length === 0) return makeResult(!!policy.default_allow, 'default', [], policy);\n  const allowed: boolean = policy.deny_wins ? (anyAllow && !anyDeny) : anyAllow;\n  return makeResult(allowed, 'grants', matched, policy);\n}\n", "policy.ts": "export const POLICY: any = {\n  \"deny_wins\": false,\n  \"expiry_inclusive\": false,\n  \"inherit_roles\": false,\n  \"owner_bypass\": false,\n  \"action_casefold\": false,\n  \"group_union\": false,\n  \"resource_scope\": \"prefix\",\n  \"suspended_denies\": false,\n  \"token_ttl\": 200,\n  \"wildcard_action\": false,\n  \"audit_denials\": false,\n  \"default_allow\": false\n};\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 code = s.charCodeAt(i);\n    if (code >= 65 && code <= 90) out += String.fromCharCode(code + 32);\n    else out += s.charAt(i);\n  }\n  return out;\n}\nexport function makeResult(allowed: boolean, reason: string, matched: number[], policy: any): any {\n  return {allowed: allowed, reason: reason, matched: matched.slice(), audit: (!allowed && policy && policy.audit_denials) ? [reason] : []};\n}\nexport function actionMatches(grantAction: any, requestedAction: any, policy: any): boolean {\n  if (grantAction === '*') return !!(policy && policy.wildcard_action);\n  if (policy && policy.action_casefold) return asciiLower(grantAction) === asciiLower(requestedAction);\n  return grantAction === requestedAction;\n}\nexport function scopeMatches(scope: any, resourceId: any, policy: any): boolean {\n  if (typeof scope !== 'string' || typeof resourceId !== 'string') return scope === resourceId;\n  if (policy && policy.resource_scope === 'exact') return scope === resourceId;\n  return resourceId.startsWith(scope);\n}\nexport function expiryMatches(expires: any, now: any, policy: any): boolean {\n  if (expires === null || expires === undefined) return true;\n  if (policy && policy.expiry_inclusive) return now <= expires;\n  return now < expires;\n}\nfunction effectString(grant: any): string {\n  if (grant && typeof grant.effect === 'string') return asciiLower(grant.effect);\n  if (grant && typeof grant.type === 'string') return asciiLower(grant.type);\n  if (grant && typeof grant.decision === 'string') return asciiLower(grant.decision);\n  return '';\n}\nexport function grantIsDeny(grant: any): boolean {\n  if (grant && typeof grant.allow === 'boolean') return !grant.allow;\n  if (grant && typeof grant.allowed === 'boolean') return !grant.allowed;\n  return effectString(grant) === 'deny';\n}\nexport function grantIsAllow(grant: any): boolean {\n  if (grant && typeof grant.allow === 'boolean') return grant.allow;\n  if (grant && typeof grant.allowed === 'boolean') return grant.allowed;\n  const effect = effectString(grant);\n  if (effect === 'deny') return false;\n  return true;\n}\n"}}