{
  "slug": "nest-notifications",
  "name": "@gj-kit/nest-notifications",
  "version": "0.1.2",
  "description": "NestJS notification relay and dispatch primitives with durable stores, typed outcomes, and Expo push adapters.",
  "homepage": "https://gj-kit.github.io/gj-kit/packages/nest-notifications/",
  "repository": "git+https://github.com/gj-kit/gj-kit.git",
  "license": "MIT",
  "engines": {
    "node": ">=20"
  },
  "peerDependencies": {
    "@nestjs/common": "^10 || ^11",
    "reflect-metadata": "^0.1.13 || ^0.2",
    "rxjs": "^7"
  },
  "peerDependenciesMeta": {},
  "entries": [
    {
      "subpath": ".",
      "id": "root",
      "declarationTarget": "./dist/index.d.cts",
      "symbols": [
        {
          "name": "AppendItemInput",
          "slug": "append-item-input",
          "kind": "interface",
          "declaration": "interface AppendItemInput {\n    readonly applicationKey: string;\n    readonly deliveryId: string;\n    readonly sourceOutboxId: string;\n    readonly at: Date;\n}"
        },
        {
          "name": "BatchIdentity",
          "slug": "batch-identity",
          "kind": "interface",
          "declaration": "/** The five columns whose combination must be unique per delivery (R5). */\ninterface BatchIdentity {\n    readonly applicationKey: string;\n    readonly recipientRef: string;\n    readonly batchKey: string;\n    readonly batchWindowStartedAt: Date;\n    readonly batchPolicyKey: string;\n}",
          "sourceDocumentation": "The five columns whose combination must be unique per delivery (R5)."
        },
        {
          "name": "ClaimedNotificationCommand",
          "slug": "claimed-notification-command",
          "kind": "interface",
          "declaration": "interface ClaimedNotificationCommand {\n    readonly id: string;\n    readonly applicationKey: string;\n    readonly recipientRef: string;\n    readonly actorRef: string | null;\n    readonly targetRef: string | null;\n    readonly category: string;\n    /** A plain string: narrow it with `notificationPriorityFrom`. */\n    readonly priority: string;\n    readonly title: string | null;\n    readonly body: string;\n    readonly action: NotificationAction | null;\n    readonly eventKey: string;\n    readonly batchKey: string | null;\n    readonly batchLabel: string | null;\n    readonly batchItemCount: number;\n    readonly timing: NotificationTiming;\n    /**\n     * When this row entered the ingress outbox. The library never writes it: it has\n     * no staging method (staging belongs to the host's `NotificationPublisher`), so\n     * this timestamp comes from the host's staging path (R13). It is also the input\n     * to the batch bucket, which is why R13 makes it an obligation rather than a\n     * field description.\n     */\n    readonly createdAt: Date;\n    /** How many times a worker has claimed this row, including this claim (R13). */\n    readonly attempts: number;\n}"
        },
        {
          "name": "ClaimedNotificationDelivery",
          "slug": "claimed-notification-delivery",
          "kind": "interface",
          "declaration": "interface ClaimedNotificationDelivery {\n    readonly id: string;\n    readonly applicationKey: string;\n    readonly recipientRef: string;\n    readonly actorRef: string | null;\n    readonly category: string;\n    readonly priority: string;\n    readonly title: string | null;\n    readonly body: string;\n    readonly action: NotificationAction | null;\n    readonly batchCount: number;\n    readonly batchItemCount: number;\n    readonly aggregationLabel: string | null;\n    /** How many times a worker has claimed this delivery, including this claim (D9). */\n    readonly attempts: number;\n}"
        },
        {
          "name": "CreateDeliveryInput",
          "slug": "create-delivery-input",
          "kind": "interface",
          "declaration": "interface CreateDeliveryInput {\n    readonly applicationKey: string;\n    readonly recipientRef: string;\n    readonly actorRef: string | null;\n    readonly category: string;\n    readonly priority: NotificationPriority;\n    readonly title: string | null;\n    readonly body: string;\n    readonly action: NotificationAction | null;\n    /** Null for a standalone delivery: the batch unique constraint then does not apply. */\n    readonly batchKey: string | null;\n    readonly batchWindowStartedAt: Date | null;\n    readonly batchPolicyKey: string | null;\n    readonly aggregationLabel: string | null;\n    readonly batchCount: number;\n    readonly batchItemCount: number;\n    /** Not dispatchable before this instant (D5). */\n    readonly deliverAfter: Date;\n    readonly createdAt: Date;\n}"
        },
        {
          "name": "CreateDeliveryResult",
          "slug": "create-delivery-result",
          "kind": "interface",
          "declaration": "/**\n * `created: false` means a delivery with this batch identity already existed and\n * `id` is that row. It is NOT an error and MUST NOT throw (R11): the caller falls\n * back to `mergeIntoBatch`, and to the follow-up route when that fails.\n * Appending an item to a delivery you did not create can bind it to a\n * presentation-locked row, which loses the notification silently (design 0.3-7).\n */\ninterface CreateDeliveryResult {\n    readonly id: string;\n    readonly created: boolean;\n}",
          "sourceDocumentation": "`created: false` means a delivery with this batch identity already existed and\n`id` is that row. It is NOT an error and MUST NOT throw (R11): the caller falls\nback to `mergeIntoBatch`, and to the follow-up route when that fails.\nAppending an item to a delivery you did not create can bind it to a\npresentation-locked row, which loses the notification silently (design 0.3-7)."
        },
        {
          "name": "DispatchClaimRequest",
          "slug": "dispatch-claim-request",
          "kind": "interface",
          "declaration": "interface DispatchClaimRequest {\n    readonly applicationKey: string;\n    readonly limit: number;\n    /** From the injected clock. Also the due cutoff for `deliverAfter` (D5). */\n    readonly at: Date;\n    /** A duration; the store compares it against its own clock (D8). */\n    readonly claimStaleMs: number;\n    /** Same predicate and the same \"pass count, not duration\" caveat as R13 (D9). */\n    readonly maxAttempts?: number | undefined;\n    readonly claimToken: string;\n}"
        },
        {
          "name": "DispatchCompleteRequest",
          "slug": "dispatch-complete-request",
          "kind": "interface",
          "declaration": "interface DispatchCompleteRequest {\n    readonly applicationKey: string;\n    readonly deliveryId: string;\n    readonly claimToken: string;\n    readonly at: Date;\n}"
        },
        {
          "name": "DispatchReleaseRequest",
          "slug": "dispatch-release-request",
          "kind": "interface",
          "declaration": "interface DispatchReleaseRequest {\n    readonly applicationKey: string;\n    readonly deliveryId: string;\n    readonly claimToken: string;\n    /** Already redacted: a stable short code, never an exception message. */\n    readonly errorCode: string | null;\n}"
        },
        {
          "name": "DispatchTransactionRequest",
          "slug": "dispatch-transaction-request",
          "kind": "interface",
          "declaration": "interface DispatchTransactionRequest {\n    readonly applicationKey: string;\n    readonly deliveryId: string;\n    readonly claimToken: string;\n    readonly at: Date;\n}"
        },
        {
          "name": "EnsureMessageInput",
          "slug": "ensure-message-input",
          "kind": "interface",
          "declaration": "interface EnsureMessageInput {\n    readonly applicationKey: string;\n    readonly deliveryId: string;\n    readonly recipientRef: string;\n    readonly actorRef: string | null;\n    readonly category: string;\n    readonly priority: NotificationPriority;\n    readonly title: string | null;\n    readonly body: string;\n    readonly action: NotificationAction | null;\n    readonly at: Date;\n}"
        },
        {
          "name": "fromNestLogger",
          "slug": "from-nest-logger",
          "kind": "function",
          "declaration": "/**\n * Adapts Nest's `LoggerService` (message-first) to the `NotificationLogger` port\n * (fields-first). The structured fields are passed through as an extra\n * parameter, which Nest's own console logger prints alongside the message.\n */\ndeclare function fromNestLogger(logger: LoggerService, context?: string): NotificationLogger;",
          "sourceDocumentation": "Adapts Nest's `LoggerService` (message-first) to the `NotificationLogger` port\n(fields-first). The structured fields are passed through as an extra\nparameter, which Nest's own console logger prints alongside the message."
        },
        {
          "name": "MergeBatchInput",
          "slug": "merge-batch-input",
          "kind": "interface",
          "declaration": "interface MergeBatchInput {\n    readonly applicationKey: string;\n    readonly deliveryId: string;\n    /** Added to `batchCount`. Always 1 today; a parameter so a store never guesses. */\n    readonly addedCount: number;\n    readonly addedItemCount: number;\n    readonly aggregationLabel: string | null;\n    readonly at: Date;\n}"
        },
        {
          "name": "NestNotificationsAsyncOptions",
          "slug": "nest-notifications-async-options",
          "kind": "interface",
          "declaration": "interface NestNotificationsAsyncOptions {\n    readonly imports?: DynamicModule['imports'] | undefined;\n    readonly inject?: readonly InjectionToken[] | undefined;\n    readonly useFactory: (...deps: readonly any[]) => NestNotificationsOptions | Promise<NestNotificationsOptions>;\n}"
        },
        {
          "name": "NestNotificationsModule",
          "slug": "nest-notifications-module",
          "kind": "class",
          "declaration": "/**\n * The notification pipeline as a Nest module.\n *\n * Deliberately not `@Global()`: a host that wants these providers everywhere says\n * so itself. Everything the pipeline needs is a required option, because a\n * silently working default would mean the library chose someone's product policy\n * — the store trio, the presenter, the policy and the application key have no\n * defaults for exactly that reason.\n */\ndeclare class NestNotificationsModule {\n    /**\n     * Synchronous wiring. Configuration is validated here, at assembly time: an\n     * empty `applicationKey` or `providers` list fails to boot rather than failing\n     * on the scheduler's first call.\n     */\n    static forRoot(options: NestNotificationsOptions): DynamicModule;\n    /**\n     * Asynchronous wiring, for hosts whose stores or policy come from other\n     * providers. The same assembly-time validation runs immediately after the\n     * factory resolves, so the boot-failure guarantee survives this path.\n     */\n    static forRootAsync(options: NestNotificationsAsyncOptions): DynamicModule;\n}",
          "sourceDocumentation": "The notification pipeline as a Nest module.\n\nDeliberately not `@Global()`: a host that wants these providers everywhere says\nso itself. Everything the pipeline needs is a required option, because a\nsilently working default would mean the library chose someone's product policy\n— the store trio, the presenter, the policy and the application key have no\ndefaults for exactly that reason."
        },
        {
          "name": "NestNotificationsOptions",
          "slug": "nest-notifications-options",
          "kind": "interface",
          "declaration": "interface NestNotificationsOptions {\n    readonly applicationKey: string;\n    readonly relayStore: NotificationRelayStore;\n    readonly deliveryStore: NotificationDeliveryStore;\n    readonly endpointStore: NotificationEndpointStore;\n    readonly pushGateway: NotificationPushGateway;\n    readonly presenter: NotificationPresenter;\n    readonly policy: NotificationSchedulingPolicy;\n    /** Which endpoint providers the gateway handles. Must be non-empty. */\n    readonly providers: readonly string[];\n    /**\n     * Exposed through `NOTIFICATION_PUBLISHER` for source-domain code. The pipeline\n     * never calls it: staging happens in the host's own transaction.\n     */\n    readonly publisher?: NotificationPublisher<never> | undefined;\n    readonly logger?: NotificationLogger | undefined;\n    /**\n     * Shared by the relay, the dispatcher and the wakeup hint - one instance, not\n     * three. Defaults to `systemNotificationRuntime()`. Without this a consumer\n     * wired through `forRoot` cannot fix the clock, so their own quiet-hours and\n     * batch-window behaviour is untestable, and `defer` cannot be swapped for a\n     * serverless host (design 0.2-12).\n     */\n    readonly runtime?: NotificationRuntime | undefined;\n    readonly wakeup?: {\n        readonly enabled?: boolean | undefined;\n    } | undefined;\n    readonly relay?: Pick<NotificationRelayOptions, 'pageSize' | 'claimStaleMs' | 'maxAttempts'> | undefined;\n    readonly dispatch?: Pick<NotificationDispatcherOptions, 'pageSize' | 'claimStaleMs' | 'maxAttempts' | 'disableRejectedEndpoints'> | undefined;\n}"
        },
        {
          "name": "NOTIFICATION_APPLICATION_KEY",
          "slug": "notification-application-key",
          "kind": "constant",
          "declaration": "NOTIFICATION_APPLICATION_KEY: unique symbol",
          "sourceDocumentation": "The server-owned application key every store call is scoped by."
        },
        {
          "name": "NOTIFICATION_DELIVERY_STORE",
          "slug": "notification-delivery-store",
          "kind": "constant",
          "declaration": "NOTIFICATION_DELIVERY_STORE: unique symbol"
        },
        {
          "name": "NOTIFICATION_ENDPOINT_STORE",
          "slug": "notification-endpoint-store",
          "kind": "constant",
          "declaration": "NOTIFICATION_ENDPOINT_STORE: unique symbol"
        },
        {
          "name": "NOTIFICATION_LOGGER",
          "slug": "notification-logger",
          "kind": "constant",
          "declaration": "NOTIFICATION_LOGGER: unique symbol",
          "sourceDocumentation": "Lets a host swap the logger through DI instead of only through `forRoot`."
        },
        {
          "name": "NOTIFICATION_PIPELINE_WAKEUP",
          "slug": "notification-pipeline-wakeup",
          "kind": "constant",
          "declaration": "NOTIFICATION_PIPELINE_WAKEUP: unique symbol",
          "sourceDocumentation": "The best-effort wakeup hint. Correctness still belongs to a periodic runner."
        },
        {
          "name": "NOTIFICATION_PRESENTER",
          "slug": "notification-presenter",
          "kind": "constant",
          "declaration": "NOTIFICATION_PRESENTER: unique symbol"
        },
        {
          "name": "NOTIFICATION_PUBLISHER",
          "slug": "notification-publisher",
          "kind": "constant",
          "declaration": "NOTIFICATION_PUBLISHER: unique symbol",
          "sourceDocumentation": "The host's `NotificationPublisher`, when it wired one. `null` otherwise."
        },
        {
          "name": "NOTIFICATION_PUSH_GATEWAY",
          "slug": "notification-push-gateway",
          "kind": "constant",
          "declaration": "NOTIFICATION_PUSH_GATEWAY: unique symbol"
        },
        {
          "name": "NOTIFICATION_RELAY_STORE",
          "slug": "notification-relay-store",
          "kind": "constant",
          "declaration": "NOTIFICATION_RELAY_STORE: unique symbol"
        },
        {
          "name": "NOTIFICATION_RUNTIME",
          "slug": "notification-runtime",
          "kind": "constant",
          "declaration": "NOTIFICATION_RUNTIME: unique symbol",
          "sourceDocumentation": "The one runtime the three runners share. Injectable so a host can fix the clock."
        },
        {
          "name": "NOTIFICATION_SCHEDULING_POLICY",
          "slug": "notification-scheduling-policy",
          "kind": "constant",
          "declaration": "NOTIFICATION_SCHEDULING_POLICY: unique symbol"
        },
        {
          "name": "NotificationAccountLifecycle",
          "slug": "notification-account-lifecycle",
          "kind": "interface",
          "declaration": "/**\n * Host bridge for account deletion. Call both methods inside the same host\n * transaction as the deletion itself; the ordering obligations L1-L4 are the\n * entire basis of G7 (\"no delivery after a tombstone\"), and nothing in this\n * library can enforce them.\n *\n * - **L1** — `tombstone` and every delete run in one transaction with the\n *   account deletion. Commit the tombstone first and a relay running in between\n *   leaves a delivery behind; commit the purge first and a late stage creates a\n *   new outbox row.\n * - **L2** — delete ingress rows *before* deliveries: the ingress `DELETE` blocks\n *   on the relay transaction's row lock (R7), so that relay serialises either\n *   side of this statement, and the delivery/message deletes that follow remove\n *   whatever it just committed. Delete deliveries first and a relay that\n *   committed in between survives the deletion and pushes.\n * - **L3** — the tombstone row survives; everything else goes.\n * - **L4** — `anonymizeActor` clears actor references left in *other* recipients'\n *   messages. A recipient purge does not do it for you.\n *\n * The full order is: tombstone -> ingress -> delivery -> message -> endpoint ->\n * preference.\n */\ninterface NotificationAccountLifecycle<Transaction = unknown> {\n    purgeRecipient(transaction: Transaction, applicationKey: string, recipientRef: string): Promise<void>;\n    anonymizeActor(transaction: Transaction, applicationKey: string, actorRef: string): Promise<void>;\n}",
          "sourceDocumentation": "Host bridge for account deletion. Call both methods inside the same host\ntransaction as the deletion itself; the ordering obligations L1-L4 are the\nentire basis of G7 (\"no delivery after a tombstone\"), and nothing in this\nlibrary can enforce them.\n\n- **L1** — `tombstone` and every delete run in one transaction with the\n  account deletion. Commit the tombstone first and a relay running in between\n  leaves a delivery behind; commit the purge first and a late stage creates a\n  new outbox row.\n- **L2** — delete ingress rows *before* deliveries: the ingress `DELETE` blocks\n  on the relay transaction's row lock (R7), so that relay serialises either\n  side of this statement, and the delivery/message deletes that follow remove\n  whatever it just committed. Delete deliveries first and a relay that\n  committed in between survives the deletion and pushes.\n- **L3** — the tombstone row survives; everything else goes.\n- **L4** — `anonymizeActor` clears actor references left in *other* recipients'\n  messages. A recipient purge does not do it for you.\n\nThe full order is: tombstone -> ingress -> delivery -> message -> endpoint ->\npreference."
        },
        {
          "name": "NotificationAction",
          "slug": "notification-action",
          "kind": "type",
          "declaration": "/** The client-visible action is intentionally transport and domain agnostic. */\ntype NotificationAction = {\n    readonly href?: string | undefined;\n    readonly [key: string]: NotificationJsonValue | undefined;\n};",
          "sourceDocumentation": "The client-visible action is intentionally transport and domain agnostic."
        },
        {
          "name": "NotificationBatch",
          "slug": "notification-batch",
          "kind": "interface",
          "declaration": "interface NotificationBatch {\n    readonly key: string;\n    readonly label?: string | undefined;\n    readonly itemCount?: number | undefined;\n}"
        },
        {
          "name": "NotificationBatchWindow",
          "slug": "notification-batch-window",
          "kind": "interface",
          "declaration": "/** Half-open aggregation bucket `[startedAt, endsAt)`. Never spans a local midnight. */\ninterface NotificationBatchWindow {\n    readonly startedAt: Date;\n    readonly endsAt: Date;\n}",
          "sourceDocumentation": "Half-open aggregation bucket `[startedAt, endsAt)`. Never spans a local midnight."
        },
        {
          "name": "NotificationClock",
          "slug": "notification-clock",
          "kind": "interface",
          "declaration": "/** The single source of \"now\" for every policy decision the pipeline makes. */\ninterface NotificationClock {\n    now(): Date;\n}",
          "sourceDocumentation": "The single source of \"now\" for every policy decision the pipeline makes."
        },
        {
          "name": "NotificationCommand",
          "slug": "notification-command",
          "kind": "interface",
          "declaration": "/**\n * One source recipient plus one stable event key is the idempotency boundary\n * (design 3.1 G1). `applicationKey` is server-owned configuration: API callers\n * must never choose it.\n *\n * Delivery order between two commands is never guaranteed (G8). Quiet-hours\n * holds, batch windows, scheduled timings, parallel workers and per-item retries\n * all reorder deliveries, so a domain that needs an order must put it in the\n * body.\n */\ninterface NotificationCommand {\n    readonly applicationKey: string;\n    readonly recipientRef: string;\n    readonly actorRef?: string | null | undefined;\n    readonly targetRef?: string | null | undefined;\n    readonly category: string;\n    readonly priority: NotificationPriority;\n    readonly title?: string | null | undefined;\n    readonly body: string;\n    readonly action?: NotificationAction | null | undefined;\n    readonly eventKey: string;\n    readonly batch?: NotificationBatch | null | undefined;\n    readonly timing?: NotificationTiming | undefined;\n}",
          "sourceDocumentation": "One source recipient plus one stable event key is the idempotency boundary\n(design 3.1 G1). `applicationKey` is server-owned configuration: API callers\nmust never choose it.\n\nDelivery order between two commands is never guaranteed (G8). Quiet-hours\nholds, batch windows, scheduled timings, parallel workers and per-item retries\nall reorder deliveries, so a domain that needs an order must put it in the\nbody."
        },
        {
          "name": "NotificationDeliveryStore",
          "slug": "notification-delivery-store--interface",
          "kind": "interface",
          "declaration": "interface NotificationDeliveryStore {\n    /**\n     * Atomically claim due deliveries. The claim MUST also stamp the presentation\n     * lock in the same statement (D1): split into two statements, a relay can merge\n     * an item in between and the user never sees it.\n     */\n    claimDue(request: DispatchClaimRequest): Promise<readonly ClaimedNotificationDelivery[]>;\n    materializeInTransaction<T>(request: DispatchTransactionRequest, work: (tx: NotificationDispatchTransaction) => Promise<T>): Promise<T | null>;\n    /** `false` means the claim was lost; `deliveredAt` is unchanged (D3). */\n    complete(request: DispatchCompleteRequest): Promise<boolean>;\n    /**\n     * Release a failed claim. The presentation lock is NOT released: the inbox\n     * sentence may already have been shown to the user (D1).\n     */\n    releaseClaim(request: DispatchReleaseRequest): Promise<void>;\n}"
        },
        {
          "name": "NotificationDispatcher",
          "slug": "notification-dispatcher",
          "kind": "interface",
          "declaration": "interface NotificationDispatcher {\n    dispatchDue(): Promise<NotificationDispatchSummary>;\n}"
        },
        {
          "name": "NotificationDispatcherOptions",
          "slug": "notification-dispatcher-options",
          "kind": "interface",
          "declaration": "interface NotificationDispatcherOptions {\n    readonly applicationKey: string;\n    readonly store: NotificationDeliveryStore;\n    readonly endpoints: NotificationEndpointStore;\n    readonly pushGateway: NotificationPushGateway;\n    readonly presenter: NotificationPresenter;\n    /** Which endpoint providers this gateway handles. Required: no default provider. */\n    readonly providers: readonly string[];\n    readonly runtime?: NotificationRuntime | undefined;\n    readonly logger?: NotificationLogger | undefined;\n    /** Defaults to {@link DEFAULT_DISPATCH_PAGE_SIZE}. */\n    readonly pageSize?: number | undefined;\n    /** A duration; the store compares it against its own clock (D8). */\n    readonly claimStaleMs?: number | undefined;\n    /**\n     * Deliveries already attempted this many times are left out of the page (D9).\n     * Same trade-off, same arithmetic and the same silence as\n     * `NotificationRelayOptions.maxAttempts` - read that one. The dispatch\n     * side loses less on exhaustion (the inbox message is already written, so only\n     * the push is lost), but the push is lost for good.\n     */\n    readonly maxAttempts?: number | undefined;\n    /**\n     * Disable locally rejected endpoints too. Default false: a local shape check is\n     * not a provider confirmation, and the day our check becomes stricter than the\n     * provider's it would permanently darken live devices (design 0.2-6).\n     */\n    readonly disableRejectedEndpoints?: boolean | undefined;\n}"
        },
        {
          "name": "NotificationDispatchRunner",
          "slug": "notification-dispatch-runner",
          "kind": "class",
          "declaration": "/** Injectable wrapper whose `run()` is what a scheduler calls. */\ndeclare class NotificationDispatchRunner {\n    #private;\n    constructor(dispatcher: NotificationDispatcher);\n    /** One dispatch pass. Never throws for a single failed delivery. */\n    run(): Promise<NotificationDispatchSummary>;\n}",
          "sourceDocumentation": "Injectable wrapper whose `run()` is what a scheduler calls."
        },
        {
          "name": "NotificationDispatchSummary",
          "slug": "notification-dispatch-summary",
          "kind": "type",
          "declaration": "/** A type alias for the same reason `NotificationRelaySummary` is (design 3.6). */\ntype NotificationDispatchSummary = {\n    readonly ok: boolean;\n    readonly claimed: number;\n    readonly delivered: number;\n    readonly failed: number;\n    /**\n     * Endpoints this pass asked the store to disable. `disable` returns void, so a\n     * revision that no longer matches is counted here even though the store made\n     * it a no-op (D6). It is a request count, not a row count.\n     */\n    readonly endpointsDisabled: number;\n};",
          "sourceDocumentation": "A type alias for the same reason `NotificationRelaySummary` is (design 3.6)."
        },
        {
          "name": "NotificationDispatchTransaction",
          "slug": "notification-dispatch-transaction",
          "kind": "interface",
          "declaration": "interface NotificationDispatchTransaction {\n    readDelivery(): Promise<ClaimedNotificationDelivery | null>;\n    /** Conflict-safe insert then read. Never throws on a duplicate (D2). */\n    ensureMessage(input: EnsureMessageInput): Promise<{\n        readonly id: string;\n    }>;\n}"
        },
        {
          "name": "NotificationEndpointDisableTarget",
          "slug": "notification-endpoint-disable-target",
          "kind": "interface",
          "declaration": "interface NotificationEndpointDisableTarget {\n    readonly id: string;\n    /** Exactly the value `listEnabled` returned for this endpoint. */\n    readonly revision: string;\n}"
        },
        {
          "name": "NotificationEndpointStore",
          "slug": "notification-endpoint-store--interface",
          "kind": "interface",
          "declaration": "interface NotificationEndpointStore {\n    listEnabled(input: {\n        readonly applicationKey: string;\n        readonly recipientRef: string;\n        readonly providers: readonly string[];\n    }): Promise<readonly ObservedNotificationEndpoint[]>;\n    /**\n     * Idempotent and stale-safe. An empty list is a no-op, and so is an entry whose\n     * `revision` no longer matches the stored row: the device re-registered between\n     * `listEnabled` and here, and disabling it would leave a live device dark\n     * indefinitely (D6, design 0.2-18).\n     */\n    disable(input: {\n        readonly applicationKey: string;\n        readonly endpoints: readonly NotificationEndpointDisableTarget[];\n        /** Recorded as the disable instant. From the injected clock (D7). */\n        readonly at: Date;\n    }): Promise<void>;\n}"
        },
        {
          "name": "NotificationJsonPrimitive",
          "slug": "notification-json-primitive",
          "kind": "type",
          "declaration": "type NotificationJsonPrimitive = string | number | boolean | null;"
        },
        {
          "name": "NotificationJsonValue",
          "slug": "notification-json-value",
          "kind": "type",
          "declaration": "type NotificationJsonValue = NotificationJsonPrimitive | readonly NotificationJsonValue[] | {\n    readonly [key: string]: NotificationJsonValue;\n};"
        },
        {
          "name": "NotificationLogger",
          "slug": "notification-logger--interface",
          "kind": "interface",
          "declaration": "/**\n * 구조적 로거 포트 — 소스의 `PinoLogger`(nestjs-pino) 직접 의존을 대체한다(설계 §0.2-⑮).\n * 형제 `nest-operations-jobs`의 `JobLogger`와 같은 형태라 pino 인스턴스가 그대로 대입되고,\n * Nest 내장 `Logger`는 `.` 서브패스의 `fromNestLogger` 어댑터가 흡수한다.\n */\n/** Fields-first structured logger. A pino instance satisfies this shape as is. */\ninterface NotificationLogger {\n    info(fields: Record<string, unknown>, message: string): void;\n    warn(fields: Record<string, unknown>, message: string): void;\n    error(fields: Record<string, unknown>, message: string): void;\n}",
          "sourceDocumentation": "Fields-first structured logger. A pino instance satisfies this shape as is."
        },
        {
          "name": "NotificationPipelineWakeup",
          "slug": "notification-pipeline-wakeup--interface",
          "kind": "interface",
          "declaration": "/**\n * Post-commit latency hint. NOT an ingress and NOT a correctness dependency.\n *\n * `request()` returns nothing: there is no promise to await, no result to\n * inspect, and no error to catch. A hint may be coalesced with others, dropped\n * entirely, or fail silently.\n *\n * A periodic runner owns correctness. A host that wires only this hint will never\n * deliver a batched, quiet-hours-held, or scheduled notification, because nothing\n * calls the pipeline at the instant those become due (design 0.3-1).\n */\ninterface NotificationPipelineWakeup {\n    request(): void;\n}",
          "sourceDocumentation": "Post-commit latency hint. NOT an ingress and NOT a correctness dependency.\n\n`request()` returns nothing: there is no promise to await, no result to\ninspect, and no error to catch. A hint may be coalesced with others, dropped\nentirely, or fail silently.\n\nA periodic runner owns correctness. A host that wires only this hint will never\ndeliver a batched, quiet-hours-held, or scheduled notification, because nothing\ncalls the pipeline at the instant those become due (design 0.3-1)."
        },
        {
          "name": "NotificationPresentation",
          "slug": "notification-presentation",
          "kind": "interface",
          "declaration": "interface NotificationPresentation {\n    readonly title: string | null;\n    readonly body: string;\n    readonly action: NotificationAction | null;\n}"
        },
        {
          "name": "NotificationPresentationInput",
          "slug": "notification-presentation-input",
          "kind": "interface",
          "declaration": "/**\n * 표시 포트 — **기본 구현이 없다**(설계 §0.2-② · §3.4.4).\n *\n * 소스는 `새 알림 N건`을 하드코딩했다. 사용자가 실제로 읽는 문장은 제품 카피이고,\n * 기본값을 주면 영어권 소비자가 남의 언어를 배포하며, 중립 폴백을 주면 5건짜리 배치가\n * 첫 항목의 문장으로 나간다(둘 다 거짓말이다). 필수 옵션이면 컴파일 에러가 결정을 강제한다.\n */\ninterface NotificationPresentationInput {\n    readonly title: string | null;\n    readonly body: string;\n    readonly action: NotificationAction | null;\n    readonly category: string;\n    readonly priority: NotificationPriority;\n    /** How many source commands were merged into this delivery. */\n    readonly batchCount: number;\n    /** Sum of the merged commands' item counts. */\n    readonly batchItemCount: number;\n    readonly aggregationLabel: string | null;\n}",
          "sourceDocumentation": "표시 포트 — **기본 구현이 없다**(설계 §0.2-② · §3.4.4).\n\n소스는 `새 알림 N건`을 하드코딩했다. 사용자가 실제로 읽는 문장은 제품 카피이고,\n기본값을 주면 영어권 소비자가 남의 언어를 배포하며, 중립 폴백을 주면 5건짜리 배치가\n첫 항목의 문장으로 나간다(둘 다 거짓말이다). 필수 옵션이면 컴파일 에러가 결정을 강제한다."
        },
        {
          "name": "NotificationPresenter",
          "slug": "notification-presenter--interface",
          "kind": "interface",
          "declaration": "/**\n * Produces the sentence a person actually reads, in the inbox and in the push\n * payload. The library ships no implementation: batch copy is product copy, and\n * a default would ship one product's language to every consumer.\n *\n * A presenter that returns an empty body makes the notification invisible, which\n * the dispatcher treats as a permanent failure\n * (`ERR_NOTIFICATION_MESSAGE_NOT_VISIBLE`) rather than writing a blank inbox card.\n */\ninterface NotificationPresenter {\n    present(input: NotificationPresentationInput): NotificationPresentation;\n}",
          "sourceDocumentation": "Produces the sentence a person actually reads, in the inbox and in the push\npayload. The library ships no implementation: batch copy is product copy, and\na default would ship one product's language to every consumer.\n\nA presenter that returns an empty body makes the notification invisible, which\nthe dispatcher treats as a permanent failure\n(`ERR_NOTIFICATION_MESSAGE_NOT_VISIBLE`) rather than writing a blank inbox card."
        },
        {
          "name": "NotificationPriority",
          "slug": "notification-priority",
          "kind": "type",
          "declaration": "type NotificationPriority = 'NORMAL' | 'ESSENTIAL';"
        },
        {
          "name": "NotificationPublisher",
          "slug": "notification-publisher--interface",
          "kind": "interface",
          "declaration": "/**\n * The only port source-domain code needs. `Transaction` stays generic because\n * staging happens inside the host's own source transaction — this is the one\n * port in this package that takes a host transaction object (design 3.4.1).\n *\n * Implementations owe obligations I1-I3 (design 3.3.6): conflict-safe insert,\n * a liveness gate acquired before the insert, and a staging timestamp written\n * exactly once.\n */\ninterface NotificationPublisher<Transaction = unknown> {\n    stage(transaction: Transaction, command: NotificationCommand): Promise<NotificationStageResult>;\n}",
          "sourceDocumentation": "The only port source-domain code needs. `Transaction` stays generic because\nstaging happens inside the host's own source transaction — this is the one\nport in this package that takes a host transaction object (design 3.4.1).\n\nImplementations owe obligations I1-I3 (design 3.3.6): conflict-safe insert,\na liveness gate acquired before the insert, and a staging timestamp written\nexactly once."
        },
        {
          "name": "NotificationPushEndpoint",
          "slug": "notification-push-endpoint",
          "kind": "interface",
          "declaration": "/**\n * 전송 포트 — provider 중립(설계 §3.4.5).\n *\n * 소스가 스스로 \"provider port. 저장소도 recipient의 application identity도 모른다\"고\n * 적어 둔 경계다. 우리는 그 경계를 지운 게 아니라 지킨다 — 어떤 provider SDK도 dependency,\n * peer, optional peer 중 무엇으로도 들어오지 않는다(설계 §2.2). provider 고유 지식은\n * 별도 서브패스가 무의존 순수 함수로 소유하고, 이 파일은 그 이름조차 모른다(가드가 강제).\n */\ninterface NotificationPushEndpoint {\n    readonly id: string;\n    /** Opaque to this library. The dispatcher's `providers` option decides routing. */\n    readonly provider: string;\n    readonly address: string;\n}",
          "sourceDocumentation": "전송 포트 — provider 중립(설계 §3.4.5).\n\n소스가 스스로 \"provider port. 저장소도 recipient의 application identity도 모른다\"고\n적어 둔 경계다. 우리는 그 경계를 지운 게 아니라 지킨다 — 어떤 provider SDK도 dependency,\npeer, optional peer 중 무엇으로도 들어오지 않는다(설계 §2.2). provider 고유 지식은\n별도 서브패스가 무의존 순수 함수로 소유하고, 이 파일은 그 이름조차 모른다(가드가 강제)."
        },
        {
          "name": "NotificationPushGateway",
          "slug": "notification-push-gateway--interface",
          "kind": "interface",
          "declaration": "interface NotificationPushGateway {\n    /** Reject malformed provider addresses before they become durable endpoints. */\n    isValidEndpoint(endpoint: Pick<NotificationPushEndpoint, 'provider' | 'address'>): boolean;\n    /**\n     * Hands one delivery to the transport. Implementations absorb transport\n     * failures into `accepted: false` rather than throwing: a rejected handoff is\n     * an outcome the dispatcher records, not an exception it has to classify.\n     */\n    send(endpoints: readonly NotificationPushEndpoint[], payload: NotificationPushPayload): Promise<NotificationPushResult>;\n}"
        },
        {
          "name": "NotificationPushPayload",
          "slug": "notification-push-payload",
          "kind": "interface",
          "declaration": "interface NotificationPushPayload {\n    /** The durable inbox message id. */\n    readonly notificationId: string;\n    /**\n     * Stable across every retry of this delivery. Transports that support\n     * de-duplication or collapsing should map it onto their own key: retries are\n     * at-least-once (design 3.1 G5), and this is the only lever that reduces\n     * duplicates.\n     */\n    readonly idempotencyKey: string;\n    readonly collapseKey?: string | undefined;\n    readonly recipientRef: string;\n    readonly title: string | null;\n    readonly body: string;\n    readonly action: NotificationAction | null;\n    readonly priority: NotificationPriority;\n}"
        },
        {
          "name": "NotificationPushResult",
          "slug": "notification-push-result",
          "kind": "interface",
          "declaration": "interface NotificationPushResult {\n    /** False retains the durable delivery for retry. */\n    readonly accepted: boolean;\n    /** The provider confirmed these endpoints are gone. Safe to disable. */\n    readonly invalidEndpointIds: readonly string[];\n    /**\n     * Locally malformed addresses. NOT provider-confirmed: the dispatcher logs\n     * them and, by default, leaves them enabled (design 0.2-6). Merging the two\n     * lists is how the source could permanently disable a live device the day its\n     * own regex became stricter than the provider's.\n     */\n    readonly rejectedEndpointIds: readonly string[];\n}"
        },
        {
          "name": "NotificationQuietHours",
          "slug": "notification-quiet-hours",
          "kind": "interface",
          "declaration": "/** Half-open local-clock window `[startHour, endHour)`. `start > end` wraps midnight. */\ninterface NotificationQuietHours {\n    /** 0-23, inclusive. */\n    readonly startHour: number;\n    /** 0-23, exclusive. */\n    readonly endHour: number;\n}",
          "sourceDocumentation": "Half-open local-clock window `[startHour, endHour)`. `start > end` wraps midnight."
        },
        {
          "name": "NotificationRecipientLiveness",
          "slug": "notification-recipient-liveness",
          "kind": "interface",
          "declaration": "/**\n * ingress·계정 수명주기 포트와 의무 I1–I3 · L1–L4 (설계 §3.3.6).\n *\n * 파이프라인은 이 파일의 어떤 메서드도 호출하지 않는다. 그런데도 `./core`에 있는 이유는\n * 이 패키지의 첫 보증(G1: ingress 멱등)과 유일한 \"개인정보 사고\" 등급 보증(G7: tombstone\n * 이후 배달 0)이 전적으로 이 두 포트 위에 서 있기 때문이다. 라이브러리가 이 의무를 강제할\n * 수 없다는 사실도 그대로 적는다 — 강제하는 것은 `./testing`의 적합성 케이스뿐이다.\n */\n/**\n * Recipient lifecycle barrier. The library calls neither method: staging calls\n * `ensureLive` from the host publisher, and account deletion calls `tombstone`\n * from the host lifecycle.\n *\n * `notificationRecipientKey(applicationKey, recipientRef)` is the intended key\n * for the tombstone row: it lets an implementation retain the barrier after a\n * purge without retaining the raw recipient reference.\n *\n * Obligations (design 3.3.6):\n *\n * - **I2** — `stage` calls `ensureLive` inside its own transaction, before the\n *   insert, and writes nothing when it returns false. Acquiring - not merely\n *   reading - is what makes stage and purge serialise against each other.\n * - **L3** — the tombstone row survives the purge that follows it. Delete it and\n *   a late `ensureLive` returns true, so a deleted account starts receiving\n *   notifications again.\n */\ninterface NotificationRecipientLiveness<Transaction = unknown> {\n    /**\n     * Acquires the recipient gate inside this transaction and returns false once\n     * the ref is tombstoned (I2).\n     */\n    ensureLive(transaction: Transaction, applicationKey: string, recipientRef: string): Promise<boolean>;\n    /** Marks deletion. The tombstone row must survive the purge that follows (L3). */\n    tombstone(transaction: Transaction, applicationKey: string, recipientRef: string): Promise<void>;\n}",
          "sourceDocumentation": "Recipient lifecycle barrier. The library calls neither method: staging calls\n`ensureLive` from the host publisher, and account deletion calls `tombstone`\nfrom the host lifecycle.\n\n`notificationRecipientKey(applicationKey, recipientRef)` is the intended key\nfor the tombstone row: it lets an implementation retain the barrier after a\npurge without retaining the raw recipient reference.\n\nObligations (design 3.3.6):\n\n- **I2** — `stage` calls `ensureLive` inside its own transaction, before the\n  insert, and writes nothing when it returns false. Acquiring - not merely\n  reading - is what makes stage and purge serialise against each other.\n- **L3** — the tombstone row survives the purge that follows it. Delete it and\n  a late `ensureLive` returns true, so a deleted account starts receiving\n  notifications again."
        },
        {
          "name": "NotificationRelay",
          "slug": "notification-relay",
          "kind": "interface",
          "declaration": "interface NotificationRelay {\n    relayDue(): Promise<NotificationRelaySummary>;\n}"
        },
        {
          "name": "NotificationRelayOptions",
          "slug": "notification-relay-options",
          "kind": "interface",
          "declaration": "/**\n * 릴레이 — ingress outbox 행을 배달로 물질화한다(설계 §3.6).\n *\n * 소스 로직을 유지하되 저장소 호출만 포트로 바꾼다. 소스와 달라지는 지점은 세 개이고\n * 전부 결함 수정이다: 시각을 행마다 다시 읽고(§0.2-⑦), stale 임계를 기간으로만 넘기며\n * (R12), `createDelivery`의 `created: false`를 병합/follow-up으로 되돌린다(R11 · §0.3-⑦).\n */\ninterface NotificationRelayOptions {\n    readonly applicationKey: string;\n    readonly store: NotificationRelayStore;\n    readonly policy: NotificationSchedulingPolicy;\n    readonly runtime?: NotificationRuntime | undefined;\n    readonly logger?: NotificationLogger | undefined;\n    /** Defaults to {@link DEFAULT_RELAY_PAGE_SIZE}. */\n    readonly pageSize?: number | undefined;\n    /**\n     * Passed to the store as a duration; the store compares it against its own\n     * clock (R12). Defaults to {@link DEFAULT_CLAIM_STALE_MS}.\n     */\n    readonly claimStaleMs?: number | undefined;\n    /**\n     * Rows already attempted this many times are left out of the due page (R13).\n     * Absent means no bound - a permanently failing row is then re-claimed every\n     * pass and, at `pageSize` such rows, starves healthy notifications (design\n     * 7-16). The library owns no backoff policy (design 0.4-7); this is the only\n     * lever it offers, and choosing a value is an operational decision.\n     *\n     * **Both settings lose something, so read this before picking one.**\n     *\n     * - `attempts` counts *claims*, not elapsed time: there is no cooldown between\n     *   a `releaseClaim` and the next claim, so the retry window this buys is\n     *   `maxAttempts ÷ pass frequency`, not a duration. Every pass counts - a\n     *   periodic runner's and a `NotificationPipelineWakeup` pass alike - so with\n     *   the wakeup hint enabled a staging burst can spend the whole budget of an\n     *   unrelated failing row in seconds.\n     * - An exhausted row leaves the due page **permanently**, and nothing in this\n     *   package reports it: neither summary type counts it, and the store is asked\n     *   for a filtered page rather than for what it filtered out. Watching for\n     *   exhausted rows is a host query (design 6-15), and a host that does not run\n     *   one converts a transport outage longer than the budget into silent loss.\n     */\n    readonly maxAttempts?: number | undefined;\n}",
          "sourceDocumentation": "릴레이 — ingress outbox 행을 배달로 물질화한다(설계 §3.6).\n\n소스 로직을 유지하되 저장소 호출만 포트로 바꾼다. 소스와 달라지는 지점은 세 개이고\n전부 결함 수정이다: 시각을 행마다 다시 읽고(§0.2-⑦), stale 임계를 기간으로만 넘기며\n(R12), `createDelivery`의 `created: false`를 병합/follow-up으로 되돌린다(R11 · §0.3-⑦)."
        },
        {
          "name": "NotificationRelayOutcome",
          "slug": "notification-relay-outcome",
          "kind": "type",
          "declaration": "type NotificationRelayOutcome = 'relayed' | 'suppressed' | 'already-relayed' | 'no-longer-live';"
        },
        {
          "name": "NotificationRelayRunner",
          "slug": "notification-relay-runner",
          "kind": "class",
          "declaration": "/**\n * 러너 — 주기 실행자가 부르는 것은 `run()`이다(설계 §3.8.2).\n *\n * 반환 타입이 곧 계약이다: `run(): Promise<Summary>`가 정확성 경로이고\n * `NotificationPipelineWakeup.request(): void`가 지연 경로다. 두 요약 타입이 **type alias**라\n * `Record<string, unknown>`에 구조적으로 대입되므로, 형제 잡 패키지의 어댑터가 12줄이 된다\n * — 그것은 의존이 아니라 구조적 호환의 결과다(설계 §0.4-③).\n */\n/** Injectable wrapper whose `run()` is what a scheduler calls. */\ndeclare class NotificationRelayRunner {\n    #private;\n    constructor(relay: NotificationRelay);\n    /** One relay pass. Never throws for a single failed row; the summary reports it. */\n    run(): Promise<NotificationRelaySummary>;\n}",
          "sourceDocumentation": "Injectable wrapper whose `run()` is what a scheduler calls."
        },
        {
          "name": "NotificationRelayStore",
          "slug": "notification-relay-store--interface",
          "kind": "interface",
          "declaration": "/**\n * Ingress outbox persistence. The library owns no schema; a host maps these four\n * operations onto its own table. The obligations R1-R13 documented in the design\n * are part of the contract, and `notificationStoreContractCases()` from the\n * `./testing` subpath checks them.\n */\ninterface NotificationRelayStore {\n    /** Atomically claim up to `limit` due rows. Only rows this call actually won are returned (R1). */\n    claimDue(request: RelayClaimRequest): Promise<readonly ClaimedNotificationCommand[]>;\n    /**\n     * Run `work` in one transaction that holds the outbox row lock (R7). Resolves\n     * to `null` without running `work` when this worker no longer owns the claim.\n     */\n    relayInTransaction<T>(request: RelayTransactionRequest, work: (tx: NotificationRelayTransaction) => Promise<T>): Promise<T | null>;\n    /** `false` means the claim was lost; the stored outcome is unchanged (R8). */\n    completeClaim(request: RelayCompleteRequest): Promise<boolean>;\n    /** Release a failed claim so the next pass can retry it. Never throws for a lost claim. */\n    releaseClaim(request: RelayReleaseRequest): Promise<void>;\n}",
          "sourceDocumentation": "Ingress outbox persistence. The library owns no schema; a host maps these four\noperations onto its own table. The obligations R1-R13 documented in the design\nare part of the contract, and `notificationStoreContractCases()` from the\n`./testing` subpath checks them."
        },
        {
          "name": "NotificationRelaySummary",
          "slug": "notification-relay-summary",
          "kind": "type",
          "declaration": "/**\n * Declared as a type alias, NOT an interface. Only object type aliases get an\n * implicit index signature, so only this form is assignable to\n * `Record<string, unknown>` - which is exactly the shape a sibling job runner's\n * summary slot has. An interface fails with \"Index signature for type 'string'\n * is missing\" and the 12-line job adapter in the README stops compiling\n * (measured; design 0.4-3).\n *\n * The outcome counters can sum to less than `claimed`: a claim lost mid-pass is\n * neither an outcome nor a failure, and it is logged rather than counted.\n */\ntype NotificationRelaySummary = {\n    readonly ok: boolean;\n    readonly claimed: number;\n    readonly relayed: number;\n    readonly suppressed: number;\n    readonly alreadyRelayed: number;\n    readonly noLongerLive: number;\n    readonly failed: number;\n};",
          "sourceDocumentation": "Declared as a type alias, NOT an interface. Only object type aliases get an\nimplicit index signature, so only this form is assignable to\n`Record<string, unknown>` - which is exactly the shape a sibling job runner's\nsummary slot has. An interface fails with \"Index signature for type 'string'\nis missing\" and the 12-line job adapter in the README stops compiling\n(measured; design 0.4-3).\n\nThe outcome counters can sum to less than `claimed`: a claim lost mid-pass is\nneither an outcome nor a failure, and it is logged rather than counted."
        },
        {
          "name": "NotificationRelayTransaction",
          "slug": "notification-relay-transaction",
          "kind": "interface",
          "declaration": "interface NotificationRelayTransaction {\n    /** Re-read the locked source row. `null` means it is gone (recipient purge). */\n    readCommand(): Promise<ClaimedNotificationCommand | null>;\n    /** Category preference gate. Absent rows mean enabled. */\n    isCategoryEnabled(input: {\n        readonly recipientRef: string;\n        readonly category: string;\n    }): Promise<boolean>;\n    /** Idempotency probe for this source row (G2). */\n    findDeliveryBySource(): Promise<{\n        readonly deliveryId: string;\n    } | null>;\n    findOpenBatch(key: BatchIdentity): Promise<OpenBatchDelivery | null>;\n    /** Conditional merge. `false` means the batch closed between read and write (R6). */\n    mergeIntoBatch(input: MergeBatchInput): Promise<boolean>;\n    /** Conflict-safe. Never throws on the batch-identity unique constraint (R11). */\n    createDelivery(input: CreateDeliveryInput): Promise<CreateDeliveryResult>;\n    /** `false` means an item for this source row already existed (R4). */\n    appendItem(input: AppendItemInput): Promise<boolean>;\n}"
        },
        {
          "name": "NotificationSchedulingPolicy",
          "slug": "notification-scheduling-policy--interface",
          "kind": "interface",
          "declaration": "/**\n * Pure scheduling decisions. Implement this interface to vary policy per\n * recipient (their own zone) or per category; {@link createQuietHoursPolicy} is\n * the built-in single-zone implementation.\n */\ninterface NotificationSchedulingPolicy {\n    /** True when `at` falls inside the configured quiet window. */\n    isQuietHours(at: Date): boolean;\n    /** Earliest instant this command may be delivered. Never earlier than `now`. */\n    resolveDeliveryAt(input: ResolveDeliveryInput): Date;\n    /** Aggregation bucket that contains `at`. */\n    batchWindow(at: Date): NotificationBatchWindow;\n}",
          "sourceDocumentation": "Pure scheduling decisions. Implement this interface to vary policy per\nrecipient (their own zone) or per category; {@link createQuietHoursPolicy} is\nthe built-in single-zone implementation."
        },
        {
          "name": "NotificationsError",
          "slug": "notifications-error",
          "kind": "type",
          "declaration": "/**\n * `NotificationsError`는 **클래스**다. `export type { NotificationsError }`로 내면 dts\n * 롤업이 `type` 수식어를 떨어뜨려 산출 선언이 이 이름을 런타임 값으로 광고하고, 소비자의\n * `import { NotificationsError }`가 타입 검사만 통과한 뒤 ESM에서 모듈 인스턴스화 실패로\n * 프로세스를 죽인다(CJS에서는 `undefined`). 그래서 값이 될 수 없는 **별칭**으로 낸다.\n * 생성과 판정은 `./core`의 몫이다(`isNotificationsError`가 정본, §2.5).\n * 형제 `nest-operations-jobs`가 같은 결함에 같은 처방을 냈다.\n */\ntype NotificationsError = NotificationsError$1;",
          "sourceDocumentation": "`NotificationsError`는 **클래스**다. `export type { NotificationsError }`로 내면 dts\n롤업이 `type` 수식어를 떨어뜨려 산출 선언이 이 이름을 런타임 값으로 광고하고, 소비자의\n`import { NotificationsError }`가 타입 검사만 통과한 뒤 ESM에서 모듈 인스턴스화 실패로\n프로세스를 죽인다(CJS에서는 `undefined`). 그래서 값이 될 수 없는 **별칭**으로 낸다.\n생성과 판정은 `./core`의 몫이다(`isNotificationsError`가 정본, §2.5).\n형제 `nest-operations-jobs`가 같은 결함에 같은 처방을 냈다."
        },
        {
          "name": "NotificationsErrorCode",
          "slug": "notifications-error-code",
          "kind": "type",
          "declaration": "/**\n * 타입드 에러와 에러 코드 축약.\n *\n * 소스는 `new Error(문자열)`과 Nest 예외를 섞어 던졌다(설계 §0.2-⑧). AGENTS.md §2가\n * 요구하는 것은 안정적인 code 유니언과 type guard이고, 이 파일이 그 둘을 소유한다.\n */\n/** Stable, closed set of error codes this package throws. */\ntype NotificationsErrorCode = 'ERR_NOTIFICATION_COMMAND_INVALID' | 'ERR_NOTIFICATION_APPLICATION_KEY_INVALID' | 'ERR_NOTIFICATION_RECIPIENT_KEY_INPUT' | 'ERR_NOTIFICATION_POLICY_INVALID' | 'ERR_NOTIFICATION_TIMEZONE_INVALID' | 'ERR_NOTIFICATION_PRIORITY_UNSUPPORTED' | 'ERR_NOTIFICATION_PUSH_HANDOFF_REJECTED' | 'ERR_NOTIFICATION_MESSAGE_NOT_VISIBLE' | 'ERR_NOTIFICATION_CONFIG_INVALID';",
          "sourceDocumentation": "Stable, closed set of error codes this package throws."
        },
        {
          "name": "NotificationStageResult",
          "slug": "notification-stage-result",
          "kind": "interface",
          "declaration": "interface NotificationStageResult {\n    /** Null only when the recipient lifecycle has already tombstoned this ref. */\n    readonly id: string | null;\n    readonly staged: boolean;\n    readonly discarded?: boolean | undefined;\n}"
        },
        {
          "name": "NotificationTiming",
          "slug": "notification-timing",
          "kind": "type",
          "declaration": "/**\n * An ISO instant rather than a Date, so a command stays JSON-serialisable while\n * it waits in a durable ingress outbox.\n */\ntype NotificationTiming = {\n    readonly mode: 'IMMEDIATE';\n} | {\n    readonly mode: 'SCHEDULED';\n    readonly at: string;\n};",
          "sourceDocumentation": "An ISO instant rather than a Date, so a command stays JSON-serialisable while\nit waits in a durable ingress outbox."
        },
        {
          "name": "NotificationWakeupOptions",
          "slug": "notification-wakeup-options",
          "kind": "interface",
          "declaration": "interface NotificationWakeupOptions {\n    readonly relay: NotificationRelay;\n    readonly dispatcher: NotificationDispatcher;\n    /** Set false to make `request()` a no-op (serverless, tests). Default true. */\n    readonly enabled?: boolean | undefined;\n    readonly runtime?: NotificationRuntime | undefined;\n    readonly logger?: NotificationLogger | undefined;\n}"
        },
        {
          "name": "ObservedNotificationEndpoint",
          "slug": "observed-notification-endpoint",
          "kind": "interface",
          "declaration": "/**\n * An endpoint plus the registration revision observed when it was listed. A\n * disable computed from this observation must not survive a re-registration that\n * happened afterwards (D6).\n */\ninterface ObservedNotificationEndpoint extends NotificationPushEndpoint {\n    /**\n     * Opaque and compared only for equality. Any value that changes whenever the\n     * row is re-registered works: `lastSeenAt.toISOString()`, a version counter, or\n     * an xmin/rowversion column.\n     */\n    readonly revision: string;\n}",
          "sourceDocumentation": "An endpoint plus the registration revision observed when it was listed. A\ndisable computed from this observation must not survive a re-registration that\nhappened afterwards (D6)."
        },
        {
          "name": "OpenBatchDelivery",
          "slug": "open-batch-delivery",
          "kind": "interface",
          "declaration": "interface OpenBatchDelivery {\n    readonly id: string;\n    /** False once the delivery is claimed, presentation-locked or delivered. */\n    readonly open: boolean;\n}"
        },
        {
          "name": "QuietHoursPolicyOptions",
          "slug": "quiet-hours-policy-options",
          "kind": "interface",
          "declaration": "interface QuietHoursPolicyOptions {\n    /**\n     * IANA time zone name (for example `'Europe/Paris'`), or `'UTC'`.\n     * The library holds no regional default: this field is required.\n     */\n    readonly timeZone: string;\n    /** `null` disables quiet hours entirely. */\n    readonly quietHours?: NotificationQuietHours | null | undefined;\n    /** Aggregation window length. Must divide 24h evenly. Defaults to {@link DEFAULT_BATCH_WINDOW_MS}. */\n    readonly batchWindowMs?: number | undefined;\n    /** Priorities held during quiet hours. Defaults to `['NORMAL']`. */\n    readonly holdPriorities?: readonly NotificationPriority[] | undefined;\n}"
        },
        {
          "name": "RelayClaimRequest",
          "slug": "relay-claim-request",
          "kind": "interface",
          "declaration": "interface RelayClaimRequest {\n    readonly applicationKey: string;\n    readonly limit: number;\n    /**\n     * From the injected clock. Recorded verbatim on completion stamps and passed to\n     * the policy (R9). It is NOT the input to the staleness comparison - see\n     * `claimStaleMs`.\n     */\n    readonly at: Date;\n    /**\n     * A duration, deliberately not an instant. The store decides staleness on its\n     * own clock (`claimedAt < now() - claimStaleMs`, R12): with N workers there are\n     * N process clocks, and the only clock they share is the store's.\n     */\n    readonly claimStaleMs: number;\n    /**\n     * Skip rows already attempted this many times. Absent means no bound, which\n     * lets a permanently failing row occupy the due page forever (R13, design 7-16).\n     *\n     * The predicate is `attempts < maxAttempts` and nothing else: there is no\n     * cooldown column and no `retryAfter` field on this request or on\n     * {@link RelayReleaseRequest}, because retry timing has exactly one owner and\n     * it is the host's scheduler (design 0.4-7). A released row is therefore due\n     * again on the very next pass, so this bound is a pass count, not a duration.\n     */\n    readonly maxAttempts?: number | undefined;\n    /** Opaque token this worker writes onto every row it wins. */\n    readonly claimToken: string;\n}"
        },
        {
          "name": "RelayCompleteRequest",
          "slug": "relay-complete-request",
          "kind": "interface",
          "declaration": "interface RelayCompleteRequest {\n    readonly applicationKey: string;\n    readonly outboxId: string;\n    readonly claimToken: string;\n    readonly at: Date;\n    readonly suppressed: boolean;\n}"
        },
        {
          "name": "RelayReleaseRequest",
          "slug": "relay-release-request",
          "kind": "interface",
          "declaration": "interface RelayReleaseRequest {\n    readonly applicationKey: string;\n    readonly outboxId: string;\n    readonly claimToken: string;\n    /** Already redacted by the relay: a stable short code, never an exception message. */\n    readonly errorCode: string | null;\n}"
        },
        {
          "name": "RelayTransactionRequest",
          "slug": "relay-transaction-request",
          "kind": "interface",
          "declaration": "interface RelayTransactionRequest {\n    readonly applicationKey: string;\n    readonly outboxId: string;\n    readonly claimToken: string;\n    readonly at: Date;\n}"
        },
        {
          "name": "ResolveDeliveryInput",
          "slug": "resolve-delivery-input",
          "kind": "interface",
          "declaration": "interface ResolveDeliveryInput {\n    readonly priority: NotificationPriority;\n    readonly timing: NotificationTiming | undefined;\n    readonly now: Date;\n    /** Present so a host implementation can vary policy per recipient or category. */\n    readonly recipientRef: string;\n    readonly category: string;\n}"
        }
      ]
    },
    {
      "subpath": "./core",
      "id": "core",
      "declarationTarget": "./dist/core.d.cts",
      "symbols": [
        {
          "name": "AppendItemInput",
          "slug": "append-item-input",
          "kind": "interface",
          "declaration": "interface AppendItemInput {\n    readonly applicationKey: string;\n    readonly deliveryId: string;\n    readonly sourceOutboxId: string;\n    readonly at: Date;\n}"
        },
        {
          "name": "assertNotificationCommand",
          "slug": "assert-notification-command",
          "kind": "function",
          "declaration": "/**\n * Validates a command before it reaches a durable outbox.\n *\n * Throws {@link NotificationsError} with code `ERR_NOTIFICATION_COMMAND_INVALID`;\n * the source threw a bare `Error` (design 0.2-8).\n */\ndeclare function assertNotificationCommand(command: NotificationCommand): void;",
          "sourceDocumentation": "Validates a command before it reaches a durable outbox.\n\nThrows {@link NotificationsError } with code `ERR_NOTIFICATION_COMMAND_INVALID`;\nthe source threw a bare `Error` (design 0.2-8)."
        },
        {
          "name": "BatchIdentity",
          "slug": "batch-identity",
          "kind": "interface",
          "declaration": "/** The five columns whose combination must be unique per delivery (R5). */\ninterface BatchIdentity {\n    readonly applicationKey: string;\n    readonly recipientRef: string;\n    readonly batchKey: string;\n    readonly batchWindowStartedAt: Date;\n    readonly batchPolicyKey: string;\n}",
          "sourceDocumentation": "The five columns whose combination must be unique per delivery (R5)."
        },
        {
          "name": "ClaimedNotificationCommand",
          "slug": "claimed-notification-command",
          "kind": "interface",
          "declaration": "interface ClaimedNotificationCommand {\n    readonly id: string;\n    readonly applicationKey: string;\n    readonly recipientRef: string;\n    readonly actorRef: string | null;\n    readonly targetRef: string | null;\n    readonly category: string;\n    /** A plain string: narrow it with `notificationPriorityFrom`. */\n    readonly priority: string;\n    readonly title: string | null;\n    readonly body: string;\n    readonly action: NotificationAction | null;\n    readonly eventKey: string;\n    readonly batchKey: string | null;\n    readonly batchLabel: string | null;\n    readonly batchItemCount: number;\n    readonly timing: NotificationTiming;\n    /**\n     * When this row entered the ingress outbox. The library never writes it: it has\n     * no staging method (staging belongs to the host's `NotificationPublisher`), so\n     * this timestamp comes from the host's staging path (R13). It is also the input\n     * to the batch bucket, which is why R13 makes it an obligation rather than a\n     * field description.\n     */\n    readonly createdAt: Date;\n    /** How many times a worker has claimed this row, including this claim (R13). */\n    readonly attempts: number;\n}"
        },
        {
          "name": "ClaimedNotificationDelivery",
          "slug": "claimed-notification-delivery",
          "kind": "interface",
          "declaration": "interface ClaimedNotificationDelivery {\n    readonly id: string;\n    readonly applicationKey: string;\n    readonly recipientRef: string;\n    readonly actorRef: string | null;\n    readonly category: string;\n    readonly priority: string;\n    readonly title: string | null;\n    readonly body: string;\n    readonly action: NotificationAction | null;\n    readonly batchCount: number;\n    readonly batchItemCount: number;\n    readonly aggregationLabel: string | null;\n    /** How many times a worker has claimed this delivery, including this claim (D9). */\n    readonly attempts: number;\n}"
        },
        {
          "name": "CreateDeliveryInput",
          "slug": "create-delivery-input",
          "kind": "interface",
          "declaration": "interface CreateDeliveryInput {\n    readonly applicationKey: string;\n    readonly recipientRef: string;\n    readonly actorRef: string | null;\n    readonly category: string;\n    readonly priority: NotificationPriority;\n    readonly title: string | null;\n    readonly body: string;\n    readonly action: NotificationAction | null;\n    /** Null for a standalone delivery: the batch unique constraint then does not apply. */\n    readonly batchKey: string | null;\n    readonly batchWindowStartedAt: Date | null;\n    readonly batchPolicyKey: string | null;\n    readonly aggregationLabel: string | null;\n    readonly batchCount: number;\n    readonly batchItemCount: number;\n    /** Not dispatchable before this instant (D5). */\n    readonly deliverAfter: Date;\n    readonly createdAt: Date;\n}"
        },
        {
          "name": "CreateDeliveryResult",
          "slug": "create-delivery-result",
          "kind": "interface",
          "declaration": "/**\n * `created: false` means a delivery with this batch identity already existed and\n * `id` is that row. It is NOT an error and MUST NOT throw (R11): the caller falls\n * back to `mergeIntoBatch`, and to the follow-up route when that fails.\n * Appending an item to a delivery you did not create can bind it to a\n * presentation-locked row, which loses the notification silently (design 0.3-7).\n */\ninterface CreateDeliveryResult {\n    readonly id: string;\n    readonly created: boolean;\n}",
          "sourceDocumentation": "`created: false` means a delivery with this batch identity already existed and\n`id` is that row. It is NOT an error and MUST NOT throw (R11): the caller falls\nback to `mergeIntoBatch`, and to the follow-up route when that fails.\nAppending an item to a delivery you did not create can bind it to a\npresentation-locked row, which loses the notification silently (design 0.3-7)."
        },
        {
          "name": "createNotificationDispatcher",
          "slug": "create-notification-dispatcher",
          "kind": "function",
          "declaration": "/**\n * Builds the dispatch stage of the pipeline.\n *\n * One pass claims a page of due deliveries — the claim also stamps the\n * presentation lock, which is what freezes what the user will read (D1) — writes\n * exactly one inbox message per delivery (G4), then hands the payload to the\n * transport. The push handoff is at-least-once and lives outside the\n * transaction, so a duplicate banner is a documented cost rather than a bug\n * (design 3.1 G5).\n */\ndeclare function createNotificationDispatcher(options: NotificationDispatcherOptions): NotificationDispatcher;",
          "sourceDocumentation": "Builds the dispatch stage of the pipeline.\n\nOne pass claims a page of due deliveries — the claim also stamps the\npresentation lock, which is what freezes what the user will read (D1) — writes\nexactly one inbox message per delivery (G4), then hands the payload to the\ntransport. The push handoff is at-least-once and lives outside the\ntransaction, so a duplicate banner is a documented cost rather than a bug\n(design 3.1 G5)."
        },
        {
          "name": "createNotificationRelay",
          "slug": "create-notification-relay",
          "kind": "function",
          "declaration": "/**\n * Builds the relay stage of the pipeline.\n *\n * One pass claims a page of due ingress rows, materialises each into a delivery\n * inside the store's own transaction, and stamps completion. It is safe to run\n * concurrently on many workers: every safety property comes from the store's\n * atomicity obligations (R1-R13) rather than from this code.\n *\n * A periodic runner owns correctness. `relayDue()` is what a scheduler calls;\n * the wakeup hint is only a latency optimisation (design 0.3-1).\n */\ndeclare function createNotificationRelay(options: NotificationRelayOptions): NotificationRelay;",
          "sourceDocumentation": "Builds the relay stage of the pipeline.\n\nOne pass claims a page of due ingress rows, materialises each into a delivery\ninside the store's own transaction, and stamps completion. It is safe to run\nconcurrently on many workers: every safety property comes from the store's\natomicity obligations (R1-R13) rather than from this code.\n\nA periodic runner owns correctness. `relayDue()` is what a scheduler calls;\nthe wakeup hint is only a latency optimisation (design 0.3-1)."
        },
        {
          "name": "createNotificationWakeup",
          "slug": "create-notification-wakeup",
          "kind": "function",
          "declaration": "/**\n * Builds the best-effort wakeup hint.\n *\n * A burst of requests collapses into one pass, nothing runs on the caller's\n * stack, the deferred timer never keeps the process alive, and every failure is\n * swallowed into a single `warn` carrying only a redacted error code — a payload\n * must never reach the log from here.\n */\ndeclare function createNotificationWakeup(options: NotificationWakeupOptions): NotificationPipelineWakeup;",
          "sourceDocumentation": "Builds the best-effort wakeup hint.\n\nA burst of requests collapses into one pass, nothing runs on the caller's\nstack, the deferred timer never keeps the process alive, and every failure is\nswallowed into a single `warn` carrying only a redacted error code — a payload\nmust never reach the log from here."
        },
        {
          "name": "createQuietHoursPolicy",
          "slug": "create-quiet-hours-policy",
          "kind": "function",
          "declaration": "/**\n * Single-zone quiet-hours policy.\n *\n * Every decision is wall-clock arithmetic over an IANA zone, so a DST boundary\n * or a non-hourly offset such as +05:45 stays correct. Three interpretation\n * rules are part of the contract (design 3.2.3):\n *\n * 1. A release instant that does not exist (spring-forward gap) releases at the\n *    first instant after the gap.\n * 2. A release instant that exists twice (autumn fall-back) releases at the\n *    earlier one.\n * 3. A computed release at or before `now` advances a day and recomputes; with\n *    no valid solution inside 48 hours it delivers immediately, because an\n *    unbounded hold is indistinguishable from a lost notification.\n *\n * Assembly-time validation throws `ERR_NOTIFICATION_TIMEZONE_INVALID` or\n * `ERR_NOTIFICATION_POLICY_INVALID`, so a misconfigured deployment fails to boot\n * rather than mis-delivering quietly.\n */\ndeclare function createQuietHoursPolicy(options: QuietHoursPolicyOptions): NotificationSchedulingPolicy;",
          "sourceDocumentation": "Single-zone quiet-hours policy.\n\nEvery decision is wall-clock arithmetic over an IANA zone, so a DST boundary\nor a non-hourly offset such as +05:45 stays correct. Three interpretation\nrules are part of the contract (design 3.2.3):\n\n1. A release instant that does not exist (spring-forward gap) releases at the\n   first instant after the gap.\n2. A release instant that exists twice (autumn fall-back) releases at the\n   earlier one.\n3. A computed release at or before `now` advances a day and recomputes; with\n   no valid solution inside 48 hours it delivers immediately, because an\n   unbounded hold is indistinguishable from a lost notification.\n\nAssembly-time validation throws `ERR_NOTIFICATION_TIMEZONE_INVALID` or\n`ERR_NOTIFICATION_POLICY_INVALID`, so a misconfigured deployment fails to boot\nrather than mis-delivering quietly."
        },
        {
          "name": "DEFAULT_BATCH_WINDOW_MS",
          "slug": "default-batch-window-ms",
          "kind": "constant",
          "declaration": "DEFAULT_BATCH_WINDOW_MS = 600000",
          "sourceDocumentation": "Ten minutes. The source constant, with the region dropped from its name."
        },
        {
          "name": "DEFAULT_CLAIM_STALE_MS",
          "slug": "default-claim-stale-ms",
          "kind": "constant",
          "declaration": "DEFAULT_CLAIM_STALE_MS = 300000",
          "sourceDocumentation": "Five minutes — the source value. Long enough to survive a GC pause, short enough to recover."
        },
        {
          "name": "DEFAULT_DISPATCH_PAGE_SIZE",
          "slug": "default-dispatch-page-size",
          "kind": "constant",
          "declaration": "DEFAULT_DISPATCH_PAGE_SIZE = 100"
        },
        {
          "name": "DEFAULT_RELAY_PAGE_SIZE",
          "slug": "default-relay-page-size",
          "kind": "constant",
          "declaration": "DEFAULT_RELAY_PAGE_SIZE = 100"
        },
        {
          "name": "DispatchClaimRequest",
          "slug": "dispatch-claim-request",
          "kind": "interface",
          "declaration": "interface DispatchClaimRequest {\n    readonly applicationKey: string;\n    readonly limit: number;\n    /** From the injected clock. Also the due cutoff for `deliverAfter` (D5). */\n    readonly at: Date;\n    /** A duration; the store compares it against its own clock (D8). */\n    readonly claimStaleMs: number;\n    /** Same predicate and the same \"pass count, not duration\" caveat as R13 (D9). */\n    readonly maxAttempts?: number | undefined;\n    readonly claimToken: string;\n}"
        },
        {
          "name": "DispatchCompleteRequest",
          "slug": "dispatch-complete-request",
          "kind": "interface",
          "declaration": "interface DispatchCompleteRequest {\n    readonly applicationKey: string;\n    readonly deliveryId: string;\n    readonly claimToken: string;\n    readonly at: Date;\n}"
        },
        {
          "name": "DispatchReleaseRequest",
          "slug": "dispatch-release-request",
          "kind": "interface",
          "declaration": "interface DispatchReleaseRequest {\n    readonly applicationKey: string;\n    readonly deliveryId: string;\n    readonly claimToken: string;\n    /** Already redacted: a stable short code, never an exception message. */\n    readonly errorCode: string | null;\n}"
        },
        {
          "name": "DispatchTransactionRequest",
          "slug": "dispatch-transaction-request",
          "kind": "interface",
          "declaration": "interface DispatchTransactionRequest {\n    readonly applicationKey: string;\n    readonly deliveryId: string;\n    readonly claimToken: string;\n    readonly at: Date;\n}"
        },
        {
          "name": "EnsureMessageInput",
          "slug": "ensure-message-input",
          "kind": "interface",
          "declaration": "interface EnsureMessageInput {\n    readonly applicationKey: string;\n    readonly deliveryId: string;\n    readonly recipientRef: string;\n    readonly actorRef: string | null;\n    readonly category: string;\n    readonly priority: NotificationPriority;\n    readonly title: string | null;\n    readonly body: string;\n    readonly action: NotificationAction | null;\n    readonly at: Date;\n}"
        },
        {
          "name": "isNotificationsError",
          "slug": "is-notifications-error",
          "kind": "function",
          "declaration": "/**\n * Prefer this over `instanceof`: dual CJS/ESM loads can produce two classes, and\n * the brand survives that (design 2.5).\n */\ndeclare function isNotificationsError(value: unknown): value is NotificationsError;",
          "sourceDocumentation": "Prefer this over `instanceof`: dual CJS/ESM loads can produce two classes, and\nthe brand survives that (design 2.5)."
        },
        {
          "name": "MergeBatchInput",
          "slug": "merge-batch-input",
          "kind": "interface",
          "declaration": "interface MergeBatchInput {\n    readonly applicationKey: string;\n    readonly deliveryId: string;\n    /** Added to `batchCount`. Always 1 today; a parameter so a store never guesses. */\n    readonly addedCount: number;\n    readonly addedItemCount: number;\n    readonly aggregationLabel: string | null;\n    readonly at: Date;\n}"
        },
        {
          "name": "NotificationAccountLifecycle",
          "slug": "notification-account-lifecycle",
          "kind": "interface",
          "declaration": "/**\n * Host bridge for account deletion. Call both methods inside the same host\n * transaction as the deletion itself; the ordering obligations L1-L4 are the\n * entire basis of G7 (\"no delivery after a tombstone\"), and nothing in this\n * library can enforce them.\n *\n * - **L1** — `tombstone` and every delete run in one transaction with the\n *   account deletion. Commit the tombstone first and a relay running in between\n *   leaves a delivery behind; commit the purge first and a late stage creates a\n *   new outbox row.\n * - **L2** — delete ingress rows *before* deliveries: the ingress `DELETE` blocks\n *   on the relay transaction's row lock (R7), so that relay serialises either\n *   side of this statement, and the delivery/message deletes that follow remove\n *   whatever it just committed. Delete deliveries first and a relay that\n *   committed in between survives the deletion and pushes.\n * - **L3** — the tombstone row survives; everything else goes.\n * - **L4** — `anonymizeActor` clears actor references left in *other* recipients'\n *   messages. A recipient purge does not do it for you.\n *\n * The full order is: tombstone -> ingress -> delivery -> message -> endpoint ->\n * preference.\n */\ninterface NotificationAccountLifecycle<Transaction = unknown> {\n    purgeRecipient(transaction: Transaction, applicationKey: string, recipientRef: string): Promise<void>;\n    anonymizeActor(transaction: Transaction, applicationKey: string, actorRef: string): Promise<void>;\n}",
          "sourceDocumentation": "Host bridge for account deletion. Call both methods inside the same host\ntransaction as the deletion itself; the ordering obligations L1-L4 are the\nentire basis of G7 (\"no delivery after a tombstone\"), and nothing in this\nlibrary can enforce them.\n\n- **L1** — `tombstone` and every delete run in one transaction with the\n  account deletion. Commit the tombstone first and a relay running in between\n  leaves a delivery behind; commit the purge first and a late stage creates a\n  new outbox row.\n- **L2** — delete ingress rows *before* deliveries: the ingress `DELETE` blocks\n  on the relay transaction's row lock (R7), so that relay serialises either\n  side of this statement, and the delivery/message deletes that follow remove\n  whatever it just committed. Delete deliveries first and a relay that\n  committed in between survives the deletion and pushes.\n- **L3** — the tombstone row survives; everything else goes.\n- **L4** — `anonymizeActor` clears actor references left in *other* recipients'\n  messages. A recipient purge does not do it for you.\n\nThe full order is: tombstone -> ingress -> delivery -> message -> endpoint ->\npreference."
        },
        {
          "name": "NotificationAction",
          "slug": "notification-action",
          "kind": "type",
          "declaration": "/** The client-visible action is intentionally transport and domain agnostic. */\ntype NotificationAction = {\n    readonly href?: string | undefined;\n    readonly [key: string]: NotificationJsonValue | undefined;\n};",
          "sourceDocumentation": "The client-visible action is intentionally transport and domain agnostic."
        },
        {
          "name": "NotificationBatch",
          "slug": "notification-batch",
          "kind": "interface",
          "declaration": "interface NotificationBatch {\n    readonly key: string;\n    readonly label?: string | undefined;\n    readonly itemCount?: number | undefined;\n}"
        },
        {
          "name": "notificationBatchPolicyKey",
          "slug": "notification-batch-policy-key",
          "kind": "function",
          "declaration": "/**\n * Route key for one batch identity.\n *\n * JSON array encoding rather than a delimiter join: a category name is opaque to\n * this library and could contain whatever separator we picked (source rationale,\n * kept verbatim).\n */\ndeclare function notificationBatchPolicyKey(category: string, priority: NotificationPriority, timing: NotificationTiming): string;",
          "sourceDocumentation": "Route key for one batch identity.\n\nJSON array encoding rather than a delimiter join: a category name is opaque to\nthis library and could contain whatever separator we picked (source rationale,\nkept verbatim)."
        },
        {
          "name": "NotificationBatchWindow",
          "slug": "notification-batch-window",
          "kind": "interface",
          "declaration": "/** Half-open aggregation bucket `[startedAt, endsAt)`. Never spans a local midnight. */\ninterface NotificationBatchWindow {\n    readonly startedAt: Date;\n    readonly endsAt: Date;\n}",
          "sourceDocumentation": "Half-open aggregation bucket `[startedAt, endsAt)`. Never spans a local midnight."
        },
        {
          "name": "NotificationClock",
          "slug": "notification-clock",
          "kind": "interface",
          "declaration": "/** The single source of \"now\" for every policy decision the pipeline makes. */\ninterface NotificationClock {\n    now(): Date;\n}",
          "sourceDocumentation": "The single source of \"now\" for every policy decision the pipeline makes."
        },
        {
          "name": "NotificationCommand",
          "slug": "notification-command",
          "kind": "interface",
          "declaration": "/**\n * One source recipient plus one stable event key is the idempotency boundary\n * (design 3.1 G1). `applicationKey` is server-owned configuration: API callers\n * must never choose it.\n *\n * Delivery order between two commands is never guaranteed (G8). Quiet-hours\n * holds, batch windows, scheduled timings, parallel workers and per-item retries\n * all reorder deliveries, so a domain that needs an order must put it in the\n * body.\n */\ninterface NotificationCommand {\n    readonly applicationKey: string;\n    readonly recipientRef: string;\n    readonly actorRef?: string | null | undefined;\n    readonly targetRef?: string | null | undefined;\n    readonly category: string;\n    readonly priority: NotificationPriority;\n    readonly title?: string | null | undefined;\n    readonly body: string;\n    readonly action?: NotificationAction | null | undefined;\n    readonly eventKey: string;\n    readonly batch?: NotificationBatch | null | undefined;\n    readonly timing?: NotificationTiming | undefined;\n}",
          "sourceDocumentation": "One source recipient plus one stable event key is the idempotency boundary\n(design 3.1 G1). `applicationKey` is server-owned configuration: API callers\nmust never choose it.\n\nDelivery order between two commands is never guaranteed (G8). Quiet-hours\nholds, batch windows, scheduled timings, parallel workers and per-item retries\nall reorder deliveries, so a domain that needs an order must put it in the\nbody."
        },
        {
          "name": "NotificationDeliveryStore",
          "slug": "notification-delivery-store",
          "kind": "interface",
          "declaration": "interface NotificationDeliveryStore {\n    /**\n     * Atomically claim due deliveries. The claim MUST also stamp the presentation\n     * lock in the same statement (D1): split into two statements, a relay can merge\n     * an item in between and the user never sees it.\n     */\n    claimDue(request: DispatchClaimRequest): Promise<readonly ClaimedNotificationDelivery[]>;\n    materializeInTransaction<T>(request: DispatchTransactionRequest, work: (tx: NotificationDispatchTransaction) => Promise<T>): Promise<T | null>;\n    /** `false` means the claim was lost; `deliveredAt` is unchanged (D3). */\n    complete(request: DispatchCompleteRequest): Promise<boolean>;\n    /**\n     * Release a failed claim. The presentation lock is NOT released: the inbox\n     * sentence may already have been shown to the user (D1).\n     */\n    releaseClaim(request: DispatchReleaseRequest): Promise<void>;\n}"
        },
        {
          "name": "NotificationDispatcher",
          "slug": "notification-dispatcher",
          "kind": "interface",
          "declaration": "interface NotificationDispatcher {\n    dispatchDue(): Promise<NotificationDispatchSummary>;\n}"
        },
        {
          "name": "NotificationDispatcherOptions",
          "slug": "notification-dispatcher-options",
          "kind": "interface",
          "declaration": "interface NotificationDispatcherOptions {\n    readonly applicationKey: string;\n    readonly store: NotificationDeliveryStore;\n    readonly endpoints: NotificationEndpointStore;\n    readonly pushGateway: NotificationPushGateway;\n    readonly presenter: NotificationPresenter;\n    /** Which endpoint providers this gateway handles. Required: no default provider. */\n    readonly providers: readonly string[];\n    readonly runtime?: NotificationRuntime | undefined;\n    readonly logger?: NotificationLogger | undefined;\n    /** Defaults to {@link DEFAULT_DISPATCH_PAGE_SIZE}. */\n    readonly pageSize?: number | undefined;\n    /** A duration; the store compares it against its own clock (D8). */\n    readonly claimStaleMs?: number | undefined;\n    /**\n     * Deliveries already attempted this many times are left out of the page (D9).\n     * Same trade-off, same arithmetic and the same silence as\n     * `NotificationRelayOptions.maxAttempts` - read that one. The dispatch\n     * side loses less on exhaustion (the inbox message is already written, so only\n     * the push is lost), but the push is lost for good.\n     */\n    readonly maxAttempts?: number | undefined;\n    /**\n     * Disable locally rejected endpoints too. Default false: a local shape check is\n     * not a provider confirmation, and the day our check becomes stricter than the\n     * provider's it would permanently darken live devices (design 0.2-6).\n     */\n    readonly disableRejectedEndpoints?: boolean | undefined;\n}"
        },
        {
          "name": "NotificationDispatchSummary",
          "slug": "notification-dispatch-summary",
          "kind": "type",
          "declaration": "/** A type alias for the same reason `NotificationRelaySummary` is (design 3.6). */\ntype NotificationDispatchSummary = {\n    readonly ok: boolean;\n    readonly claimed: number;\n    readonly delivered: number;\n    readonly failed: number;\n    /**\n     * Endpoints this pass asked the store to disable. `disable` returns void, so a\n     * revision that no longer matches is counted here even though the store made\n     * it a no-op (D6). It is a request count, not a row count.\n     */\n    readonly endpointsDisabled: number;\n};",
          "sourceDocumentation": "A type alias for the same reason `NotificationRelaySummary` is (design 3.6)."
        },
        {
          "name": "NotificationDispatchTransaction",
          "slug": "notification-dispatch-transaction",
          "kind": "interface",
          "declaration": "interface NotificationDispatchTransaction {\n    readDelivery(): Promise<ClaimedNotificationDelivery | null>;\n    /** Conflict-safe insert then read. Never throws on a duplicate (D2). */\n    ensureMessage(input: EnsureMessageInput): Promise<{\n        readonly id: string;\n    }>;\n}"
        },
        {
          "name": "NotificationEndpointDisableTarget",
          "slug": "notification-endpoint-disable-target",
          "kind": "interface",
          "declaration": "interface NotificationEndpointDisableTarget {\n    readonly id: string;\n    /** Exactly the value `listEnabled` returned for this endpoint. */\n    readonly revision: string;\n}"
        },
        {
          "name": "NotificationEndpointStore",
          "slug": "notification-endpoint-store",
          "kind": "interface",
          "declaration": "interface NotificationEndpointStore {\n    listEnabled(input: {\n        readonly applicationKey: string;\n        readonly recipientRef: string;\n        readonly providers: readonly string[];\n    }): Promise<readonly ObservedNotificationEndpoint[]>;\n    /**\n     * Idempotent and stale-safe. An empty list is a no-op, and so is an entry whose\n     * `revision` no longer matches the stored row: the device re-registered between\n     * `listEnabled` and here, and disabling it would leave a live device dark\n     * indefinitely (D6, design 0.2-18).\n     */\n    disable(input: {\n        readonly applicationKey: string;\n        readonly endpoints: readonly NotificationEndpointDisableTarget[];\n        /** Recorded as the disable instant. From the injected clock (D7). */\n        readonly at: Date;\n    }): Promise<void>;\n}"
        },
        {
          "name": "notificationFollowUpBatchPolicyKey",
          "slug": "notification-follow-up-batch-policy-key",
          "kind": "function",
          "declaration": "/**\n * Route key for a follow-up delivery: an item that arrived after its batch was\n * claimed gets its own delivery rather than disappearing (design 3.1 F10).\n * Including the source outbox id keeps every follow-up unique.\n */\ndeclare function notificationFollowUpBatchPolicyKey(batchPolicyKey: string, sourceOutboxId: string): string;",
          "sourceDocumentation": "Route key for a follow-up delivery: an item that arrived after its batch was\nclaimed gets its own delivery rather than disappearing (design 3.1 F10).\nIncluding the source outbox id keeps every follow-up unique."
        },
        {
          "name": "NotificationJsonPrimitive",
          "slug": "notification-json-primitive",
          "kind": "type",
          "declaration": "type NotificationJsonPrimitive = string | number | boolean | null;"
        },
        {
          "name": "NotificationJsonValue",
          "slug": "notification-json-value",
          "kind": "type",
          "declaration": "type NotificationJsonValue = NotificationJsonPrimitive | readonly NotificationJsonValue[] | {\n    readonly [key: string]: NotificationJsonValue;\n};"
        },
        {
          "name": "NotificationLogger",
          "slug": "notification-logger",
          "kind": "interface",
          "declaration": "/**\n * 구조적 로거 포트 — 소스의 `PinoLogger`(nestjs-pino) 직접 의존을 대체한다(설계 §0.2-⑮).\n * 형제 `nest-operations-jobs`의 `JobLogger`와 같은 형태라 pino 인스턴스가 그대로 대입되고,\n * Nest 내장 `Logger`는 `.` 서브패스의 `fromNestLogger` 어댑터가 흡수한다.\n */\n/** Fields-first structured logger. A pino instance satisfies this shape as is. */\ninterface NotificationLogger {\n    info(fields: Record<string, unknown>, message: string): void;\n    warn(fields: Record<string, unknown>, message: string): void;\n    error(fields: Record<string, unknown>, message: string): void;\n}",
          "sourceDocumentation": "Fields-first structured logger. A pino instance satisfies this shape as is."
        },
        {
          "name": "NotificationPipelineWakeup",
          "slug": "notification-pipeline-wakeup",
          "kind": "interface",
          "declaration": "/**\n * Post-commit latency hint. NOT an ingress and NOT a correctness dependency.\n *\n * `request()` returns nothing: there is no promise to await, no result to\n * inspect, and no error to catch. A hint may be coalesced with others, dropped\n * entirely, or fail silently.\n *\n * A periodic runner owns correctness. A host that wires only this hint will never\n * deliver a batched, quiet-hours-held, or scheduled notification, because nothing\n * calls the pipeline at the instant those become due (design 0.3-1).\n */\ninterface NotificationPipelineWakeup {\n    request(): void;\n}",
          "sourceDocumentation": "Post-commit latency hint. NOT an ingress and NOT a correctness dependency.\n\n`request()` returns nothing: there is no promise to await, no result to\ninspect, and no error to catch. A hint may be coalesced with others, dropped\nentirely, or fail silently.\n\nA periodic runner owns correctness. A host that wires only this hint will never\ndeliver a batched, quiet-hours-held, or scheduled notification, because nothing\ncalls the pipeline at the instant those become due (design 0.3-1)."
        },
        {
          "name": "NotificationPresentation",
          "slug": "notification-presentation",
          "kind": "interface",
          "declaration": "interface NotificationPresentation {\n    readonly title: string | null;\n    readonly body: string;\n    readonly action: NotificationAction | null;\n}"
        },
        {
          "name": "NotificationPresentationInput",
          "slug": "notification-presentation-input",
          "kind": "interface",
          "declaration": "/**\n * 표시 포트 — **기본 구현이 없다**(설계 §0.2-② · §3.4.4).\n *\n * 소스는 `새 알림 N건`을 하드코딩했다. 사용자가 실제로 읽는 문장은 제품 카피이고,\n * 기본값을 주면 영어권 소비자가 남의 언어를 배포하며, 중립 폴백을 주면 5건짜리 배치가\n * 첫 항목의 문장으로 나간다(둘 다 거짓말이다). 필수 옵션이면 컴파일 에러가 결정을 강제한다.\n */\ninterface NotificationPresentationInput {\n    readonly title: string | null;\n    readonly body: string;\n    readonly action: NotificationAction | null;\n    readonly category: string;\n    readonly priority: NotificationPriority;\n    /** How many source commands were merged into this delivery. */\n    readonly batchCount: number;\n    /** Sum of the merged commands' item counts. */\n    readonly batchItemCount: number;\n    readonly aggregationLabel: string | null;\n}",
          "sourceDocumentation": "표시 포트 — **기본 구현이 없다**(설계 §0.2-② · §3.4.4).\n\n소스는 `새 알림 N건`을 하드코딩했다. 사용자가 실제로 읽는 문장은 제품 카피이고,\n기본값을 주면 영어권 소비자가 남의 언어를 배포하며, 중립 폴백을 주면 5건짜리 배치가\n첫 항목의 문장으로 나간다(둘 다 거짓말이다). 필수 옵션이면 컴파일 에러가 결정을 강제한다."
        },
        {
          "name": "NotificationPresenter",
          "slug": "notification-presenter",
          "kind": "interface",
          "declaration": "/**\n * Produces the sentence a person actually reads, in the inbox and in the push\n * payload. The library ships no implementation: batch copy is product copy, and\n * a default would ship one product's language to every consumer.\n *\n * A presenter that returns an empty body makes the notification invisible, which\n * the dispatcher treats as a permanent failure\n * (`ERR_NOTIFICATION_MESSAGE_NOT_VISIBLE`) rather than writing a blank inbox card.\n */\ninterface NotificationPresenter {\n    present(input: NotificationPresentationInput): NotificationPresentation;\n}",
          "sourceDocumentation": "Produces the sentence a person actually reads, in the inbox and in the push\npayload. The library ships no implementation: batch copy is product copy, and\na default would ship one product's language to every consumer.\n\nA presenter that returns an empty body makes the notification invisible, which\nthe dispatcher treats as a permanent failure\n(`ERR_NOTIFICATION_MESSAGE_NOT_VISIBLE`) rather than writing a blank inbox card."
        },
        {
          "name": "NotificationPriority",
          "slug": "notification-priority",
          "kind": "type",
          "declaration": "type NotificationPriority = 'NORMAL' | 'ESSENTIAL';"
        },
        {
          "name": "notificationPriorityFrom",
          "slug": "notification-priority-from",
          "kind": "function",
          "declaration": "/**\n * Narrows a stored priority string. Stores hand back plain strings, so this\n * conversion still exists; unlike the source it throws a typed error rather than\n * a bare one, and the dispatcher lets that failure kill one delivery instead of\n * the page (design 3.3.4).\n */\ndeclare function notificationPriorityFrom(value: string): NotificationPriority;",
          "sourceDocumentation": "Narrows a stored priority string. Stores hand back plain strings, so this\nconversion still exists; unlike the source it throws a typed error rather than\na bare one, and the dispatcher lets that failure kill one delivery instead of\nthe page (design 3.3.4)."
        },
        {
          "name": "NotificationPublisher",
          "slug": "notification-publisher",
          "kind": "interface",
          "declaration": "/**\n * The only port source-domain code needs. `Transaction` stays generic because\n * staging happens inside the host's own source transaction — this is the one\n * port in this package that takes a host transaction object (design 3.4.1).\n *\n * Implementations owe obligations I1-I3 (design 3.3.6): conflict-safe insert,\n * a liveness gate acquired before the insert, and a staging timestamp written\n * exactly once.\n */\ninterface NotificationPublisher<Transaction = unknown> {\n    stage(transaction: Transaction, command: NotificationCommand): Promise<NotificationStageResult>;\n}",
          "sourceDocumentation": "The only port source-domain code needs. `Transaction` stays generic because\nstaging happens inside the host's own source transaction — this is the one\nport in this package that takes a host transaction object (design 3.4.1).\n\nImplementations owe obligations I1-I3 (design 3.3.6): conflict-safe insert,\na liveness gate acquired before the insert, and a staging timestamp written\nexactly once."
        },
        {
          "name": "NotificationPushEndpoint",
          "slug": "notification-push-endpoint",
          "kind": "interface",
          "declaration": "/**\n * 전송 포트 — provider 중립(설계 §3.4.5).\n *\n * 소스가 스스로 \"provider port. 저장소도 recipient의 application identity도 모른다\"고\n * 적어 둔 경계다. 우리는 그 경계를 지운 게 아니라 지킨다 — 어떤 provider SDK도 dependency,\n * peer, optional peer 중 무엇으로도 들어오지 않는다(설계 §2.2). provider 고유 지식은\n * 별도 서브패스가 무의존 순수 함수로 소유하고, 이 파일은 그 이름조차 모른다(가드가 강제).\n */\ninterface NotificationPushEndpoint {\n    readonly id: string;\n    /** Opaque to this library. The dispatcher's `providers` option decides routing. */\n    readonly provider: string;\n    readonly address: string;\n}",
          "sourceDocumentation": "전송 포트 — provider 중립(설계 §3.4.5).\n\n소스가 스스로 \"provider port. 저장소도 recipient의 application identity도 모른다\"고\n적어 둔 경계다. 우리는 그 경계를 지운 게 아니라 지킨다 — 어떤 provider SDK도 dependency,\npeer, optional peer 중 무엇으로도 들어오지 않는다(설계 §2.2). provider 고유 지식은\n별도 서브패스가 무의존 순수 함수로 소유하고, 이 파일은 그 이름조차 모른다(가드가 강제)."
        },
        {
          "name": "NotificationPushGateway",
          "slug": "notification-push-gateway",
          "kind": "interface",
          "declaration": "interface NotificationPushGateway {\n    /** Reject malformed provider addresses before they become durable endpoints. */\n    isValidEndpoint(endpoint: Pick<NotificationPushEndpoint, 'provider' | 'address'>): boolean;\n    /**\n     * Hands one delivery to the transport. Implementations absorb transport\n     * failures into `accepted: false` rather than throwing: a rejected handoff is\n     * an outcome the dispatcher records, not an exception it has to classify.\n     */\n    send(endpoints: readonly NotificationPushEndpoint[], payload: NotificationPushPayload): Promise<NotificationPushResult>;\n}"
        },
        {
          "name": "NotificationPushPayload",
          "slug": "notification-push-payload",
          "kind": "interface",
          "declaration": "interface NotificationPushPayload {\n    /** The durable inbox message id. */\n    readonly notificationId: string;\n    /**\n     * Stable across every retry of this delivery. Transports that support\n     * de-duplication or collapsing should map it onto their own key: retries are\n     * at-least-once (design 3.1 G5), and this is the only lever that reduces\n     * duplicates.\n     */\n    readonly idempotencyKey: string;\n    readonly collapseKey?: string | undefined;\n    readonly recipientRef: string;\n    readonly title: string | null;\n    readonly body: string;\n    readonly action: NotificationAction | null;\n    readonly priority: NotificationPriority;\n}"
        },
        {
          "name": "NotificationPushResult",
          "slug": "notification-push-result",
          "kind": "interface",
          "declaration": "interface NotificationPushResult {\n    /** False retains the durable delivery for retry. */\n    readonly accepted: boolean;\n    /** The provider confirmed these endpoints are gone. Safe to disable. */\n    readonly invalidEndpointIds: readonly string[];\n    /**\n     * Locally malformed addresses. NOT provider-confirmed: the dispatcher logs\n     * them and, by default, leaves them enabled (design 0.2-6). Merging the two\n     * lists is how the source could permanently disable a live device the day its\n     * own regex became stricter than the provider's.\n     */\n    readonly rejectedEndpointIds: readonly string[];\n}"
        },
        {
          "name": "NotificationQuietHours",
          "slug": "notification-quiet-hours",
          "kind": "interface",
          "declaration": "/** Half-open local-clock window `[startHour, endHour)`. `start > end` wraps midnight. */\ninterface NotificationQuietHours {\n    /** 0-23, inclusive. */\n    readonly startHour: number;\n    /** 0-23, exclusive. */\n    readonly endHour: number;\n}",
          "sourceDocumentation": "Half-open local-clock window `[startHour, endHour)`. `start > end` wraps midnight."
        },
        {
          "name": "notificationRecipientKey",
          "slug": "notification-recipient-key",
          "kind": "function",
          "declaration": "/**\n * Stable opaque key for the recipient liveness barrier. The digest is\n * byte-identical to `sha256(applicationKey + U+0000 + recipientRef)`, so a host\n * that already stores tombstones under the source's key needs no migration.\n *\n * Throws `ERR_NOTIFICATION_RECIPIENT_KEY_INPUT` when either input contains a\n * U+0000 code point: the separator is only injective while the inputs are free\n * of it, and a length-prefixed encoding would have changed the digest (design\n * 0.2-5).\n *\n * The caller is the host, not the pipeline: a\n * {@link ../core/lifecycle!NotificationRecipientLiveness} implementation uses it\n * as the tombstone row key, which lets the barrier outlive a purge without\n * retaining the raw recipient reference.\n */\ndeclare function notificationRecipientKey(applicationKey: string, recipientRef: string): string;",
          "sourceDocumentation": "Stable opaque key for the recipient liveness barrier. The digest is\nbyte-identical to `sha256(applicationKey + U+0000 + recipientRef)`, so a host\nthat already stores tombstones under the source's key needs no migration.\n\nThrows `ERR_NOTIFICATION_RECIPIENT_KEY_INPUT` when either input contains a\nU+0000 code point: the separator is only injective while the inputs are free\nof it, and a length-prefixed encoding would have changed the digest (design\n0.2-5).\n\nThe caller is the host, not the pipeline: a\n{@link ../core/lifecycle!NotificationRecipientLiveness} implementation uses it\nas the tombstone row key, which lets the barrier outlive a purge without\nretaining the raw recipient reference."
        },
        {
          "name": "NotificationRecipientLiveness",
          "slug": "notification-recipient-liveness",
          "kind": "interface",
          "declaration": "/**\n * ingress·계정 수명주기 포트와 의무 I1–I3 · L1–L4 (설계 §3.3.6).\n *\n * 파이프라인은 이 파일의 어떤 메서드도 호출하지 않는다. 그런데도 `./core`에 있는 이유는\n * 이 패키지의 첫 보증(G1: ingress 멱등)과 유일한 \"개인정보 사고\" 등급 보증(G7: tombstone\n * 이후 배달 0)이 전적으로 이 두 포트 위에 서 있기 때문이다. 라이브러리가 이 의무를 강제할\n * 수 없다는 사실도 그대로 적는다 — 강제하는 것은 `./testing`의 적합성 케이스뿐이다.\n */\n/**\n * Recipient lifecycle barrier. The library calls neither method: staging calls\n * `ensureLive` from the host publisher, and account deletion calls `tombstone`\n * from the host lifecycle.\n *\n * `notificationRecipientKey(applicationKey, recipientRef)` is the intended key\n * for the tombstone row: it lets an implementation retain the barrier after a\n * purge without retaining the raw recipient reference.\n *\n * Obligations (design 3.3.6):\n *\n * - **I2** — `stage` calls `ensureLive` inside its own transaction, before the\n *   insert, and writes nothing when it returns false. Acquiring - not merely\n *   reading - is what makes stage and purge serialise against each other.\n * - **L3** — the tombstone row survives the purge that follows it. Delete it and\n *   a late `ensureLive` returns true, so a deleted account starts receiving\n *   notifications again.\n */\ninterface NotificationRecipientLiveness<Transaction = unknown> {\n    /**\n     * Acquires the recipient gate inside this transaction and returns false once\n     * the ref is tombstoned (I2).\n     */\n    ensureLive(transaction: Transaction, applicationKey: string, recipientRef: string): Promise<boolean>;\n    /** Marks deletion. The tombstone row must survive the purge that follows (L3). */\n    tombstone(transaction: Transaction, applicationKey: string, recipientRef: string): Promise<void>;\n}",
          "sourceDocumentation": "Recipient lifecycle barrier. The library calls neither method: staging calls\n`ensureLive` from the host publisher, and account deletion calls `tombstone`\nfrom the host lifecycle.\n\n`notificationRecipientKey(applicationKey, recipientRef)` is the intended key\nfor the tombstone row: it lets an implementation retain the barrier after a\npurge without retaining the raw recipient reference.\n\nObligations (design 3.3.6):\n\n- **I2** — `stage` calls `ensureLive` inside its own transaction, before the\n  insert, and writes nothing when it returns false. Acquiring - not merely\n  reading - is what makes stage and purge serialise against each other.\n- **L3** — the tombstone row survives the purge that follows it. Delete it and\n  a late `ensureLive` returns true, so a deleted account starts receiving\n  notifications again."
        },
        {
          "name": "NotificationRelay",
          "slug": "notification-relay",
          "kind": "interface",
          "declaration": "interface NotificationRelay {\n    relayDue(): Promise<NotificationRelaySummary>;\n}"
        },
        {
          "name": "NotificationRelayOptions",
          "slug": "notification-relay-options",
          "kind": "interface",
          "declaration": "/**\n * 릴레이 — ingress outbox 행을 배달로 물질화한다(설계 §3.6).\n *\n * 소스 로직을 유지하되 저장소 호출만 포트로 바꾼다. 소스와 달라지는 지점은 세 개이고\n * 전부 결함 수정이다: 시각을 행마다 다시 읽고(§0.2-⑦), stale 임계를 기간으로만 넘기며\n * (R12), `createDelivery`의 `created: false`를 병합/follow-up으로 되돌린다(R11 · §0.3-⑦).\n */\ninterface NotificationRelayOptions {\n    readonly applicationKey: string;\n    readonly store: NotificationRelayStore;\n    readonly policy: NotificationSchedulingPolicy;\n    readonly runtime?: NotificationRuntime | undefined;\n    readonly logger?: NotificationLogger | undefined;\n    /** Defaults to {@link DEFAULT_RELAY_PAGE_SIZE}. */\n    readonly pageSize?: number | undefined;\n    /**\n     * Passed to the store as a duration; the store compares it against its own\n     * clock (R12). Defaults to {@link DEFAULT_CLAIM_STALE_MS}.\n     */\n    readonly claimStaleMs?: number | undefined;\n    /**\n     * Rows already attempted this many times are left out of the due page (R13).\n     * Absent means no bound - a permanently failing row is then re-claimed every\n     * pass and, at `pageSize` such rows, starves healthy notifications (design\n     * 7-16). The library owns no backoff policy (design 0.4-7); this is the only\n     * lever it offers, and choosing a value is an operational decision.\n     *\n     * **Both settings lose something, so read this before picking one.**\n     *\n     * - `attempts` counts *claims*, not elapsed time: there is no cooldown between\n     *   a `releaseClaim` and the next claim, so the retry window this buys is\n     *   `maxAttempts ÷ pass frequency`, not a duration. Every pass counts - a\n     *   periodic runner's and a `NotificationPipelineWakeup` pass alike - so with\n     *   the wakeup hint enabled a staging burst can spend the whole budget of an\n     *   unrelated failing row in seconds.\n     * - An exhausted row leaves the due page **permanently**, and nothing in this\n     *   package reports it: neither summary type counts it, and the store is asked\n     *   for a filtered page rather than for what it filtered out. Watching for\n     *   exhausted rows is a host query (design 6-15), and a host that does not run\n     *   one converts a transport outage longer than the budget into silent loss.\n     */\n    readonly maxAttempts?: number | undefined;\n}",
          "sourceDocumentation": "릴레이 — ingress outbox 행을 배달로 물질화한다(설계 §3.6).\n\n소스 로직을 유지하되 저장소 호출만 포트로 바꾼다. 소스와 달라지는 지점은 세 개이고\n전부 결함 수정이다: 시각을 행마다 다시 읽고(§0.2-⑦), stale 임계를 기간으로만 넘기며\n(R12), `createDelivery`의 `created: false`를 병합/follow-up으로 되돌린다(R11 · §0.3-⑦)."
        },
        {
          "name": "NotificationRelayOutcome",
          "slug": "notification-relay-outcome",
          "kind": "type",
          "declaration": "type NotificationRelayOutcome = 'relayed' | 'suppressed' | 'already-relayed' | 'no-longer-live';"
        },
        {
          "name": "NotificationRelayStore",
          "slug": "notification-relay-store",
          "kind": "interface",
          "declaration": "/**\n * Ingress outbox persistence. The library owns no schema; a host maps these four\n * operations onto its own table. The obligations R1-R13 documented in the design\n * are part of the contract, and `notificationStoreContractCases()` from the\n * `./testing` subpath checks them.\n */\ninterface NotificationRelayStore {\n    /** Atomically claim up to `limit` due rows. Only rows this call actually won are returned (R1). */\n    claimDue(request: RelayClaimRequest): Promise<readonly ClaimedNotificationCommand[]>;\n    /**\n     * Run `work` in one transaction that holds the outbox row lock (R7). Resolves\n     * to `null` without running `work` when this worker no longer owns the claim.\n     */\n    relayInTransaction<T>(request: RelayTransactionRequest, work: (tx: NotificationRelayTransaction) => Promise<T>): Promise<T | null>;\n    /** `false` means the claim was lost; the stored outcome is unchanged (R8). */\n    completeClaim(request: RelayCompleteRequest): Promise<boolean>;\n    /** Release a failed claim so the next pass can retry it. Never throws for a lost claim. */\n    releaseClaim(request: RelayReleaseRequest): Promise<void>;\n}",
          "sourceDocumentation": "Ingress outbox persistence. The library owns no schema; a host maps these four\noperations onto its own table. The obligations R1-R13 documented in the design\nare part of the contract, and `notificationStoreContractCases()` from the\n`./testing` subpath checks them."
        },
        {
          "name": "NotificationRelaySummary",
          "slug": "notification-relay-summary",
          "kind": "type",
          "declaration": "/**\n * Declared as a type alias, NOT an interface. Only object type aliases get an\n * implicit index signature, so only this form is assignable to\n * `Record<string, unknown>` - which is exactly the shape a sibling job runner's\n * summary slot has. An interface fails with \"Index signature for type 'string'\n * is missing\" and the 12-line job adapter in the README stops compiling\n * (measured; design 0.4-3).\n *\n * The outcome counters can sum to less than `claimed`: a claim lost mid-pass is\n * neither an outcome nor a failure, and it is logged rather than counted.\n */\ntype NotificationRelaySummary = {\n    readonly ok: boolean;\n    readonly claimed: number;\n    readonly relayed: number;\n    readonly suppressed: number;\n    readonly alreadyRelayed: number;\n    readonly noLongerLive: number;\n    readonly failed: number;\n};",
          "sourceDocumentation": "Declared as a type alias, NOT an interface. Only object type aliases get an\nimplicit index signature, so only this form is assignable to\n`Record<string, unknown>` - which is exactly the shape a sibling job runner's\nsummary slot has. An interface fails with \"Index signature for type 'string'\nis missing\" and the 12-line job adapter in the README stops compiling\n(measured; design 0.4-3).\n\nThe outcome counters can sum to less than `claimed`: a claim lost mid-pass is\nneither an outcome nor a failure, and it is logged rather than counted."
        },
        {
          "name": "NotificationRelayTransaction",
          "slug": "notification-relay-transaction",
          "kind": "interface",
          "declaration": "interface NotificationRelayTransaction {\n    /** Re-read the locked source row. `null` means it is gone (recipient purge). */\n    readCommand(): Promise<ClaimedNotificationCommand | null>;\n    /** Category preference gate. Absent rows mean enabled. */\n    isCategoryEnabled(input: {\n        readonly recipientRef: string;\n        readonly category: string;\n    }): Promise<boolean>;\n    /** Idempotency probe for this source row (G2). */\n    findDeliveryBySource(): Promise<{\n        readonly deliveryId: string;\n    } | null>;\n    findOpenBatch(key: BatchIdentity): Promise<OpenBatchDelivery | null>;\n    /** Conditional merge. `false` means the batch closed between read and write (R6). */\n    mergeIntoBatch(input: MergeBatchInput): Promise<boolean>;\n    /** Conflict-safe. Never throws on the batch-identity unique constraint (R11). */\n    createDelivery(input: CreateDeliveryInput): Promise<CreateDeliveryResult>;\n    /** `false` means an item for this source row already existed (R4). */\n    appendItem(input: AppendItemInput): Promise<boolean>;\n}"
        },
        {
          "name": "NotificationRuntime",
          "slug": "notification-runtime",
          "kind": "interface",
          "declaration": "interface NotificationRuntime {\n    readonly clock: NotificationClock;\n    /** Opaque claim token. Must be unguessable enough that two workers never collide. */\n    claimToken(): string;\n    /** Defers work off the caller's stack. Must not keep the process alive. */\n    defer(work: () => void): void;\n}"
        },
        {
          "name": "NotificationSchedulingPolicy",
          "slug": "notification-scheduling-policy",
          "kind": "interface",
          "declaration": "/**\n * Pure scheduling decisions. Implement this interface to vary policy per\n * recipient (their own zone) or per category; {@link createQuietHoursPolicy} is\n * the built-in single-zone implementation.\n */\ninterface NotificationSchedulingPolicy {\n    /** True when `at` falls inside the configured quiet window. */\n    isQuietHours(at: Date): boolean;\n    /** Earliest instant this command may be delivered. Never earlier than `now`. */\n    resolveDeliveryAt(input: ResolveDeliveryInput): Date;\n    /** Aggregation bucket that contains `at`. */\n    batchWindow(at: Date): NotificationBatchWindow;\n}",
          "sourceDocumentation": "Pure scheduling decisions. Implement this interface to vary policy per\nrecipient (their own zone) or per category; {@link createQuietHoursPolicy} is\nthe built-in single-zone implementation."
        },
        {
          "name": "NotificationsError",
          "slug": "notifications-error",
          "kind": "class",
          "declaration": "/** Error thrown by every public entry point of this package. */\ndeclare class NotificationsError extends Error {\n    /** Stable code. Compare against this, never against `message`. */\n    readonly code: NotificationsErrorCode;\n    constructor(code: NotificationsErrorCode, message: string, options?: {\n        readonly cause?: unknown;\n    });\n}",
          "sourceDocumentation": "Error thrown by every public entry point of this package."
        },
        {
          "name": "NotificationsErrorCode",
          "slug": "notifications-error-code",
          "kind": "type",
          "declaration": "/**\n * 타입드 에러와 에러 코드 축약.\n *\n * 소스는 `new Error(문자열)`과 Nest 예외를 섞어 던졌다(설계 §0.2-⑧). AGENTS.md §2가\n * 요구하는 것은 안정적인 code 유니언과 type guard이고, 이 파일이 그 둘을 소유한다.\n */\n/** Stable, closed set of error codes this package throws. */\ntype NotificationsErrorCode = 'ERR_NOTIFICATION_COMMAND_INVALID' | 'ERR_NOTIFICATION_APPLICATION_KEY_INVALID' | 'ERR_NOTIFICATION_RECIPIENT_KEY_INPUT' | 'ERR_NOTIFICATION_POLICY_INVALID' | 'ERR_NOTIFICATION_TIMEZONE_INVALID' | 'ERR_NOTIFICATION_PRIORITY_UNSUPPORTED' | 'ERR_NOTIFICATION_PUSH_HANDOFF_REJECTED' | 'ERR_NOTIFICATION_MESSAGE_NOT_VISIBLE' | 'ERR_NOTIFICATION_CONFIG_INVALID';",
          "sourceDocumentation": "Stable, closed set of error codes this package throws."
        },
        {
          "name": "NotificationStageResult",
          "slug": "notification-stage-result",
          "kind": "interface",
          "declaration": "interface NotificationStageResult {\n    /** Null only when the recipient lifecycle has already tombstoned this ref. */\n    readonly id: string | null;\n    readonly staged: boolean;\n    readonly discarded?: boolean | undefined;\n}"
        },
        {
          "name": "NotificationTiming",
          "slug": "notification-timing",
          "kind": "type",
          "declaration": "/**\n * An ISO instant rather than a Date, so a command stays JSON-serialisable while\n * it waits in a durable ingress outbox.\n */\ntype NotificationTiming = {\n    readonly mode: 'IMMEDIATE';\n} | {\n    readonly mode: 'SCHEDULED';\n    readonly at: string;\n};",
          "sourceDocumentation": "An ISO instant rather than a Date, so a command stays JSON-serialisable while\nit waits in a durable ingress outbox."
        },
        {
          "name": "NotificationWakeupOptions",
          "slug": "notification-wakeup-options",
          "kind": "interface",
          "declaration": "interface NotificationWakeupOptions {\n    readonly relay: NotificationRelay;\n    readonly dispatcher: NotificationDispatcher;\n    /** Set false to make `request()` a no-op (serverless, tests). Default true. */\n    readonly enabled?: boolean | undefined;\n    readonly runtime?: NotificationRuntime | undefined;\n    readonly logger?: NotificationLogger | undefined;\n}"
        },
        {
          "name": "ObservedNotificationEndpoint",
          "slug": "observed-notification-endpoint",
          "kind": "interface",
          "declaration": "/**\n * An endpoint plus the registration revision observed when it was listed. A\n * disable computed from this observation must not survive a re-registration that\n * happened afterwards (D6).\n */\ninterface ObservedNotificationEndpoint extends NotificationPushEndpoint {\n    /**\n     * Opaque and compared only for equality. Any value that changes whenever the\n     * row is re-registered works: `lastSeenAt.toISOString()`, a version counter, or\n     * an xmin/rowversion column.\n     */\n    readonly revision: string;\n}",
          "sourceDocumentation": "An endpoint plus the registration revision observed when it was listed. A\ndisable computed from this observation must not survive a re-registration that\nhappened afterwards (D6)."
        },
        {
          "name": "OpenBatchDelivery",
          "slug": "open-batch-delivery",
          "kind": "interface",
          "declaration": "interface OpenBatchDelivery {\n    readonly id: string;\n    /** False once the delivery is claimed, presentation-locked or delivered. */\n    readonly open: boolean;\n}"
        },
        {
          "name": "QuietHoursPolicyOptions",
          "slug": "quiet-hours-policy-options",
          "kind": "interface",
          "declaration": "interface QuietHoursPolicyOptions {\n    /**\n     * IANA time zone name (for example `'Europe/Paris'`), or `'UTC'`.\n     * The library holds no regional default: this field is required.\n     */\n    readonly timeZone: string;\n    /** `null` disables quiet hours entirely. */\n    readonly quietHours?: NotificationQuietHours | null | undefined;\n    /** Aggregation window length. Must divide 24h evenly. Defaults to {@link DEFAULT_BATCH_WINDOW_MS}. */\n    readonly batchWindowMs?: number | undefined;\n    /** Priorities held during quiet hours. Defaults to `['NORMAL']`. */\n    readonly holdPriorities?: readonly NotificationPriority[] | undefined;\n}"
        },
        {
          "name": "RelayClaimRequest",
          "slug": "relay-claim-request",
          "kind": "interface",
          "declaration": "interface RelayClaimRequest {\n    readonly applicationKey: string;\n    readonly limit: number;\n    /**\n     * From the injected clock. Recorded verbatim on completion stamps and passed to\n     * the policy (R9). It is NOT the input to the staleness comparison - see\n     * `claimStaleMs`.\n     */\n    readonly at: Date;\n    /**\n     * A duration, deliberately not an instant. The store decides staleness on its\n     * own clock (`claimedAt < now() - claimStaleMs`, R12): with N workers there are\n     * N process clocks, and the only clock they share is the store's.\n     */\n    readonly claimStaleMs: number;\n    /**\n     * Skip rows already attempted this many times. Absent means no bound, which\n     * lets a permanently failing row occupy the due page forever (R13, design 7-16).\n     *\n     * The predicate is `attempts < maxAttempts` and nothing else: there is no\n     * cooldown column and no `retryAfter` field on this request or on\n     * {@link RelayReleaseRequest}, because retry timing has exactly one owner and\n     * it is the host's scheduler (design 0.4-7). A released row is therefore due\n     * again on the very next pass, so this bound is a pass count, not a duration.\n     */\n    readonly maxAttempts?: number | undefined;\n    /** Opaque token this worker writes onto every row it wins. */\n    readonly claimToken: string;\n}"
        },
        {
          "name": "RelayCompleteRequest",
          "slug": "relay-complete-request",
          "kind": "interface",
          "declaration": "interface RelayCompleteRequest {\n    readonly applicationKey: string;\n    readonly outboxId: string;\n    readonly claimToken: string;\n    readonly at: Date;\n    readonly suppressed: boolean;\n}"
        },
        {
          "name": "RelayReleaseRequest",
          "slug": "relay-release-request",
          "kind": "interface",
          "declaration": "interface RelayReleaseRequest {\n    readonly applicationKey: string;\n    readonly outboxId: string;\n    readonly claimToken: string;\n    /** Already redacted by the relay: a stable short code, never an exception message. */\n    readonly errorCode: string | null;\n}"
        },
        {
          "name": "RelayTransactionRequest",
          "slug": "relay-transaction-request",
          "kind": "interface",
          "declaration": "interface RelayTransactionRequest {\n    readonly applicationKey: string;\n    readonly outboxId: string;\n    readonly claimToken: string;\n    readonly at: Date;\n}"
        },
        {
          "name": "ResolveDeliveryInput",
          "slug": "resolve-delivery-input",
          "kind": "interface",
          "declaration": "interface ResolveDeliveryInput {\n    readonly priority: NotificationPriority;\n    readonly timing: NotificationTiming | undefined;\n    readonly now: Date;\n    /** Present so a host implementation can vary policy per recipient or category. */\n    readonly recipientRef: string;\n    readonly category: string;\n}"
        },
        {
          "name": "safeErrorCode",
          "slug": "safe-error-code",
          "kind": "function",
          "declaration": "/**\n * Shortens any thrown value to a stable, secret-free code.\n *\n * The exception message is never part of the result: it can carry recipient\n * data, connection strings or tokens, and the value produced here is written to\n * the host's store and to logs (design 3.4.6).\n */\ndeclare function safeErrorCode(error: unknown, limit?: number): string;",
          "sourceDocumentation": "Shortens any thrown value to a stable, secret-free code.\n\nThe exception message is never part of the result: it can carry recipient\ndata, connection strings or tokens, and the value produced here is written to\nthe host's store and to logs (design 3.4.6)."
        },
        {
          "name": "silentNotificationLogger",
          "slug": "silent-notification-logger",
          "kind": "function",
          "declaration": "/** Discards everything. The default when a host wires no logger. */\ndeclare function silentNotificationLogger(): NotificationLogger;",
          "sourceDocumentation": "Discards everything. The default when a host wires no logger."
        },
        {
          "name": "systemNotificationRuntime",
          "slug": "system-notification-runtime",
          "kind": "function",
          "declaration": "/** Uses `Date`, `crypto.randomUUID`, and an unref'd `setTimeout(0)`. */\ndeclare function systemNotificationRuntime(): NotificationRuntime;",
          "sourceDocumentation": "Uses `Date`, `crypto.randomUUID`, and an unref'd `setTimeout(0)`."
        }
      ]
    },
    {
      "subpath": "./expo",
      "id": "expo",
      "declarationTarget": "./dist/expo.d.cts",
      "symbols": [
        {
          "name": "chunkExpoPushMessages",
          "slug": "chunk-expo-push-messages",
          "kind": "function",
          "declaration": "/**\n * Splits entries into request-sized chunks. Each chunk keeps its endpoints beside\n * its messages, so ticket attribution is a data-structure fact rather than an\n * assumption about a third-party chunker's ordering (design 0.3-4).\n */\ndeclare function chunkExpoPushMessages(entries: readonly ExpoPushEntry[], options?: {\n    readonly chunkSize?: number | undefined;\n}): readonly (readonly ExpoPushEntry[])[];",
          "sourceDocumentation": "Splits entries into request-sized chunks. Each chunk keeps its endpoints beside\nits messages, so ticket attribution is a data-structure fact rather than an\nassumption about a third-party chunker's ordering (design 0.3-4)."
        },
        {
          "name": "classifyExpoPushTickets",
          "slug": "classify-expo-push-tickets",
          "kind": "function",
          "declaration": "/**\n * Maps one chunk's tickets back onto its entries.\n *\n * A response whose length differs from the request is never treated as a\n * handoff: which messages landed is unknowable, and losing a notification is\n * worse than sending it twice (design 3.1 F7). The tickets that did arrive are\n * still classified, so an endpoint the provider already confirmed as gone is\n * reported even in that case.\n */\ndeclare function classifyExpoPushTickets(entries: readonly ExpoPushEntry[], tickets: readonly ExpoPushTicket[]): ExpoTicketClassification;",
          "sourceDocumentation": "Maps one chunk's tickets back onto its entries.\n\nA response whose length differs from the request is never treated as a\nhandoff: which messages landed is unknowable, and losing a notification is\nworse than sending it twice (design 3.1 F7). The tickets that did arrive are\nstill classified, so an endpoint the provider already confirmed as gone is\nreported even in that case."
        },
        {
          "name": "createExpoPushGateway",
          "slug": "create-expo-push-gateway",
          "kind": "function",
          "declaration": "/**\n * Builds a {@link NotificationPushGateway} over an injected send callback.\n *\n * Locally malformed addresses come back as `rejectedEndpointIds`, never merged\n * into the provider-confirmed `invalidEndpointIds` (design 0.2-6). A transport\n * failure is absorbed into `accepted: false` rather than thrown, and a partial\n * chunk failure re-sends the whole delivery on the next pass — the concrete cost\n * of at-least-once (design 3.1 F4).\n */\ndeclare function createExpoPushGateway(options: ExpoPushGatewayOptions): NotificationPushGateway;",
          "sourceDocumentation": "Builds a {@link NotificationPushGateway} over an injected send callback.\n\nLocally malformed addresses come back as `rejectedEndpointIds`, never merged\ninto the provider-confirmed `invalidEndpointIds` (design 0.2-6). A transport\nfailure is absorbed into `accepted: false` rather than thrown, and a partial\nchunk failure re-sends the whole delivery on the next pass — the concrete cost\nof at-least-once (design 3.1 F4)."
        },
        {
          "name": "EXPO_DEVICE_NOT_REGISTERED",
          "slug": "expo-device-not-registered",
          "kind": "constant",
          "declaration": "EXPO_DEVICE_NOT_REGISTERED = \"DeviceNotRegistered\"",
          "sourceDocumentation": "The ticket error Expo returns for a token whose device unregistered."
        },
        {
          "name": "EXPO_PUSH_CHUNK_SIZE",
          "slug": "expo-push-chunk-size",
          "kind": "constant",
          "declaration": "EXPO_PUSH_CHUNK_SIZE = 100",
          "sourceDocumentation": "Expo accepts at most 100 messages per request."
        },
        {
          "name": "ExpoPushEntry",
          "slug": "expo-push-entry",
          "kind": "interface",
          "declaration": "/** One endpoint bound to the message built for it. The binding is the point. */\ninterface ExpoPushEntry {\n    readonly endpoint: NotificationPushEndpoint;\n    readonly message: ExpoPushMessage;\n}",
          "sourceDocumentation": "One endpoint bound to the message built for it. The binding is the point."
        },
        {
          "name": "ExpoPushGatewayOptions",
          "slug": "expo-push-gateway-options",
          "kind": "interface",
          "declaration": "interface ExpoPushGatewayOptions {\n    /**\n     * Sends one chunk. Declared with method syntax on purpose: method parameters\n     * are compared bivariantly, so an `expo-server-sdk` instance's\n     * `sendPushNotificationsAsync(messages: ExpoPushMessage[])` is assignable as is.\n     * As an arrow-function property it would not be - under `strictFunctionTypes`\n     * the parameter is contravariant and `readonly ExpoPushMessage[]` is not\n     * assignable to `ExpoPushMessage[]` (design 2.2). A 15-line `fetch` call fits\n     * the same shape; the library never imports either.\n     *\n     * **Assignability is not binding.** The gateway detaches this callback from the\n     * options object and calls it with no receiver, so a class method that reads\n     * `this` MUST be bound:\n     *\n     * ```ts\n     * send: expo.sendPushNotificationsAsync.bind(expo)\n     * // or: send: (messages) => expo.sendPushNotificationsAsync([...messages])\n     * ```\n     *\n     * `expo-server-sdk`'s method is exactly such a method (it dereferences\n     * `this.limitConcurrentRequests`), so `send: expo.sendPushNotificationsAsync`\n     * type-checks and then throws on the first call - which this gateway absorbs\n     * into `accepted: false`. Every push then fails silently, every delivery burns\n     * its attempts, and nothing in the log names the cause.\n     */\n    send(messages: readonly ExpoPushMessage[]): Promise<readonly ExpoPushTicket[]>;\n    /**\n     * Title used when a notification has none. Required and nullable rather than\n     * defaulted: the source hard-coded its product name here, and that is a value\n     * this library cannot hold.\n     */\n    readonly defaultTitle: string | null;\n    /** Defaults to `'default'`, matching the source. */\n    readonly sound?: 'default' | null | undefined;\n    readonly channelId?: string | undefined;\n    /** Continue remaining chunks after one fails. Default true (matches source). */\n    readonly continueAfterChunkFailure?: boolean | undefined;\n}"
        },
        {
          "name": "ExpoPushMessage",
          "slug": "expo-push-message",
          "kind": "interface",
          "declaration": "/**\n * Expo push의 wire shape 최소 부분집합 — SDK import 0 (설계 §3.5).\n *\n * 값어치 있는 부분(청킹·ticket 분류·undersized 가드)은 SDK **타입**이 아니라 wire shape에\n * 대한 순수 함수다. 그래서 이 파일은 `expo-server-sdk`를 참조하지 않고 형태만 적는다.\n */\n/** The subset of Expo's push message wire shape this library produces. */\ninterface ExpoPushMessage {\n    readonly to: string;\n    readonly title?: string | undefined;\n    readonly body: string;\n    readonly sound?: 'default' | null | undefined;\n    readonly priority?: 'default' | 'normal' | 'high' | undefined;\n    readonly channelId?: string | undefined;\n    readonly collapseId?: string | undefined;\n    readonly data?: Record<string, unknown> | undefined;\n}",
          "sourceDocumentation": "The subset of Expo's push message wire shape this library produces."
        },
        {
          "name": "ExpoPushTicket",
          "slug": "expo-push-ticket",
          "kind": "type",
          "declaration": "/** The subset of Expo's ticket wire shape this library reads. */\ntype ExpoPushTicket = {\n    readonly status: 'ok';\n    readonly id: string;\n} | {\n    readonly status: 'error';\n    readonly message?: string | undefined;\n    readonly details?: {\n        readonly error?: string | undefined;\n    } | undefined;\n};",
          "sourceDocumentation": "The subset of Expo's ticket wire shape this library reads."
        },
        {
          "name": "ExpoTicketClassification",
          "slug": "expo-ticket-classification",
          "kind": "interface",
          "declaration": "/**\n * ticket 분류 — undersized 응답 가드 포함(설계 §3.5).\n *\n * 순수 함수로 공개하는 이유는 두 가지다. ① 호스트가 receipt 폴링을 직접 붙일 수 있도록\n * 성공 ticket의 id를 돌려준다(§0.3-② — Expo의 `DeviceNotRegistered` 상당수는 ticket이\n * 아니라 receipt로 온다). ② 길이 불일치를 \"핸드오프 성공\"으로 취급하지 않는다는 판단이\n * 테스트 가능한 형태로 남는다.\n */\ninterface ExpoTicketClassification {\n    readonly accepted: boolean;\n    readonly invalidEndpointIds: readonly string[];\n    /** Ids of accepted tickets, for a host that polls Expo receipts (design 0.3-2). */\n    readonly ticketIds: readonly string[];\n    /** Error codes only. A ticket `message` can carry payload text and never appears here. */\n    readonly otherErrors: readonly string[];\n}",
          "sourceDocumentation": "ticket 분류 — undersized 응답 가드 포함(설계 §3.5).\n\n순수 함수로 공개하는 이유는 두 가지다. ① 호스트가 receipt 폴링을 직접 붙일 수 있도록\n성공 ticket의 id를 돌려준다(§0.3-② — Expo의 `DeviceNotRegistered` 상당수는 ticket이\n아니라 receipt로 온다). ② 길이 불일치를 \"핸드오프 성공\"으로 취급하지 않는다는 판단이\n테스트 가능한 형태로 남는다."
        },
        {
          "name": "isExpoPushToken",
          "slug": "is-expo-push-token",
          "kind": "function",
          "declaration": "/**\n * Expo 전송 게이트웨이 — SDK를 소유하지 않고 **전송 콜백을 주입받는다**(설계 §2.2-C).\n *\n * SDK에서 실제로 쓰던 것은 셋이었다: 토큰 형태 검사(정규식 한 줄), 청킹(100개 슬라이스),\n * 그리고 `POST https://exp.host/--/api/v2/push/send`. 앞의 둘은 순수 함수라 우리가 소유하는\n * 편이 낫고(청킹을 소유하면 ticket 대응이 자료구조가 된다), 남는 것은 HTTP 호출 하나이며\n * 호스트가 SDK를 쓰든 `fetch`를 쓰든 20줄이다.\n */\n/** `ExpoPushToken[…]` / `ExponentPushToken[…]` shape check. No network, no SDK. */\ndeclare function isExpoPushToken(address: string): boolean;",
          "sourceDocumentation": "`ExpoPushToken[…]` / `ExponentPushToken[…]` shape check. No network, no SDK."
        }
      ]
    },
    {
      "subpath": "./testing",
      "id": "testing",
      "declarationTarget": "./dist/testing.d.cts",
      "symbols": [
        {
          "name": "fakeNotificationRuntime",
          "slug": "fake-notification-runtime",
          "kind": "function",
          "declaration": "/** Deterministic runtime for tests. Claim tokens are a counter, not a UUID. */\ndeclare function fakeNotificationRuntime(options?: {\n    readonly now?: Date | undefined;\n}): FakeNotificationRuntime;",
          "sourceDocumentation": "Deterministic runtime for tests. Claim tokens are a counter, not a UUID."
        },
        {
          "name": "FakeNotificationRuntime",
          "slug": "fake-notification-runtime--interface",
          "kind": "interface",
          "declaration": "/**\n * 결정적 런타임 — 시계·claim 토큰·defer를 전부 테스트가 소유한다.\n *\n * 소스의 wakeup 스펙은 실제로 `realSetTimeout(5ms)`로 기다렸다. 여기서는 `flush()`가 그\n * 대기를 없앤다 — 그것이 `defer`를 포트로 만든 이유이기도 하다(설계 §0.2-⑫).\n */\ninterface FakeNotificationRuntime extends NotificationRuntime {\n    /** Moves the clock forward. Deferred work is not run by this call. */\n    advance(ms: number): void;\n    /** Runs every deferred callback synchronously, including ones they enqueue. */\n    flush(): void;\n}",
          "sourceDocumentation": "결정적 런타임 — 시계·claim 토큰·defer를 전부 테스트가 소유한다.\n\n소스의 wakeup 스펙은 실제로 `realSetTimeout(5ms)`로 기다렸다. 여기서는 `flush()`가 그\n대기를 없앤다 — 그것이 `defer`를 포트로 만든 이유이기도 하다(설계 §0.2-⑫)."
        },
        {
          "name": "LogEntry",
          "slug": "log-entry",
          "kind": "interface",
          "declaration": "/** 기록 로거 — 어떤 필드가 로그로 나가는지를 테스트가 단언할 수 있게 한다. */\ninterface LogEntry {\n    readonly level: 'info' | 'warn' | 'error';\n    readonly fields: Record<string, unknown>;\n    readonly message: string;\n}",
          "sourceDocumentation": "기록 로거 — 어떤 필드가 로그로 나가는지를 테스트가 단언할 수 있게 한다."
        },
        {
          "name": "MemoryNotificationSnapshot",
          "slug": "memory-notification-snapshot",
          "kind": "interface",
          "declaration": "/** A read-only view of everything the in-memory suite holds. */\ninterface MemoryNotificationSnapshot {\n    readonly outbox: readonly Readonly<OutboxRow>[];\n    readonly deliveries: readonly Readonly<DeliveryRow>[];\n    readonly items: readonly Readonly<ItemRow>[];\n    readonly messages: readonly Readonly<MessageRow>[];\n    readonly endpoints: readonly Readonly<EndpointRow>[];\n    readonly preferences: readonly {\n        readonly applicationKey: string;\n        readonly recipientRef: string;\n        readonly category: string;\n        readonly enabled: boolean;\n    }[];\n    /** Opaque recipient keys, as `notificationRecipientKey` computes them. */\n    readonly tombstones: readonly string[];\n}",
          "sourceDocumentation": "A read-only view of everything the in-memory suite holds."
        },
        {
          "name": "memoryNotificationStores",
          "slug": "memory-notification-stores",
          "kind": "function",
          "declaration": "/**\n * Never use in production: no durability, no cross-process atomicity, and a\n * snapshot API that would expose every recipient's content.\n *\n * It implements the same `stage`, `tombstoneRecipient` and `registerEndpoint`\n * seams a host wires over its own adapters, so our own unit suite and a host's\n * conformance run enter through one door (design 5.4). Row locking is modelled\n * with a promise chain per outbox row, which is what makes the L1/L2 interleaving\n * cases mean anything here.\n */\ndeclare function memoryNotificationStores(runtime?: NotificationRuntime): MemoryNotificationStores;",
          "sourceDocumentation": "Never use in production: no durability, no cross-process atomicity, and a\nsnapshot API that would expose every recipient's content.\n\nIt implements the same `stage`, `tombstoneRecipient` and `registerEndpoint`\nseams a host wires over its own adapters, so our own unit suite and a host's\nconformance run enter through one door (design 5.4). Row locking is modelled\nwith a promise chain per outbox row, which is what makes the L1/L2 interleaving\ncases mean anything here."
        },
        {
          "name": "MemoryNotificationStores",
          "slug": "memory-notification-stores--interface",
          "kind": "interface",
          "declaration": "interface MemoryNotificationStores extends NotificationStoreSuite {\n    snapshot(): MemoryNotificationSnapshot;\n}"
        },
        {
          "name": "NotificationObligation",
          "slug": "notification-obligation",
          "kind": "type",
          "declaration": "type NotificationObligation = 'R1' | 'R2' | 'R3' | 'R4' | 'R5' | 'R6' | 'R7' | 'R8' | 'R9' | 'R10' | 'R11' | 'R12' | 'R13' | 'D1' | 'D2' | 'D3' | 'D4' | 'D5' | 'D6' | 'D7' | 'D8' | 'D9' | 'I1' | 'I2' | 'I3' | 'L1' | 'L2' | 'L3' | 'L4';"
        },
        {
          "name": "notificationStoreContractCases",
          "slug": "notification-store-contract-cases",
          "kind": "function",
          "declaration": "/**\n * The executable form of the R1-R13, D1-D9, I1-I3 and L1-L4 obligations.\n *\n * Each case receives a **fresh** suite from the factory. Cases that probe\n * concurrency issue `concurrency` simultaneous calls, so a host must point the\n * suite at a connection pool of at least two: a single-connection client\n * serialises the burst and hides a non-atomic claim.\n */\ndeclare function notificationStoreContractCases(options?: NotificationStoreContractOptions): readonly StoreContractCase[];",
          "sourceDocumentation": "The executable form of the R1-R13, D1-D9, I1-I3 and L1-L4 obligations.\n\nEach case receives a **fresh** suite from the factory. Cases that probe\nconcurrency issue `concurrency` simultaneous calls, so a host must point the\nsuite at a connection pool of at least two: a single-connection client\nserialises the burst and hides a non-atomic claim."
        },
        {
          "name": "NotificationStoreContractOptions",
          "slug": "notification-store-contract-options",
          "kind": "interface",
          "declaration": "interface NotificationStoreContractOptions {\n    /** Skip obligations an implementation legitimately cannot support, with a reason. */\n    readonly skip?: readonly NotificationObligation[] | undefined;\n    /** Concurrent calls the R1/R11 burst cases issue. Default 8; needs a pool of >= 2. */\n    readonly concurrency?: number | undefined;\n}"
        },
        {
          "name": "NotificationStoreSuite",
          "slug": "notification-store-suite",
          "kind": "interface",
          "declaration": "/**\n * What the contract cases drive. A host implements it by wiring its own three\n * stores plus thin adapters over its publisher and account lifecycle, so the\n * obligations that live outside `NotificationRelayStore` — I1-I3 (ingress) and\n * L1-L4 (lifecycle) — are checkable against a real implementation rather than\n * only against ours.\n */\ninterface NotificationStoreSuite {\n    readonly relayStore: NotificationRelayStore;\n    readonly deliveryStore: NotificationDeliveryStore;\n    readonly endpointStore: NotificationEndpointStore;\n    /** Runs the host's publisher inside its own transaction. Checks I1-I3, and so G1. */\n    stage(command: NotificationCommand): Promise<NotificationStageResult>;\n    /** Runs the host's account lifecycle for one recipient. Checks L1-L4, and so G7. */\n    tombstoneRecipient(input: {\n        readonly applicationKey: string;\n        readonly recipientRef: string;\n    }): Promise<void>;\n    /** Registers or refreshes an endpoint and returns what `listEnabled` would observe. */\n    registerEndpoint(input: {\n        readonly applicationKey: string;\n        readonly recipientRef: string;\n        readonly provider: string;\n        readonly address: string;\n    }): Promise<ObservedNotificationEndpoint>;\n    setCategoryEnabled(input: {\n        readonly applicationKey: string;\n        readonly recipientRef: string;\n        readonly category: string;\n        readonly enabled: boolean;\n    }): Promise<void>;\n}",
          "sourceDocumentation": "What the contract cases drive. A host implements it by wiring its own three\nstores plus thin adapters over its publisher and account lifecycle, so the\nobligations that live outside `NotificationRelayStore` — I1-I3 (ingress) and\nL1-L4 (lifecycle) — are checkable against a real implementation rather than\nonly against ours."
        },
        {
          "name": "passthroughPresenter",
          "slug": "passthrough-presenter",
          "kind": "function",
          "declaration": "/** 테스트 전용 presenter — 라이브러리가 카피를 배포하지 않는다는 규칙의 예외가 아니다. */\n/**\n * Batch-unaware presenter for tests only: it passes the seed command's content\n * through unchanged, which is wrong copy for any merged batch. Production hosts\n * write their own — that is why the library ships no default (design 0.2-2).\n */\ndeclare function passthroughPresenter(): NotificationPresenter;",
          "sourceDocumentation": "Batch-unaware presenter for tests only: it passes the seed command's content\nthrough unchanged, which is wrong copy for any merged batch. Production hosts\nwrite their own — that is why the library ships no default (design 0.2-2)."
        },
        {
          "name": "recordingNotificationLogger",
          "slug": "recording-notification-logger",
          "kind": "function",
          "declaration": "/**\n * Captures every call. Tests assert on it to prove a secret never reaches the\n * log: the pipeline logs a redacted `safeErrorCode`, never an exception message.\n */\ndeclare function recordingNotificationLogger(): RecordingNotificationLogger;",
          "sourceDocumentation": "Captures every call. Tests assert on it to prove a secret never reaches the\nlog: the pipeline logs a redacted `safeErrorCode`, never an exception message."
        },
        {
          "name": "RecordingNotificationLogger",
          "slug": "recording-notification-logger--interface",
          "kind": "interface",
          "declaration": "interface RecordingNotificationLogger extends NotificationLogger {\n    readonly entries: readonly LogEntry[];\n}"
        },
        {
          "name": "StoreContractCase",
          "slug": "store-contract-case",
          "kind": "interface",
          "declaration": "interface StoreContractCase {\n    /** e.g. `'R11: a losing createDelivery reports created:false, never throws'`. */\n    readonly name: string;\n    readonly obligation: NotificationObligation;\n    /**\n     * The factory returns the suite under test. Typed as `NotificationStoreSuite`\n     * and not as `MemoryNotificationStores`, because the point of these cases is a\n     * host's own implementation: narrowing the parameter to the in-memory type\n     * would make the whole array a self-test toy.\n     */\n    run(factory: () => NotificationStoreSuite | Promise<NotificationStoreSuite>): Promise<void>;\n}"
        }
      ]
    }
  ]
}
