{"version":3,"sources":["../src/validate.ts"],"names":["createCounter","VALIDATION_METRICS","VALIDATION_ISSUE_CAP","trace","VALIDATION_ATTR","createStructuredError","hashJson"],"mappings":"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","file":"validate.cjs","sourcesContent":["/**\n * Validation telemetry — connect runtime input validation (Zod or any\n * `safeParse` schema) to your traces and metrics at the boundaries where bad\n * data actually enters: HTTP bodies, events, messages.\n *\n * Today a `safeParse` failure either throws (no span, no metric, no alert) or\n * is silently swallowed in a handler. `defineValidator` makes the mismatch\n * **observable** — a `validation.*` span attribute set and a counter\n * incremented — with a per-validator `observe` vs `reject` mode:\n *\n * - `reject` (default): record telemetry, then throw a structured 400-shaped\n *   error so the boundary can fail cleanly.\n * - `observe`: record telemetry, return the raw input so the handler continues\n *   — useful for measuring real-world drift before you enforce it.\n *\n * **Not a security feature by default.** A malformed body is usually a bug or\n * version skew, not an attack. Validation telemetry is first-class on its own\n * metric; escalation to the security path is a deliberate opt-in via\n * {@link onValidationMismatch} (e.g. wired by `autotel-audit`), never automatic.\n *\n * **PII-safe by construction.** Only field *paths*, issue *codes*, and the\n * declared *type* are ever recorded — never the offending value, and never a\n * validator's error `message` (which routinely embeds the received value).\n */\n\nimport { trace } from '@opentelemetry/api';\nimport { createCounter } from './metric-helpers';\nimport { createStructuredError, type StructuredError } from './structured-error';\nimport { hashJson } from './stable-hash';\nimport type { SchemaLike } from './define-event';\nimport {\n  VALIDATION_ATTR,\n  VALIDATION_ISSUE_CAP,\n  VALIDATION_METRICS,\n} from './validation-attributes';\n\nexport type { SchemaLike } from './define-event';\n\nexport type ValidationMode = 'observe' | 'reject';\nexport type ValidationSeverity = 'info' | 'warning' | 'error';\n\n/** A single failing field, stripped of any payload values. */\nexport interface ValidationIssue {\n  /** Dotted field path, e.g. `items.0.price`. Never a value. */\n  path: string;\n  /** Issue code (e.g. Zod's `invalid_type`, `too_small`). Never a value. */\n  code: string;\n  /** Declared type/constraint summary, e.g. `string`. Never a received value. */\n  expected?: string;\n}\n\n/** Everything the recorder needs — already PII-stripped by the caller. */\nexport interface ValidationMismatch {\n  /** Contract id, e.g. `POST /orders` or `order.placed`. */\n  name: string;\n  boundary: string;\n  mode: ValidationMode;\n  issues: ValidationIssue[];\n  hash?: string;\n  severity?: ValidationSeverity;\n}\n\nlet mismatchCounter: ReturnType<typeof createCounter> | undefined;\nfunction counter(): ReturnType<typeof createCounter> {\n  if (!mismatchCounter) {\n    mismatchCounter = createCounter(VALIDATION_METRICS.mismatches, {\n      description: 'Input payloads that did not match their declared shape',\n    });\n  }\n  return mismatchCounter;\n}\n\ntype MismatchListener = (mismatch: ValidationMismatch) => void;\nconst listeners = new Set<MismatchListener>();\n\n/**\n * Register an explicit handler called on every recorded mismatch — the opt-in\n * seam for escalating to security events, a webhook, or a custom sink. There is\n * no automatic, package-presence-driven escalation: nothing fires here unless\n * you (or a package you wire up) register a handler.\n *\n * Multiple subscribers coexist: a package (e.g. `autotel-audit` bridging to\n * security events) and your own app code (a webhook, a logger) can both\n * register and all fire. Returns an unsubscribe fn that removes only this\n * handler; registering the same function twice is a no-op (Set semantics).\n */\nexport function onValidationMismatch(handler: MismatchListener): () => void {\n  listeners.add(handler);\n  return () => {\n    listeners.delete(handler);\n  };\n}\n\nconst truncate = (values: string[]): string =>\n  values.slice(0, VALIDATION_ISSUE_CAP).join(',');\n\n/**\n * Record a validation mismatch as telemetry: `validation.*` attributes on the\n * active span (if any) and an increment on `autotel.validation.mismatches`.\n * Fail-open — never throws, so instrumentation can't break the boundary.\n */\nexport function recordValidationMismatch(mismatch: ValidationMismatch): void {\n  try {\n    const paths = mismatch.issues.map((i) => i.path).filter(Boolean);\n    const codes = [...new Set(mismatch.issues.map((i) => i.code))];\n\n    const span = trace.getActiveSpan();\n    if (span) {\n      span.setAttributes({\n        [VALIDATION_ATTR.name]: mismatch.name,\n        [VALIDATION_ATTR.boundary]: mismatch.boundary,\n        [VALIDATION_ATTR.mode]: mismatch.mode,\n        [VALIDATION_ATTR.issueCount]: mismatch.issues.length,\n        [VALIDATION_ATTR.issuePaths]: truncate(paths),\n        [VALIDATION_ATTR.issueCodes]: truncate(codes),\n        ...(mismatch.hash ? { [VALIDATION_ATTR.hash]: mismatch.hash } : {}),\n        ...(mismatch.severity\n          ? { [VALIDATION_ATTR.severity]: mismatch.severity }\n          : {}),\n      });\n    }\n\n    try {\n      counter().add(1, {\n        boundary: mismatch.boundary,\n        validation: mismatch.name,\n        mode: mismatch.mode,\n      });\n    } catch {\n      // meter not initialised yet — skip the count, keep the span attrs\n    }\n\n    // Dispatch to every subscriber with per-listener fault isolation: one\n    // throwing subscriber must not starve its peers or break the boundary.\n    // Set iteration tolerates concurrent (un)subscription safely.\n    for (const listener of listeners) {\n      try {\n        listener(mismatch);\n      } catch {\n        // a misbehaving subscriber must not break the boundary or its peers\n      }\n    }\n  } catch {\n    // fail-open: telemetry must never break the validated boundary\n  }\n}\n\n/**\n * Normalise an arbitrary validation error into PII-safe issues. Reads only\n * `path`, `code`, and (when it is a declared type name) `expected` — and never\n * `message`, `received`, or any value-bearing field. Understands the Zod shape\n * (`error.issues`) and a generic `error.errors` fallback; returns `[]` for\n * anything unrecognised.\n */\nexport function formatValidationIssues(error: unknown): ValidationIssue[] {\n  const raw = extractRawIssues(error);\n  return raw.map((issue) => toSafeIssue(issue));\n}\n\nfunction extractRawIssues(error: unknown): Array<Record<string, unknown>> {\n  if (error && typeof error === 'object') {\n    const candidate =\n      (error as { issues?: unknown }).issues ??\n      (error as { errors?: unknown }).errors;\n    if (Array.isArray(candidate)) {\n      return candidate.filter(\n        (i): i is Record<string, unknown> => i !== null && typeof i === 'object',\n      );\n    }\n  }\n  return [];\n}\n\nfunction toSafeIssue(issue: Record<string, unknown>): ValidationIssue {\n  const rawPath = issue.path;\n  const path = Array.isArray(rawPath)\n    ? rawPath.map(String).join('.')\n    : typeof rawPath === 'string'\n      ? rawPath\n      : '';\n  const code = typeof issue.code === 'string' ? issue.code : 'invalid';\n  // `expected` is a declared type name in Zod (e.g. 'string'); safe. We never\n  // read `received`/`message`/`value`, which can carry the offending payload.\n  const expected =\n    typeof issue.expected === 'string' ? issue.expected : undefined;\n  return expected ? { path, code, expected } : { path, code };\n}\n\nexport interface DefineValidatorOptions<S> {\n  /** Where validation runs. Defaults to `input`. */\n  boundary?: string;\n  /** `reject` (default): record then throw. `observe`: record then continue. */\n  onMismatch?: ValidationMode;\n  /** Project the schema to JSON Schema for a stable `validation.hash`. */\n  toJsonSchema?: (schema: S) => unknown;\n  severity?: ValidationSeverity;\n  /** Build the error thrown in `reject` mode (defaults to a 400 structured error). */\n  onReject?: (issues: ValidationIssue[], name: string) => Error;\n}\n\nexport type ValidatorResult<T> =\n  | { success: true; data: T }\n  | { success: false; issues: ValidationIssue[] };\n\nexport interface Validator<T> {\n  readonly name: string;\n  readonly mode: ValidationMode;\n  /** Validate and record on failure; never throws. */\n  safeParse(input: unknown): ValidatorResult<T>;\n  /**\n   * Validate, record on failure, then apply the mode: `reject` throws,\n   * `observe` returns the raw input so the handler can continue.\n   */\n  parse(input: unknown): T;\n}\n\nfunction defaultRejectError(\n  issues: ValidationIssue[],\n  name: string,\n): StructuredError {\n  return createStructuredError({\n    name: 'ValidationError',\n    status: 400,\n    code: 'validation_failed',\n    message: `Input for \"${name}\" did not match its declared shape.`,\n    why: `${issues.length} field(s) failed validation: ${issues\n      .map((i) => i.path || '(root)')\n      .slice(0, VALIDATION_ISSUE_CAP)\n      .join(', ')}.`,\n    fix: 'Send a payload that matches the schema, or switch this validator to observe mode while you investigate.',\n    // PII-safe: paths + codes only, no received values.\n    details: { validation: name, issues },\n  });\n}\n\n/**\n * Declare an expected input shape once and get a validator that records every\n * mismatch as telemetry.\n *\n * @example\n * ```ts\n * import { z } from 'zod';\n * import { defineValidator } from 'autotel/validate';\n *\n * const OrderBody = defineValidator('POST /orders', z.object({\n *   items: z.array(z.object({ sku: z.string(), qty: z.number().int() })),\n * }), { boundary: 'http', toJsonSchema: (s) => z.toJSONSchema(s) });\n *\n * // reject mode (default): records + throws a 400-shaped structured error\n * const order = OrderBody.parse(req.body);\n *\n * // observe mode: records, returns the result, never throws\n * const result = OrderBody.safeParse(req.body);\n * if (!result.success) metrics.onDrift(result.issues);\n * ```\n */\nexport function defineValidator<T, S extends SchemaLike<T>>(\n  name: string,\n  schema: S,\n  options: DefineValidatorOptions<S> = {},\n): Validator<T> {\n  const mode = options.onMismatch ?? 'reject';\n  const boundary = options.boundary ?? 'input';\n  const hash = options.toJsonSchema\n    ? hashJson(options.toJsonSchema(schema))\n    : undefined;\n\n  const record = (issues: ValidationIssue[]): void => {\n    recordValidationMismatch({\n      name,\n      boundary,\n      mode,\n      issues,\n      hash,\n      severity: options.severity,\n    });\n  };\n\n  return {\n    name,\n    mode,\n    safeParse(input: unknown): ValidatorResult<T> {\n      const parsed = schema.safeParse(input);\n      if (parsed.success) return { success: true, data: parsed.data };\n      const issues = formatValidationIssues(parsed.error);\n      record(issues);\n      return { success: false, issues };\n    },\n    parse(input: unknown): T {\n      const parsed = schema.safeParse(input);\n      if (parsed.success) return parsed.data;\n      const issues = formatValidationIssues(parsed.error);\n      record(issues);\n      if (mode === 'reject') {\n        throw options.onReject?.(issues, name) ?? defaultRejectError(issues, name);\n      }\n      // observe: continue with the raw input (documented type caveat)\n      return input as T;\n    },\n  };\n}\n"]}