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