{"version":3,"sources":["../../src/types/index.ts","../../src/types/errors.ts","../../src/types/schemas.ts","../../src/internal/sensitive.ts"],"sourcesContent":["/**\n * @packageDocumentation\n * @module act/types\n * Barrel file for Act Framework core types.\n *\n * Re-exports all major type definitions for actions, errors, ports, reactions, registries, and schemas.\n * Also defines common environment and log level types/constants for configuration and logging.\n *\n * @remarks\n * Import from this module to access all core framework types in one place.\n */\nexport type * from \"./action.js\";\nexport type * from \"./audit.js\";\nexport * from \"./errors.js\";\nexport type * from \"./ports.js\";\nexport type * from \"./reaction.js\";\nexport type * from \"./registry.js\";\nexport * from \"./schemas.js\";\n\n/**\n * Supported runtime environments for the framework.\n * - `development`: Local development\n * - `test`: Automated testing\n * - `staging`: Pre-production\n * - `production`: Live/production\n */\nexport const Environments = [\n  \"development\",\n  \"test\",\n  \"staging\",\n  \"production\",\n] as const;\n\n/**\n * Type representing a valid environment string.\n */\nexport type Environment = (typeof Environments)[number];\n\n/**\n * Supported log levels for framework logging.\n * - `fatal`, `error`, `warn`, `info`, `debug`, `trace`\n */\nexport const LogLevels = [\n  \"fatal\",\n  \"error\",\n  \"warn\",\n  \"info\",\n  \"debug\",\n  \"trace\",\n] as const;\n\n/**\n * Type representing a valid log level string.\n */\nexport type LogLevel = (typeof LogLevels)[number];\n","import type {\n  Actor,\n  Message,\n  Schema,\n  Schemas,\n  Snapshot,\n  Target,\n} from \"./action.js\";\n\n/**\n * @packageDocumentation\n * @module act/types\n * @category Types\n * Application error type constants and error classes for the Act Framework.\n *\n * - `ERR_VALIDATION`: Schema validation error\n * - `ERR_INVARIANT`: Invariant validation error\n * - `ERR_CONCURRENCY`: Optimistic concurrency validation error on commits\n */\nexport const Errors = {\n  ValidationError: \"ERR_VALIDATION\",\n  InvariantError: \"ERR_INVARIANT\",\n  ConcurrencyError: \"ERR_CONCURRENCY\",\n  StreamClosedError: \"ERR_STREAM_CLOSED\",\n  NonRetryableError: \"ERR_NON_RETRYABLE\",\n  StoreError: \"ERR_STORE\",\n} as const;\n\n/**\n * Thrown when an action or event payload fails Zod schema validation.\n *\n * This error indicates that data doesn't match the expected schema defined\n * for an action or event. The `details` property contains the Zod validation\n * error with specific information about what failed.\n *\n * @example Catching validation errors\n * ```typescript\n * import { ValidationError } from \"@rotorsoft/act\";\n *\n * try {\n *   await app.do(\"createUser\", target, {\n *     email: \"invalid-email\",  // Missing @ symbol\n *     age: -5                  // Negative age\n *   });\n * } catch (error) {\n *   if (error instanceof ValidationError) {\n *     console.error(\"Validation failed for:\", error.target);\n *     console.error(\"Invalid payload:\", error.payload);\n *     console.error(\"Validation details:\", error.details);\n *     // details contains Zod error with field-level info\n *   }\n * }\n * ```\n *\n * @example Logging validation details\n * ```typescript\n * try {\n *   await app.do(\"updateProfile\", target, payload);\n * } catch (error) {\n *   if (error instanceof ValidationError) {\n *     error.details.errors.forEach((err) => {\n *       console.error(`Field ${err.path.join(\".\")}: ${err.message}`);\n *     });\n *   }\n * }\n * ```\n *\n * @see {@link https://zod.dev | Zod documentation} for validation details\n */\nexport class ValidationError extends Error {\n  /** The type of target being validated (e.g., \"action\", \"event\") */\n  public readonly target: string;\n  /** The invalid payload that failed validation */\n  public readonly payload: any;\n  /** Zod validation error details */\n  public readonly details: any;\n\n  constructor(target: string, payload: any, details: any) {\n    super(`Invalid ${target} payload`);\n    this.name = Errors.ValidationError;\n    this.target = target;\n    this.payload = payload;\n    this.details = details;\n  }\n}\n\n/**\n * Thrown when a business rule (invariant) is violated during action execution.\n *\n * Invariants are conditions that must hold true for an action to succeed.\n * They're checked after loading the current state but before emitting events.\n * This error provides complete context about what action was attempted and\n * why it was rejected.\n *\n * @template TState - State schema type\n * @template TEvents - Event schemas type\n * @template TActions - Action schemas type\n * @template TKey - Action name\n * @template TActor - Actor type extending base Actor\n *\n * @example Catching invariant violations\n * ```typescript\n * import { InvariantError } from \"@rotorsoft/act\";\n *\n * try {\n *   await app.do(\"withdraw\",\n *     { stream: \"account-123\", actor: { id: \"user1\", name: \"Alice\" } },\n *     { amount: 1000 }\n *   );\n * } catch (error) {\n *   if (error instanceof InvariantError) {\n *     console.error(\"Action:\", error.action);\n *     console.error(\"Reason:\", error.description);\n *     console.error(\"Current state:\", error.snapshot.state);\n *     console.error(\"Attempted payload:\", error.payload);\n *   }\n * }\n * ```\n *\n * @example User-friendly error messages\n * ```typescript\n * try {\n *   await app.do(\"closeTicket\", target, payload);\n * } catch (error) {\n *   if (error instanceof InvariantError) {\n *     // Present friendly message to user\n *     if (error.description === \"Ticket must be open\") {\n *       return { error: \"This ticket is already closed\" };\n *     } else if (error.description === \"Not authorized\") {\n *       return { error: \"You don't have permission to close this ticket\" };\n *     }\n *   }\n * }\n * ```\n *\n * @example Logging with context\n * ```typescript\n * try {\n *   await app.do(\"transfer\", target, { to: \"account2\", amount: 500 });\n * } catch (error) {\n *   if (error instanceof InvariantError) {\n *     logger.error({\n *       action: error.action,\n *       stream: error.target.stream,\n *       actor: error.target.actor,\n *       reason: error.description,\n *       balance: error.snapshot.state.balance,\n *       attempted: error.payload.amount\n *     }, \"Invariant violation\");\n *   }\n * }\n * ```\n *\n * @see {@link Invariant} for defining business rules\n */\nexport class InvariantError<\n  TState extends Schema,\n  TEvents extends Schemas,\n  TActions extends Schemas,\n  TKey extends keyof TActions,\n  TActor extends Actor = Actor,\n> extends Error {\n  /** The action that was attempted */\n  readonly action: TKey;\n  /** The action payload that was provided */\n  readonly payload: Readonly<TActions[TKey]>;\n  /** The target stream and actor context */\n  readonly target: Target<TActor>;\n  /** The current state snapshot when invariant was checked */\n  readonly snapshot: Snapshot<TState, TEvents>;\n  /** Human-readable description of why the invariant failed */\n  readonly description: string;\n\n  constructor(\n    action: TKey,\n    payload: Readonly<TActions[TKey]>,\n    target: Target<TActor>,\n    snapshot: Snapshot<TState, TEvents>,\n    description: string\n  ) {\n    super(`${action as string} failed invariant: ${description}`);\n    this.name = Errors.InvariantError;\n    this.action = action;\n    this.payload = payload;\n    this.target = target;\n    this.snapshot = snapshot;\n    this.description = description;\n  }\n}\n\n/**\n * Thrown when optimistic concurrency control detects a conflict.\n *\n * This error occurs when trying to commit events to a stream that has been\n * modified by another process since it was last loaded. The version number\n * doesn't match expectations, indicating a concurrent modification.\n *\n * This is a normal occurrence in distributed systems and should be handled\n * by reloading the current state and retrying the action.\n *\n * @example Handling concurrency conflicts with retry\n * ```typescript\n * import { ConcurrencyError } from \"@rotorsoft/act\";\n *\n * async function transferWithRetry(from, to, amount, maxRetries = 3) {\n *   for (let attempt = 0; attempt < maxRetries; attempt++) {\n *     try {\n *       await app.do(\"transfer\",\n *         { stream: from, actor: currentUser },\n *         { to, amount }\n *       );\n *       return { success: true };\n *     } catch (error) {\n *       if (error instanceof ConcurrencyError) {\n *         if (attempt < maxRetries - 1) {\n *           console.log(`Concurrent modification detected, retrying... (${attempt + 1}/${maxRetries})`);\n *           await sleep(100 * Math.pow(2, attempt)); // Exponential backoff\n *           continue;\n *         }\n *       }\n *       throw error;\n *     }\n *   }\n *   return { success: false, reason: \"Too many concurrent modifications\" };\n * }\n * ```\n *\n * @example Logging concurrency conflicts\n * ```typescript\n * try {\n *   await app.do(\"updateInventory\", target, payload);\n * } catch (error) {\n *   if (error instanceof ConcurrencyError) {\n *     logger.warn({\n *       stream: error.stream,\n *       expectedVersion: error.expectedVersion,\n *       actualVersion: error.lastVersion,\n *       events: error.events.map(e => e.name)\n *     }, \"Concurrent modification detected\");\n *   }\n * }\n * ```\n *\n * @example User feedback for conflicts\n * ```typescript\n * try {\n *   await app.do(\"editDocument\", target, { content: newContent });\n * } catch (error) {\n *   if (error instanceof ConcurrencyError) {\n *     return {\n *       error: \"This document was modified by another user. Please refresh and try again.\",\n *       code: \"CONCURRENT_MODIFICATION\"\n *     };\n *   }\n * }\n * ```\n *\n * @see {@link Store.commit} for version checking details\n */\nexport class ConcurrencyError extends Error {\n  /** The stream that had the concurrent modification */\n  public readonly stream: string;\n  /** The actual current version in the store */\n  public readonly lastVersion: number;\n  /** The events that were being committed */\n  public readonly events: Message<Schemas, keyof Schemas>[];\n  /** The version number that was expected */\n  public readonly expectedVersion: number;\n\n  constructor(\n    stream: string,\n    lastVersion: number,\n    events: Message<Schemas, keyof Schemas>[],\n    expectedVersion: number\n  ) {\n    // Message lists stream + event names only. Payloads remain accessible\n    // via `error.events` for callers who need them — keeping them out of\n    // the message avoids MB-scale strings on contended writes and keeps\n    // potentially-sensitive data out of log streams.\n    super(\n      `Concurrency error committing \"${events\n        .map((e) => `${stream}.${e.name}`)\n        .join(\n          \", \"\n        )}\". Expected version ${expectedVersion} but found version ${lastVersion}.`\n    );\n    this.name = Errors.ConcurrencyError;\n    this.stream = stream;\n    this.lastVersion = lastVersion;\n    this.events = events;\n    this.expectedVersion = expectedVersion;\n  }\n}\n\n/**\n * Thrown when attempting to write to a stream that has been closed\n * with a tombstone event.\n *\n * A tombstoned stream is permanently closed — no further actions can\n * be executed against it. The only way to reopen a tombstoned stream\n * is through `Act.close()` with a `restart` callback.\n *\n * @example\n * ```typescript\n * import { StreamClosedError } from \"@rotorsoft/act\";\n *\n * try {\n *   await app.do(\"updateTicket\", target, payload);\n * } catch (error) {\n *   if (error instanceof StreamClosedError) {\n *     console.error(`Stream ${error.stream} is closed`);\n *   }\n * }\n * ```\n *\n * @see {@link Act.close} for closing streams\n */\nexport class StreamClosedError extends Error {\n  /** The stream that is closed */\n  public readonly stream: string;\n\n  constructor(stream: string) {\n    super(`Stream \"${stream}\" is closed (tombstoned)`);\n    this.name = Errors.StreamClosedError;\n    this.stream = stream;\n  }\n}\n\n/**\n * Thrown by a {@link Store} adapter when an infrastructure operation fails\n * for a reason that is *not* a domain condition — a dropped connection, a\n * transaction rollback, a query timeout. It is the typed boundary between\n * \"the store is unavailable/degraded\" and the domain errors above\n * ({@link ConcurrencyError}, {@link StreamClosedError}), which describe\n * legitimate outcomes the caller should branch on.\n *\n * Adapters wrap their driver errors in `StoreError` (preserving the\n * original via `cause`) so the orchestrator can distinguish a degraded\n * backend from \"no work\" and react accordingly — see the drain circuit\n * breaker, which trips on repeated `StoreError`s and surfaces an\n * `error` lifecycle event instead of silently spinning on a down\n * database.\n *\n * @example\n * ```typescript\n * app.on(\"error\", ({ error, circuit }) => {\n *   if (error instanceof StoreError)\n *     alert(`store ${error.operation} failing; circuit=${circuit}`);\n * });\n * ```\n */\nexport class StoreError extends Error {\n  /** The store operation that failed (e.g. `\"claim\"`, `\"ack\"`, `\"commit\"`). */\n  public readonly operation: string;\n\n  constructor(operation: string, options?: { cause?: unknown }) {\n    super(`Store operation \"${operation}\" failed`, options);\n    this.name = Errors.StoreError;\n    this.operation = operation;\n  }\n}\n\n/**\n * Thrown by a reaction handler to signal that the failure is permanent\n * and the drain pipeline should block the stream immediately, without\n * consuming the rest of the `maxRetries` budget.\n *\n * The drain finalizer detects `instanceof NonRetryableError` and forces\n * `block = options.blockOnError` regardless of `lease.retry`. When\n * `blockOnError` is `false`, behavior is unchanged (drain keeps retrying\n * forever) — the class never overrides the operator's explicit \"never\n * block\" choice.\n *\n * Use this for failures the handler *knows* won't get better on retry:\n * a 4xx from a webhook, a `ZodError` on malformed input, a \"user\n * deleted\" 404 from a downstream API. Use regular `Error` (or a\n * subclass) for transient failures so the existing retry-with-backoff\n * loop applies.\n *\n * @example Wrapping a permanent downstream error\n * ```typescript\n * import { NonRetryableError } from \"@rotorsoft/act\";\n *\n * .on(\"OrderConfirmed\")\n *   .do(async (event) => {\n *     const res = await fetch(url, ...);\n *     if (res.status >= 400 && res.status < 500) {\n *       throw new NonRetryableError(\n *         `webhook ${url} responded ${res.status}`,\n *         { cause: await res.text() }\n *       );\n *     }\n *     if (!res.ok) throw new Error(`webhook ${url} responded ${res.status}`);\n *   })\n * ```\n *\n * @example Marking validation failures as non-retryable\n * ```typescript\n * .on(\"PaymentReceived\")\n *   .do(async (event) => {\n *     const parsed = Schema.safeParse(event.data);\n *     if (!parsed.success) {\n *       throw new NonRetryableError(\"payment payload failed validation\", {\n *         cause: parsed.error,\n *       });\n *     }\n *     // ... handle parsed payload\n *   })\n * ```\n */\nexport class NonRetryableError extends Error {\n  /** The original failure, if any. Mirrors the standard `Error.cause` shape. */\n  public override readonly cause?: unknown;\n\n  constructor(message: string, options?: { cause?: unknown }) {\n    super(message);\n    this.name = Errors.NonRetryableError;\n    this.cause = options?.cause;\n  }\n}\n","import { type ZodObject, type ZodRawShape, z } from \"zod\";\n// Deep-path import (vs `../internal/index.js`) is deliberate — `_registry` is\n// a side-effect-free leaf, and going through the internal barrel would pull\n// tracing.ts → config.ts in at type-schema load time and crash on TDZ when a\n// test imports a public schema before config is initialized.\nimport { _mark_sensitive, _registry } from \"../internal/sensitive.js\";\n\n/**\n * @packageDocumentation\n * @module act/types\n * @category Types\n * Zod schemas and helpers for the Act Framework.\n */\n\n/**\n * An empty Zod schema (no properties).\n */\nexport const ZodEmpty = z.record(z.string(), z.never());\n\n/**\n * Sensitive-data foundation re-exports (#855 / epic #566).\n *\n * - `REDACTED` / `SHREDDED` — sentinels placed in `event.data[field]`\n *   when the caller isn't authorized to see a sensitive field\n *   (`.discloses(predicate)` returned `false` or none was declared —\n *   recoverable) or the underlying PII was wiped (`Store.forget_pii` —\n *   irrecoverable).\n *   top-level field names marked via {@link sensitive}. A pure,\n *   read-only helper that inspects the out-of-band sensitive registry (a\n *   process-global `WeakMap`) `sensitive()` populates, otherwise\n *   unreachable from outside the package. Surfaced for adapters and\n *   tooling that must reflect input sensitivity on their own wire\n *   surface — e.g. the `@rotorsoft/act-http/openapi` emitter marks these\n *   fields `writeOnly` + `format: password` so generated clients and\n *   Swagger UI don't echo PII freely. Returns `[]` for non-object\n *   schemas or objects with no sensitive fields; top-level shape only.\n */\nexport { pii_fields, REDACTED, SHREDDED } from \"../internal/sensitive.js\";\n\n/**\n * Mark a Zod schema as sensitive. Returns the same schema instance — the\n * marker is registered out-of-band so the static type is preserved and the\n * call site reads as a pure annotation.\n *\n * Idempotent: re-wrapping an already-sensitive schema is a no-op.\n *\n * The marker is what the orchestrator inspects to split event payloads into\n * `data` + `pii` on commit, gate reads via `.discloses`, and strip handler\n * payloads. Part of the sensitive-data foundation (#855 / epic #566).\n *\n * @example\n * ```ts\n * import { z } from \"zod\";\n * import { state, sensitive } from \"@rotorsoft/act\";\n *\n * const UserRegistered = z.object({\n *   email: sensitive(z.string()),\n *   name: sensitive(z.string()),\n *   plan: z.enum([\"free\", \"pro\"]),  // not sensitive — stays in events.data\n * });\n * ```\n *\n * @param schema - The Zod schema to mark sensitive.\n * @returns The same schema instance, unmodified at the type level.\n */\nexport function sensitive<T extends z.ZodType>(schema: T): T {\n  _registry.add(schema, { sensitive: true });\n  // Also stamp the def, so the marker survives the clone Zod produces for\n  // any refinement chained AFTER this call — `sensitive(z.string()).min(1)`\n  // used to lose it silently and write plaintext into `events.data` (#1417).\n  _mark_sensitive(schema);\n  return schema;\n}\n\n/**\n * Zod schema for an actor (user, system, etc.).\n */\nexport const ActorSchema = z\n  .object({\n    id: z.string(),\n    name: z.string(),\n  })\n  .loose()\n  .readonly();\n\n/**\n * Zod schema for a target (stream and actor info).\n */\nexport const TargetSchema = z\n  .object({\n    stream: z.string(),\n    actor: ActorSchema,\n    expectedVersion: z.number().optional(),\n  })\n  .loose()\n  .readonly();\n\n/**\n * Zod schema for causation event metadata.\n */\nexport const CausationEventSchema = z.object({\n  id: z.number(),\n  name: z.string(),\n  stream: z.string(),\n});\n\n/**\n * Zod schema for event metadata (correlation and causation).\n */\nexport const EventMetaSchema = z\n  .object({\n    correlation: z.string(),\n    causation: z.object({\n      action: TargetSchema.and(z.object({ name: z.string() })).optional(),\n      event: CausationEventSchema.optional(),\n    }),\n  })\n  .readonly();\n\n/**\n * Zod schema for committed event metadata (id, stream, version, created, meta).\n */\nexport const CommittedMetaSchema = z\n  .object({\n    id: z.number(),\n    stream: z.string(),\n    version: z.number(),\n    created: z.date(),\n    meta: EventMetaSchema,\n  })\n  .readonly();\n\n/**\n * Type representing the full state schema for a domain.\n * @property events - Map of event names to Zod schemas.\n * @property actions - Map of action names to Zod schemas.\n * @property state - Zod schema for the state object.\n */\nexport type StateSchema = Readonly<{\n  events: Record<string, ZodObject<ZodRawShape> | typeof ZodEmpty>;\n  actions: Record<string, ZodObject<ZodRawShape> | typeof ZodEmpty>;\n  state: ZodObject<ZodRawShape>;\n}>;\n\n/**\n * Query options for event store queries.\n */\nexport const QuerySchema = z\n  .object({\n    stream: z.string().optional(),\n    names: z.string().array().optional(),\n    before: z.number().optional(),\n    after: z.number().optional(),\n    limit: z.number().optional(),\n    created_before: z.date().optional(),\n    created_after: z.date().optional(),\n    backward: z.boolean().optional(),\n    correlation: z.string().optional(),\n    with_snaps: z.boolean().optional(),\n    stream_exact: z.boolean().optional(),\n  })\n  .readonly();\n","/**\n * @module sensitive\n * @category Internal\n *\n * Internal mechanics for the sensitive-data foundation (#855 / epic #566).\n * The public surface (`sensitive(zodType)`) lives at `libs/act/src/sensitive.ts`\n * and re-exports `REDACTED` / `SHREDDED` from here; this module holds the\n * registry plus the helpers the orchestrator calls during commit, load, and\n * handler dispatch.\n *\n * - `_registry` — process-global `z.registry<{ sensitive: true }>()`. Public\n *   `sensitive()` adds to it; the helpers in this module read it.\n * - `pii_fields(schema)` — walk a Zod schema's top-level shape, return the\n *   keys marked via `sensitive(...)`.\n * - `pii_gate(event, fields, predicate, actor)` — produce the external\n *   view: plaintext when authorized, `[REDACTED]` when not, `[SHREDDED]`\n *   when the underlying pii column is null.\n * - `pii_strip(event, fields)` — remove sensitive keys entirely\n *   before invoking projection / reaction handlers.\n *\n * @internal\n */\n\nimport { z } from \"zod\";\nimport type { Actor, Committed, Schemas } from \"../types/index.js\";\n\n/**\n * Sentinel placed in `event.data[field]` when the caller isn't authorized to\n * see the sensitive field — either `.discloses(predicate)` returned `false`,\n * or no predicate was declared (framework default-deny). Recoverable: a\n * properly-authorized read returns the plaintext.\n *\n * Re-exported from `libs/act/src/sensitive.ts` as part of the public surface.\n */\nexport const REDACTED = \"[REDACTED]\" as const;\n\n/**\n * Sentinel placed in `event.data[field]` when the underlying PII payload has\n * been wiped via `Store.forget_pii(stream)` — the row's pii column is `NULL`\n * and the original plaintext is gone forever. Irrecoverable.\n *\n * Re-exported from `libs/act/src/sensitive.ts` as part of the public surface.\n */\nexport const SHREDDED = \"[SHREDDED]\" as const;\n\n/**\n * Process-global registry holding every Zod schema marked sensitive. Backed\n * by a `WeakMap`, so wrapper-created instances (`.optional()`, `.nullable()`,\n * `.default()`) that chain off a marked schema produce *new* schema instances\n * the registry doesn't track; the field walker handles those via unwrap.\n *\n * Exported so the public `sensitive(zodType)` wrapper can call `_registry.add`.\n * Underscore prefix marks \"framework-private, don't touch from user code.\"\n *\n * @internal\n */\nexport const _registry = z.registry<{ sensitive: true }>();\n\n/**\n * Marker key stamped onto a schema's own `def`. Zod clones a schema on every\n * refinement (`.min()`, `.email()`, `.trim()`, `.describe()`, `.refine()`,\n * `.transform()`, …) via `{...def}`, which copies own symbol keys — so a\n * marker on the def survives the whole chain, while the `_registry` WeakMap\n * (keyed on the *instance*) does not (#1417).\n *\n * The registry is still populated and still consulted: it covers schemas\n * marked before this key existed, and it is the mechanism the public\n * `sensitive()` doc-comment describes.\n *\n * @internal\n */\nexport const _SENSITIVE = Symbol.for(\"act.sensitive\");\n\n/**\n * Stamp the def-level marker. Called by the public `sensitive()` alongside\n * `_registry.add`.\n *\n * @internal\n */\nexport function _mark_sensitive(schema: z.ZodType): void {\n  const def = (\n    schema as unknown as {\n      _zod?: { def?: Record<PropertyKey, unknown> };\n    }\n  )._zod?.def;\n  if (def) def[_SENSITIVE] = true;\n}\n\n/**\n * True when the given schema was marked via `sensitive(...)`.\n *\n * Walks through Zod wrapper layers (`.optional()`, `.nullable()`,\n * `.default()`, `.readonly()`) by following `_def.innerType` until it reaches\n * a non-wrapper schema, then checks the registry. Wrappers create new schema\n * instances; the marker lives on the *inner* schema the user wrapped, so we\n * test that one.\n *\n * @internal\n */\nexport function is_pii(schema: z.ZodType): boolean {\n  let cur: z.ZodType = schema;\n  while (true) {\n    if (_registry.has(cur)) return true;\n    // Def-level marker: survives the clone a refinement produces, which the\n    // instance-keyed registry above cannot (#1417).\n    const def = (\n      cur as unknown as {\n        _zod?: { def?: Record<PropertyKey, unknown> };\n      }\n    )._zod?.def;\n    if (def?.[_SENSITIVE] === true) return true;\n    const inner = (cur as { _def?: { innerType?: z.ZodType } })._def?.innerType;\n    if (!inner || inner === cur) return false;\n    cur = inner;\n  }\n}\n\n/**\n * Derive an event's sensitive fields, as the declared schema of each.\n *\n * Walks the top-level shape of a `z.object({...})` and keeps the keys whose\n * schema (after unwrapping optional/nullable/default wrappers) was marked via\n * `sensitive(...)`. Returns an empty object for non-object schemas or events\n * with no sensitive fields — the common-case zero-cost path.\n *\n * Only the top-level shape is walked. Sensitive fields nested inside a\n * `z.object` declared inside the event payload would require recursive\n * descent; that's deferred until a real callsite needs it.\n *\n * A union event has no top-level shape, so the options are walked and merged:\n * a key sensitive in any variant must be split, because the stored payload\n * could be that variant ([#1417](https://github.com/Rotorsoft/act-root/issues/1417)).\n * The first variant to declare a key wins, which only matters to a caller that\n * wants the schema rather than the name.\n *\n * Returning the schemas rather than just the names is what lets a caller do\n * something per field — the event builder asks each one whether it holds a\n * date, so the `pii` sidecar's dates can be revived like any other.\n *\n * @internal\n */\nexport function pii_schemas(schema: z.ZodType): Record<string, z.ZodType> {\n  const shape = (schema as { shape?: Record<string, z.ZodType> }).shape;\n  if (shape && typeof shape === \"object\") {\n    const fields: Record<string, z.ZodType> = {};\n    for (const key of Object.keys(shape))\n      if (is_pii(shape[key])) fields[key] = shape[key];\n    return fields;\n  }\n  const options = (schema as { options?: unknown }).options;\n  if (Array.isArray(options)) {\n    const fields: Record<string, z.ZodType> = {};\n    for (const option of options)\n      for (const [key, field] of Object.entries(\n        pii_schemas(option as z.ZodType)\n      ))\n        fields[key] ??= field;\n    return fields;\n  }\n  return {};\n}\n\n/**\n * The names of an event's sensitive fields — {@link pii_schemas} keyed.\n *\n * @internal — consumed by the registry's `sensitive_fields(event_name)` lookup,\n * and public through `types/schemas.ts`, where act-http's OpenAPI emitter uses\n * it to mark request-body properties `writeOnly`.\n */\nexport function pii_fields(schema: z.ZodType): readonly string[] {\n  return Object.keys(pii_schemas(schema));\n}\n\n/**\n * Split an emitted event's `data` into `data` (non-sensitive) + `pii`\n * (sensitive) using the field list precomputed at build time. Used by the\n * State's `_pii_split` decorator just before `Store.commit`.\n *\n * Single forward pass over `Object.keys(validated)` — same shape as the\n * spread-and-delete-free implementation in slice 3, just hoisted out of the\n * orchestrator hot path so it's only invoked when the State actually has a\n * sensitive event.\n *\n * @internal\n */\nexport function pii_split<TName, TData extends Record<string, unknown>>(\n  emitted: { name: TName; data: TData },\n  fields: readonly string[]\n): { name: TName; data: TData; pii: Record<string, unknown> } {\n  const data = { ...emitted.data };\n  const pii: Record<string, unknown> = {};\n  for (const f of fields) {\n    if (f in data) {\n      pii[f] = data[f];\n      delete data[f];\n    }\n  }\n  return { name: emitted.name, data, pii };\n}\n\n/**\n * Build the **external view** of a committed event — the form returned by\n * `load()`, `query()`, `query_array()`, and the snapshot in `do()`'s reply.\n *\n * Only ever reached for events that declare sensitive fields — the sole caller\n * is {@link make_gate}, which the builder invokes exclusively for sensitive\n * events; non-sensitive events short-circuit to {@link IDENTITY_GATE} before\n * they get here. `fields` is therefore guaranteed non-empty (same contract as\n * {@link pii_strip}).\n *\n * - Event whose `pii` payload is null/undefined → substitute {@link SHREDDED}\n *   for each declared field. Irrecoverable, so no predicate check.\n * - Event with a `pii` payload, predicate returns `true` → merge `pii` into\n *   `data` (plaintext).\n * - Event with a `pii` payload, predicate returns `false` OR no predicate\n *   declared (framework default-deny) → substitute {@link REDACTED} for each\n *   declared field.\n *\n * @internal\n */\nexport function pii_gate<TEvents extends Schemas, TKey extends keyof TEvents>(\n  event: Committed<TEvents, TKey>,\n  fields: readonly string[],\n  predicate: ((event: any, actor: Actor) => boolean) | null,\n  actor: Actor | undefined\n): Committed<TEvents, TKey> {\n  const data = event.data as Record<string, unknown>;\n  // The external view NEVER carries the isolated `pii` sidecar — dropping it\n  // is the whole point of the gate. Keeping it (an earlier `...event` spread)\n  // leaked plaintext PII on every gated read surface (`load`, `query`,\n  // `query_array`) even while `data` was correctly redacted (#1277). Strip it\n  // once here; the plaintext lives in `data` only on the authorized path.\n  const { pii, ...rest } = event as Committed<TEvents, TKey> & {\n    pii?: Record<string, unknown> | null;\n  };\n  if (pii == null) {\n    const shredded: Record<string, unknown> = { ...data };\n    for (const f of fields) shredded[f] = SHREDDED;\n    return { ...rest, data: shredded as Committed<TEvents, TKey>[\"data\"] };\n  }\n  // Plaintext path requires both an actor AND a predicate that allows. Missing\n  // either → default-deny → REDACTED.\n  const allowed = !!actor && !!predicate && predicate(event, actor);\n  if (allowed) {\n    return {\n      ...rest,\n      data: { ...data, ...pii } as Committed<TEvents, TKey>[\"data\"],\n    };\n  }\n  const redacted: Record<string, unknown> = { ...data };\n  for (const f of fields) redacted[f] = REDACTED;\n  return { ...rest, data: redacted as Committed<TEvents, TKey>[\"data\"] };\n}\n\n/**\n * A prebuilt per-event read gate: given a committed event and the reading\n * actor, return the caller-visible form. This is the single gating primitive\n * the builder prebuilds for **every** read surface — both the actor-less\n * `query` / `query_array` (which pass no actor → default-deny) and the\n * actor-aware `load` / `do`-return view (which pass the reader). Non-sensitive\n * events use the shared {@link IDENTITY_GATE}; sensitive events use a redactor\n * built by {@link make_gate} that closes over the field list and the state's\n * disclosure predicate, so the read path never recomputes the sensitive-field\n * lookup nor allocates per event.\n *\n * The `actor` is optional so the actor-less surfaces can call `gate(event)`.\n *\n * @internal\n */\nexport type EventGate = <TEvents extends Schemas, TKey extends keyof TEvents>(\n  event: Committed<TEvents, TKey>,\n  actor?: Actor\n) => Committed<TEvents, TKey>;\n\n/**\n * Shared zero-cost gate for every event with no `sensitive(...)` fields — a\n * single frozen reference the builder hands back for non-sensitive events, so\n * the common path is one `Map` miss and an identity call, no allocation. This\n * is the \"by default, return the event\" half of the prebuilt per-event gate.\n *\n * @internal\n */\nexport const IDENTITY_GATE: EventGate = (event) => event;\n\n/**\n * Prebuild a read gate for a sensitive event, capturing its field list and the\n * disclosure predicate once at build time. The returned closure defers to\n * {@link pii_gate} with the reading actor supplied per call:\n *\n * - `predicate = null` (the actor-less `query` surfaces, or a state that never\n *   declared `.discloses`) → default-deny: declared fields come back\n *   {@link REDACTED} (or {@link SHREDDED} once the pii column is forgotten).\n * - `predicate` set + an authorized actor → plaintext merged into `data`.\n *\n * Either way the isolated `pii` sidecar is dropped. The builder stores one gate\n * per sensitive event (per state for the load path; predicate-less for the\n * query path); non-sensitive events fall back to {@link IDENTITY_GATE}.\n *\n * @internal\n */\nexport function make_gate(\n  fields: readonly string[],\n  predicate: ((event: any, actor: Actor) => boolean) | null\n): EventGate {\n  return (event, actor) => pii_gate(event, fields, predicate, actor);\n}\n\n/**\n * Build the **handler view** — sensitive keys removed entirely from `data`\n * and the `pii` field dropped from the event. Used before invoking projection\n * handlers and reaction handlers, which never see PII by framework rule.\n *\n * Different from {@link pii_gate} (which substitutes {@link REDACTED} or\n * {@link SHREDDED}) — projection tables and reaction sinks shouldn't even\n * structurally observe the keys, so a handler that mistakenly writes\n * `event.data.email` into a column would get `undefined`, not a sentinel\n * string that looks like real data. The strictness is deliberate.\n *\n * Reactions that genuinely need PII (e.g. a welcome-email reaction reading\n * `email`) opt back in by explicitly calling `app.load(stream, { actor:\n * system_actor })` inside the handler — pulling PII through the gate at the\n * call site makes the security-relevant path visible in code review.\n *\n * @internal\n */\nexport function pii_strip<\n  TEvents extends Schemas,\n  TKey extends keyof TEvents & string,\n>(\n  event: Committed<TEvents, TKey>,\n  fields: readonly string[]\n): Committed<TEvents, TKey> {\n  // Contract: `fields` is non-empty. `build_handle` / `build_handle_batch`\n  // filter on `fields.length > 0` before invocation.\n  const data = event.data as Record<string, unknown>;\n  const stripped: Record<string, unknown> = {};\n  for (const k of Object.keys(data)) {\n    if (!fields.includes(k)) stripped[k] = data[k];\n  }\n  const { pii: _drop_pii, ...rest } = event as Committed<TEvents, TKey> & {\n    pii?: unknown;\n  };\n  return {\n    ...rest,\n    data: stripped as Committed<TEvents, TKey>[\"data\"],\n  } as Committed<TEvents, TKey>;\n}\n"],"mappings":"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","names":["import_zod"]}