{"version":3,"file":"caveats.mjs","names":["deadline: number"],"sources":["../../src/zcap/caveats.ts"],"sourcesContent":["/**\n * Caveat types + evaluation.\n *\n * A request is allowed only if every caveat on every capability in\n * the delegation chain passes. Unknown caveat types fail closed —\n * a verifier that doesn't understand a caveat cannot prove it holds.\n */\n\nimport type { InvocationContext } from './model'\n\nexport class CaveatViolation extends Error {\n  public constructor(message: string) {\n    super(message)\n    this.name = 'CaveatViolation'\n  }\n}\n\nexport interface Caveat {\n  type: string\n  toDict(): Record<string, unknown>\n  check(ctx: InvocationContext): void\n}\n\n/** Verb scope caveat — ctx.verb MUST be one of `verbs`. */\nexport class ValidWhileTrue implements Caveat {\n  public readonly type = 'ValidWhileTrue'\n  public readonly verbs: string[]\n\n  public constructor(verbs: string[] = []) {\n    this.verbs = [...verbs]\n  }\n\n  public toDict(): Record<string, unknown> {\n    return { type: this.type, verbs: [...this.verbs] }\n  }\n\n  public check(ctx: InvocationContext): void {\n    if (this.verbs.length > 0 && !this.verbs.includes(ctx.verb)) {\n      throw new CaveatViolation(\n        `verb ${JSON.stringify(ctx.verb)} not in allowed set ${JSON.stringify(this.verbs)}`\n      )\n    }\n  }\n}\n\n/**\n * invocationTarget path-prefix caveat. Substring match — implementers\n * SHOULD anchor at a directory boundary (`/`) at the end of the\n * prefix to avoid `s3://our-bucketEVIL/` foot-guns.\n */\nexport class PathPrefix implements Caveat {\n  public readonly type = 'PathPrefix'\n  public readonly prefix: string\n\n  public constructor(prefix = '') {\n    this.prefix = prefix\n  }\n\n  public toDict(): Record<string, unknown> {\n    return { type: this.type, prefix: this.prefix }\n  }\n\n  public check(ctx: InvocationContext): void {\n    if (this.prefix && !ctx.invocationTarget.includes(this.prefix)) {\n      throw new CaveatViolation(\n        `invocationTarget ${JSON.stringify(ctx.invocationTarget)} does not satisfy PathPrefix ${JSON.stringify(this.prefix)}`\n      )\n    }\n  }\n}\n\n/** Per-request byte budget caveat. */\nexport class MaxBytes implements Caveat {\n  public readonly type = 'MaxBytes'\n  public readonly max: number\n\n  public constructor(max = 0) {\n    this.max = max\n  }\n\n  public toDict(): Record<string, unknown> {\n    return { type: this.type, max: this.max }\n  }\n\n  public check(ctx: InvocationContext): void {\n    const requestBytes = ctx.requestBytes ?? 0\n    if (this.max > 0 && requestBytes > this.max) {\n      throw new CaveatViolation(`request ${requestBytes} bytes exceeds MaxBytes ${this.max}`)\n    }\n  }\n}\n\n/** Absolute expiry caveat (ISO 8601). */\nexport class Expires implements Caveat {\n  public readonly type = 'Expires'\n  public readonly at: string\n\n  public constructor(at = '') {\n    this.at = at\n  }\n\n  public toDict(): Record<string, unknown> {\n    return { type: this.type, at: this.at }\n  }\n\n  public check(ctx: InvocationContext): void {\n    if (!this.at) return\n    let deadline: number\n    try {\n      deadline = parseIso8601(this.at).getTime()\n    } catch (err) {\n      throw new CaveatViolation(`Expires.at not ISO 8601: ${JSON.stringify(this.at)}`)\n    }\n    const now = ctx.nowIso ? parseIso8601(ctx.nowIso).getTime() : Date.now()\n    if (now > deadline) {\n      throw new CaveatViolation(`capability expired at ${this.at} (now ${new Date(now).toISOString()})`)\n    }\n  }\n}\n\n/**\n * Marks the capability as a leaf — cannot be further delegated.\n * Delegation-time check only (no runtime predicate).\n */\nexport class NonDelegable implements Caveat {\n  public readonly type = 'NonDelegable'\n\n  public toDict(): Record<string, unknown> {\n    return { type: this.type }\n  }\n\n  public check(_ctx: InvocationContext): void {\n    // No-op at invocation time; enforced by `delegate()` in issuer.ts.\n  }\n}\n\n/**\n * Parse a wire-form caveat dict back to a Caveat instance.\n * Unknown types throw `CaveatViolation` (fail closed).\n */\nexport function caveatFromDict(d: Record<string, unknown>): Caveat {\n  switch (d.type) {\n    case 'ValidWhileTrue':\n      return new ValidWhileTrue(Array.isArray(d.verbs) ? (d.verbs as string[]) : [])\n    case 'PathPrefix':\n      return new PathPrefix((d.prefix as string | undefined) ?? '')\n    case 'MaxBytes':\n      return new MaxBytes(Number((d.max as number) ?? 0))\n    case 'Expires':\n      return new Expires((d.at as string | undefined) ?? '')\n    case 'NonDelegable':\n      return new NonDelegable()\n    default:\n      throw new CaveatViolation(`unknown caveat type ${JSON.stringify(d.type)}; failing closed`)\n  }\n}\n\nexport function isNonDelegable(c: Record<string, unknown>): boolean {\n  try {\n    return caveatFromDict(c) instanceof NonDelegable\n  } catch {\n    return false\n  }\n}\n\n/** ISO 8601 parser — accepts trailing `Z` or `+HH:MM` offset. */\nexport function parseIso8601(s: string): Date {\n  const d = new Date(s)\n  if (isNaN(d.getTime())) {\n    throw new Error(`not a valid ISO 8601 timestamp: ${s}`)\n  }\n  return d\n}\n"],"mappings":"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"}