{"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 {effectiveRoles, matchesGrant} from './helpers.ts';\n\nexport function run(payload: any, policy: any): any {\n  const result = (allowed: boolean, reason: string, matched: number[] = []) => ({\n    allowed,\n    reason,\n    matched,\n    audit: !allowed && policy.audit_denials ? [reason] : []\n  });\n\n  if (payload.subject.tenant !== payload.resource.tenant) return result(false, 'tenant');\n  if (payload.now - payload.issued_at > policy.token_ttl) return result(false, 'token');\n  if (policy.suspended_denies && payload.subject.suspended) return result(false, 'suspended');\n  if (policy.owner_bypass && payload.subject.owner) return result(true, 'owner');\n\n  const roles = effectiveRoles(payload, policy);\n  const matched: number[] = [];\n  let hasAllow = false;\n  let hasDeny = false;\n  for (let index = 0; index < payload.grants.length; index++) {\n    const grant = payload.grants[index];\n    if (!matchesGrant(grant, payload, policy, roles)) continue;\n    matched.push(index);\n    // The contract leaves the effect encoding unspecified; use effect='allow'/'deny'.\n    if (grant.effect === 'allow') hasAllow = true;\n    if (grant.effect === 'deny') hasDeny = true;\n  }\n\n  if (matched.length === 0) return result(policy.default_allow, 'default');\n  return result(hasAllow && (!policy.deny_wins || !hasDeny), 'grants', matched);\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 effectiveRoles(payload: any, policy: any): Set<string> {\n  const roles = new Set<string>(payload.subject.roles);\n  if (policy.group_union) {\n    for (const group of payload.subject.groups) roles.add(group);\n  }\n  if (policy.inherit_roles) {\n    const pending = Array.from(roles);\n    for (let index = 0; index < pending.length; index++) {\n      const role = pending[index];\n      const parents = Object.prototype.hasOwnProperty.call(payload.role_parents, role)\n        ? payload.role_parents[role] : [];\n      for (const parent of parents) {\n        if (!roles.has(parent)) {\n          roles.add(parent);\n          pending.push(parent);\n        }\n      }\n    }\n  }\n  return roles;\n}\n\nexport function matchesGrant(grant: any, payload: any, policy: any, roles: Set<string>): boolean {\n  if (!roles.has(grant.role)) return false;\n  const actionMatches = policy.action_casefold\n    ? grant.action.toLowerCase() === payload.action.toLowerCase()\n    : grant.action === payload.action;\n  if (!actionMatches && !(policy.wildcard_action && grant.action === '*')) return false;\n  const scopeMatches = policy.resource_scope === 'exact'\n    ? grant.scope === payload.resource.id\n    : payload.resource.id.startsWith(grant.scope);\n  if (!scopeMatches) return false;\n  return grant.expires === null || (policy.expiry_inclusive\n    ? payload.now <= grant.expires\n    : payload.now < grant.expires);\n}\n"}}