@objectstack/service-messaging 11.1.0 → 11.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/messaging-service-plugin.ts","../src/recipient-resolver.ts","../src/preference-resolver.ts","../src/inbox-channel.ts","../src/messaging-service.ts","../src/sql-outbox.ts","../src/backoff.ts","../src/audit-timestamp.ts","../src/sql-http-outbox.ts","../src/http-outbox.ts","../src/objects/http-delivery.object.ts","../src/digest-render.ts","../src/dispatcher.ts","../src/http-sender.ts","../src/http-dispatcher.ts","../src/retention.ts","../src/template-renderer.ts","../src/email-channel.ts","../src/objects/inbox-message.object.ts","../src/objects/notification-receipt.object.ts","../src/objects/notification-delivery.object.ts","../src/objects/notification-preference.object.ts","../src/objects/notification-subscription.object.ts","../src/objects/notification-template.object.ts","../src/memory-outbox.ts","../src/memory-http-outbox.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { randomUUID } from 'node:crypto';\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport { MessagingService } from './messaging-service.js';\nimport { createInboxChannel } from './inbox-channel.js';\nimport { SqlNotificationOutbox } from './sql-outbox.js';\nimport { SqlHttpOutbox } from './sql-http-outbox.js';\nimport { NotificationDispatcher, type DispatchCluster } from './dispatcher.js';\nimport { HttpDispatcher } from './http-dispatcher.js';\nimport { NotificationRetention, DEFAULT_NOTIFICATION_RETENTION_DAYS } from './retention.js';\nimport { createEmailChannel } from './email-channel.js';\nimport { NotificationTemplateStore } from './template-renderer.js';\nimport {\n InboxMessage,\n NotificationReceipt,\n NotificationDelivery,\n NotificationPreference,\n NotificationSubscription,\n NotificationTemplate,\n HttpDelivery,\n} from './objects/index.js';\n\nexport interface MessagingServicePluginOptions {\n /**\n * Register the always-on `inbox` channel during init (default `true`).\n * Set `false` only for tests that want an empty registry.\n */\n registerInbox?: boolean;\n /**\n * Run the durable delivery outbox + dispatcher (ADR-0030 P1) when a data\n * engine is available (default `true`). When off (or no engine), `emit()`\n * fans out inline best-effort (P0 behavior).\n */\n reliableDelivery?: boolean;\n /** Outbox/dispatcher partition count (default 8). */\n partitionCount?: number;\n /** Dispatcher tick interval in ms (default 500). */\n dispatchIntervalMs?: number;\n /**\n * Topics that bypass the per-user preference matrix (ADR-0030 P2) — e.g.\n * security/system alerts users must not be able to mute. Exact match, or a\n * `prefix.` entry for a prefix match (default none).\n */\n mandatoryTopics?: readonly string[];\n /**\n * Retention window in days for the notification pipeline (ADR-0030\n * hardening). When > 0, a periodic sweep prunes events, deliveries, inbox\n * materializations and receipts older than this — bounding the event-log\n * growth from high-frequency periodic flows.\n *\n * **Default-on** at {@link DEFAULT_NOTIFICATION_RETENTION_DAYS} as of GA\n * (launch-readiness.md P1-2): an unbounded notification log is a slow leak,\n * so retention ships enabled rather than opt-in. Set to `0` to disable\n * retention entirely (notification history kept forever; operator owns\n * cleanup). Raise/lower the number to widen/narrow the window.\n */\n retentionDays?: number;\n /** Retention sweep interval in ms (default 1 hour). Only used when `retentionDays` is set. */\n retentionSweepMs?: number;\n}\n\n/**\n * MessagingServicePlugin — registers the `messaging` service (ADR-0012 /\n * ADR-0030).\n *\n * After bootstrap, `kernel.getService('messaging')` is a {@link MessagingService}\n * with the always-on `inbox` channel registered. The baseline `notify` flow\n * node dispatches through it; flows therefore stop being no-ops once this\n * plugin is installed. Other channels (email/webhook/push/IM) register\n * themselves on this same service.\n *\n * At `kernel:ready` (engine available) the plugin wires the reliable-delivery\n * path: a `SqlNotificationOutbox` over `sys_notification_delivery` plus a\n * `NotificationDispatcher` that drains it with retry/backoff/dead-letter.\n * `emit()` then enqueues durable deliveries instead of fanning out inline.\n *\n * @example\n * ```ts\n * const kernel = new ObjectKernel();\n * kernel.use(new AutomationServicePlugin()); // ships the `notify` node\n * kernel.use(new MessagingServicePlugin()); // backs it with delivery\n * await kernel.bootstrap();\n * ```\n */\nexport class MessagingServicePlugin implements Plugin {\n name = 'com.objectstack.service.messaging';\n version = '1.0.0';\n type = 'standard' as const;\n dependencies = ['com.objectstack.engine.objectql'];\n\n private readonly options: Required<MessagingServicePluginOptions>;\n private dispatcher?: NotificationDispatcher;\n private httpDispatcher?: HttpDispatcher;\n private retentionTimer?: ReturnType<typeof setInterval>;\n\n constructor(options: MessagingServicePluginOptions = {}) {\n this.options = {\n registerInbox: true,\n reliableDelivery: true,\n partitionCount: 8,\n dispatchIntervalMs: 500,\n mandatoryTopics: [],\n retentionDays: DEFAULT_NOTIFICATION_RETENTION_DAYS,\n retentionSweepMs: 3_600_000,\n ...options,\n };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n // Shared lazy data-engine resolver — used to persist the L2\n // `sys_notification` event in `emit()`, by the inbox channel to\n // materialize rows, and (at kernel:ready) to back the outbox. Resolved\n // lazily so it works regardless of plugin init order.\n const getData = (): IDataEngine | undefined => {\n try {\n return ctx.getService<IDataEngine>('data') ?? ctx.getService<IDataEngine>('objectql');\n } catch {\n return undefined;\n }\n };\n\n const service = new MessagingService({\n logger: ctx.logger,\n getData,\n mandatoryTopics: this.options.mandatoryTopics,\n });\n\n if (this.options.registerInbox) {\n service.registerChannel(createInboxChannel({ getData }));\n }\n\n ctx.registerService('messaging', service);\n\n // ADR-0030: the messaging service also backs the `notification` core\n // service slot — it owns the in-app inbox + receipts, so it answers the\n // `/api/v1/notifications` REST surface (list / mark-read / mark-all-read)\n // via its inbox read API. Registering it here makes the dispatcher\n // resolve + advertise those routes (`hasNotification`). The legacy\n // INotificationService `send()` abstraction is unused; nothing consumes\n // the slot expecting it.\n ctx.registerService('notification', service);\n\n // Register the messaging objects so their rows can be written. The\n // preference/subscription objects (ADR-0030 P2) are Studio-configurable,\n // so contribute them to the Setup app's Configuration slot (ADR-0029 D7)\n // — they appear in nav only when this plugin is installed.\n ctx.getService<{ register(m: unknown): void }>('manifest').register({\n id: 'com.objectstack.service.messaging',\n name: 'Messaging Service',\n version: '1.0.0',\n type: 'plugin',\n scope: 'system',\n objects: [\n InboxMessage,\n NotificationReceipt,\n NotificationDelivery,\n NotificationPreference,\n NotificationSubscription,\n NotificationTemplate,\n HttpDelivery,\n ],\n navigationContributions: [\n {\n app: 'setup',\n group: 'group_configuration',\n priority: 120,\n items: [\n { id: 'nav_notification_preferences', type: 'object', label: 'Notification Preferences', objectName: 'sys_notification_preference', icon: 'bell-ring', requiresObject: 'sys_notification_preference' },\n { id: 'nav_notification_subscriptions', type: 'object', label: 'Notification Subscriptions', objectName: 'sys_notification_subscription', icon: 'rss', requiresObject: 'sys_notification_subscription' },\n { id: 'nav_notification_templates', type: 'object', label: 'Notification Templates', objectName: 'sys_notification_template', icon: 'file-text', requiresObject: 'sys_notification_template' },\n ],\n },\n ],\n });\n\n // Provision the physical tables for this service's system objects\n // up-front, once the engine is ready. The inbox channel materializes\n // sys_inbox_message + sys_notification_receipt rows on first delivery,\n // so the tables are otherwise lazy-created on first WRITE — a freshly\n // provisioned env that READS the inbox / notifications before any\n // message has been delivered hits \"no such table\", logged as a\n // `Find operation failed` ERROR on every page load. Runs independently\n // of `reliableDelivery` (the inbox tables are needed either way) and is\n // idempotent. See {@link provisionSystemTables}.\n if (typeof ctx.hook === 'function') {\n ctx.hook('kernel:ready', async () => {\n const engine = getData();\n if (engine) await this.provisionSystemTables(engine, ctx);\n });\n }\n\n // Email channel (ADR-0030 P3): register when an `email` service is\n // present. Resolved at kernel:ready so init order with the email plugin\n // doesn't matter; absent email ⇒ no channel (a notify(channels:['email'])\n // then reports \"not registered\" rather than silently no-opping). The\n // dispatcher looks channels up dynamically, so registering after it is fine.\n if (typeof ctx.hook === 'function') {\n const templateStore = new NotificationTemplateStore({ getData });\n const getEmail = () => {\n try {\n return ctx.getService<import('./email-channel.js').EmailSenderSurface>('email');\n } catch {\n return undefined;\n }\n };\n ctx.hook('kernel:ready', async () => {\n if (getEmail()) {\n service.registerChannel(createEmailChannel({ getEmail, getData, store: templateStore }));\n ctx.logger.info('[messaging] email channel registered (renders sys_notification_template)');\n }\n });\n }\n\n // Reliable delivery (P1): wire the outbox + dispatcher once the engine\n // is resolvable. Until then `emit()` runs inline best-effort.\n if (this.options.reliableDelivery && typeof ctx.hook === 'function') {\n ctx.hook('kernel:ready', async () => {\n const engine = getData();\n if (!engine) {\n ctx.logger.warn('[messaging] no data engine at kernel:ready — reliable delivery disabled (inline fan-out)');\n return;\n }\n const outbox = new SqlNotificationOutbox(engine, { partitionCount: this.options.partitionCount });\n service.setOutbox(outbox);\n\n let cluster: DispatchCluster | undefined;\n try {\n cluster = ctx.getService<DispatchCluster>('cluster');\n } catch {\n cluster = undefined; // single-node fallback in the dispatcher\n }\n\n this.dispatcher = new NotificationDispatcher({\n nodeId: `notify-${process.pid}-${randomUUID().slice(0, 8)}`,\n outbox,\n channels: service,\n channelContext: { logger: ctx.logger },\n cluster,\n partitionCount: this.options.partitionCount,\n intervalMs: this.options.dispatchIntervalMs,\n logger: ctx.logger,\n });\n this.dispatcher.start();\n ctx.logger.info(\n `[messaging] reliable delivery on (outbox + dispatcher, ${this.options.partitionCount} partitions${cluster ? ', clustered' : ', single-node'})`,\n );\n\n // ADR-0018 M3: generic outbound-HTTP outbox + dispatcher. Backs\n // the Flow `http` node (and, going forward, webhook fan-out) with\n // the same retry / dead-letter substrate as notifications.\n const httpOutbox = new SqlHttpOutbox(engine, { partitionCount: this.options.partitionCount });\n service.setHttpOutbox(httpOutbox);\n this.httpDispatcher = new HttpDispatcher({\n nodeId: `http-${process.pid}-${randomUUID().slice(0, 8)}`,\n outbox: httpOutbox,\n cluster,\n partitionCount: this.options.partitionCount,\n intervalMs: this.options.dispatchIntervalMs,\n logger: ctx.logger,\n });\n this.httpDispatcher.start();\n ctx.logger.info(\n `[messaging] HTTP delivery on (sys_http_delivery outbox + dispatcher, ${this.options.partitionCount} partitions)`,\n );\n });\n }\n\n // Retention sweep (ADR-0030 hardening): opt-in pruning of the\n // notification pipeline so the event log can't grow unbounded. Runs once\n // at ready then on a low-frequency interval; the timer is unref'd so it\n // never keeps the process alive.\n if (this.options.retentionDays > 0 && typeof ctx.hook === 'function') {\n ctx.hook('kernel:ready', async () => {\n const retention = new NotificationRetention({ getData, logger: ctx.logger });\n const days = this.options.retentionDays;\n const sweep = () => {\n void retention.prune(days).catch((err) =>\n ctx.logger.warn(`[messaging] retention sweep failed: ${(err as Error)?.message ?? err}`),\n );\n };\n sweep();\n this.retentionTimer = setInterval(sweep, this.options.retentionSweepMs);\n this.retentionTimer.unref?.();\n ctx.logger.info(\n `[messaging] retention on (prune > ${days}d every ${Math.round(this.options.retentionSweepMs / 1000)}s)`,\n );\n });\n }\n\n ctx.logger.info(\n `[messaging] service registered with channels: ${service.getRegisteredChannels().join(', ') || '(none)'}`,\n );\n }\n\n /**\n * Provision the physical tables for this service's system objects up-front.\n *\n * These objects are lazy-created on first WRITE (the SQL driver issues DDL\n * when the first row is inserted), so an env that READS them first — the\n * Console bell / inbox queries sys_inbox_message + sys_notification_receipt\n * before any notification has been delivered — hits \"no such table\", which\n * the engine logs as a `Find operation failed` ERROR on every page load.\n * Creating the tables at kernel:ready makes a new env consistent from the\n * start. Idempotent (the driver only creates a table when absent), so it is\n * safe on every boot; per-object failures are isolated.\n */\n private async provisionSystemTables(engine: IDataEngine, ctx: PluginContext): Promise<void> {\n // `syncObjectSchema` lives on the concrete ObjectQL engine, not the\n // IDataEngine contract; engines without on-demand DDL skip provisioning.\n const sync = (engine as unknown as { syncObjectSchema?: (name: string) => Promise<void> }).syncObjectSchema;\n if (typeof sync !== 'function') return;\n const objects = [\n InboxMessage,\n NotificationReceipt,\n NotificationDelivery,\n NotificationPreference,\n NotificationSubscription,\n NotificationTemplate,\n HttpDelivery,\n ];\n for (const obj of objects) {\n try {\n await sync.call(engine, (obj as { name: string }).name);\n } catch (err) {\n ctx.logger.warn(`[messaging] could not provision ${(obj as { name: string }).name} storage — ${(err as Error)?.message ?? err}`);\n }\n }\n }\n\n /** Stop the dispatcher loop + retention sweep on shutdown. */\n async stop(): Promise<void> {\n await this.dispatcher?.stop();\n this.dispatcher = undefined;\n await this.httpDispatcher?.stop();\n this.httpDispatcher = undefined;\n if (this.retentionTimer) {\n clearInterval(this.retentionTimer);\n this.retentionTimer = undefined;\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport type { Audience, AudienceSpec } from './messaging-service.js';\n\n/**\n * Cheap \"looks like an email\" heuristic so we attempt id resolution. Hand-rolled\n * (no regex) to stay strictly linear — the obvious `^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$`\n * has overlapping quantifiers (the `.` is also in `[^\\s@]`) and backtracks\n * polynomially on adversarial input (ReDoS). This is O(n).\n */\nfunction looksLikeEmail(s: string): boolean {\n if (!s || /\\s/.test(s)) return false; // single char-class test — linear\n const at = s.indexOf('@');\n if (at <= 0 || at !== s.lastIndexOf('@') || at === s.length - 1) return false;\n const domain = s.slice(at + 1);\n const dot = domain.indexOf('.');\n // a dot in the domain, not first or last char\n return dot > 0 && dot < domain.length - 1;\n}\n\n/** The user identity object an email-shaped recipient is resolved against. */\nexport const USER_OBJECT = 'sys_user';\n/** Tenant-membership object backing `role:` expansion (`sys_member.role`). */\nexport const MEMBER_OBJECT = 'sys_member';\n/** Team-membership object backing `team:` expansion (`sys_team_member.team_id`). */\nexport const TEAM_MEMBER_OBJECT = 'sys_team_member';\n\n/**\n * Conventional owner/assignee field names tried, in order, for `owner_of:`\n * audience resolution. Mirrors the audit writer's `OWNER_FIELDS`.\n */\nconst DEFAULT_OWNER_FIELDS = ['owner_id', 'assigned_to', 'assignee_id', 'owner', 'assignee'];\n\nexport interface RecipientResolverLogger {\n warn(...args: unknown[]): void;\n info?(...args: unknown[]): void;\n}\n\nexport interface RecipientResolverOptions {\n /** Resolve the runtime data engine. `undefined` on a minimal/test stack. */\n getData(): IDataEngine | undefined;\n logger: RecipientResolverLogger;\n /** Identity object for email→id resolution (default {@link USER_OBJECT}). */\n userObject?: string;\n /** Membership object for `role:` (default {@link MEMBER_OBJECT}). */\n memberObject?: string;\n /** Membership object for `team:` (default {@link TEAM_MEMBER_OBJECT}). */\n teamMemberObject?: string;\n /** Owner field candidates for `owner_of:` (default {@link DEFAULT_OWNER_FIELDS}). */\n ownerFields?: string[];\n}\n\nexport interface ResolveContext {\n /** Tenant scope applied to `role:` expansion. */\n organizationId?: string;\n}\n\n/**\n * RecipientResolver (ADR-0030 P1) — expands an {@link Audience} into a flat,\n * de-duplicated list of recipient **user ids**.\n *\n * Reuses the platform's existing identity/membership object model (the same\n * `sys_member.role` / `sys_team_member.team_id` graph `plugin-sharing`'s\n * `TeamGraphService` reads) by querying it directly through the data engine —\n * so `service-messaging` gains no backward dependency on a plugin. This is the\n * single home for recipient resolution: the inbox channel no longer does its\n * own email→id fallback (that moved here per ADR-0030 P1).\n *\n * Supported audience specs:\n * - `'<userId>'` → the id verbatim\n * - `'user:<id>'` → the id (prefix stripped)\n * - `'<email>'` → `sys_user` lookup by email → id (verbatim on miss)\n * - `'role:<name>'` → `sys_member` where `role = name` (tenant-scoped)\n * - `'team:<id>'` → `sys_team_member` where `team_id = id`\n * - `'owner_of:<obj>:<id>'` → the owner field of that record\n * - `{ ownerOf: { object, id } }` → same, structured form\n *\n * Every lookup is best-effort: a failed query resolves to no recipients for\n * that spec (logged) rather than throwing — emit() must never fail because a\n * directory lookup hiccupped.\n */\nexport class RecipientResolver {\n private readonly userObject: string;\n private readonly memberObject: string;\n private readonly teamMemberObject: string;\n private readonly ownerFields: string[];\n\n constructor(private readonly opts: RecipientResolverOptions) {\n this.userObject = opts.userObject ?? USER_OBJECT;\n this.memberObject = opts.memberObject ?? MEMBER_OBJECT;\n this.teamMemberObject = opts.teamMemberObject ?? TEAM_MEMBER_OBJECT;\n this.ownerFields = opts.ownerFields ?? DEFAULT_OWNER_FIELDS;\n }\n\n /** Expand an audience to a de-duplicated list of recipient user ids. */\n async resolve(audience: Audience, ctx: ResolveContext = {}): Promise<string[]> {\n const specs = Array.isArray(audience) ? audience : [audience as AudienceSpec];\n const data = this.opts.getData();\n const out: string[] = [];\n\n for (const spec of specs) {\n for (const id of await this.resolveOne(spec, data, ctx)) {\n if (id) out.push(id);\n }\n }\n // De-dup while preserving order.\n return [...new Set(out)];\n }\n\n private async resolveOne(\n spec: AudienceSpec,\n data: IDataEngine | undefined,\n ctx: ResolveContext,\n ): Promise<string[]> {\n if (typeof spec !== 'string') {\n // Structured `{ ownerOf: { object, id } }`.\n if (spec && typeof spec === 'object' && 'ownerOf' in spec) {\n return this.resolveOwnerOf(spec.ownerOf.object, spec.ownerOf.id, data);\n }\n this.opts.logger.warn(`[recipients] unrecognized audience spec ${JSON.stringify(spec)}; skipped`);\n return [];\n }\n\n const value = spec.trim();\n if (!value) return [];\n\n if (value.startsWith('user:')) return [value.slice(5)].filter(Boolean);\n if (value.startsWith('role:')) return this.resolveRole(value.slice(5), data, ctx);\n if (value.startsWith('team:')) return this.resolveTeam(value.slice(5), data);\n if (value.startsWith('owner_of:')) {\n // `owner_of:<object>:<id>` — id may itself contain ':' (rare), so\n // split only on the first two segments.\n const rest = value.slice('owner_of:'.length);\n const sep = rest.indexOf(':');\n if (sep > 0) return this.resolveOwnerOf(rest.slice(0, sep), rest.slice(sep + 1), data);\n this.opts.logger.warn(`[recipients] malformed owner_of spec '${value}'; skipped`);\n return [];\n }\n if (looksLikeEmail(value)) return [await this.resolveEmail(value, data)];\n // Bare user id.\n return [value];\n }\n\n /** `role:` → `sys_member` rows with that role in the tenant. */\n private async resolveRole(role: string, data: IDataEngine | undefined, ctx: ResolveContext): Promise<string[]> {\n if (!role || !data) return [];\n const where: Record<string, unknown> = { role };\n if (ctx.organizationId) where.organization_id = ctx.organizationId;\n try {\n const rows = await data.find(this.memberObject, { where, fields: ['user_id'], limit: 10000 });\n return userIds(rows);\n } catch (err) {\n this.opts.logger.warn(`[recipients] role '${role}' lookup failed (${msg(err)}); 0 recipients`);\n return [];\n }\n }\n\n /** `team:` → `sys_team_member` rows for that team. */\n private async resolveTeam(teamId: string, data: IDataEngine | undefined): Promise<string[]> {\n if (!teamId || !data) return [];\n try {\n const rows = await data.find(this.teamMemberObject, {\n where: { team_id: teamId },\n fields: ['user_id'],\n limit: 10000,\n });\n return userIds(rows);\n } catch (err) {\n this.opts.logger.warn(`[recipients] team '${teamId}' lookup failed (${msg(err)}); 0 recipients`);\n return [];\n }\n }\n\n /** `owner_of:` → the owner/assignee field of the referenced record. */\n private async resolveOwnerOf(object: string, id: string, data: IDataEngine | undefined): Promise<string[]> {\n if (!object || !id || !data) return [];\n try {\n const rec = await data.findOne(object, { where: { id }, fields: ['id', ...this.ownerFields] });\n if (!rec) return [];\n for (const f of this.ownerFields) {\n const v = rec[f];\n if (typeof v === 'string' && v.length > 0) return [v];\n }\n return [];\n } catch (err) {\n this.opts.logger.warn(`[recipients] owner_of '${object}:${id}' lookup failed (${msg(err)}); 0 recipients`);\n return [];\n }\n }\n\n /**\n * Resolve an email-shaped recipient to its user id. Falls back to the email\n * verbatim on no match or lookup error (a downstream channel may still key\n * a row by it — never lose the recipient on a directory miss).\n */\n private async resolveEmail(email: string, data: IDataEngine | undefined): Promise<string> {\n if (!data) return email;\n try {\n const user = await data.findOne(this.userObject, { where: { email }, fields: ['id'] });\n const id = user?.id;\n if (id != null && String(id).length > 0) return String(id);\n this.opts.logger.warn(`[recipients] no '${this.userObject}' matched email '${email}'; keeping verbatim`);\n return email;\n } catch (err) {\n this.opts.logger.warn(`[recipients] email '${email}' lookup failed (${msg(err)}); keeping verbatim`);\n return email;\n }\n }\n}\n\n/** Pull distinct non-empty `user_id`s from a row set. */\nfunction userIds(rows: unknown): string[] {\n if (!Array.isArray(rows)) return [];\n return [...new Set(rows.map((r: any) => String(r?.user_id ?? '')).filter(Boolean))];\n}\n\nfunction msg(err: unknown): string {\n return (err as Error)?.message ?? String(err);\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\n\n/** The object the preference matrix lives in. */\nexport const PREFERENCE_OBJECT = 'sys_notification_preference';\n\nexport interface PreferenceResolverLogger {\n info(...args: unknown[]): void;\n warn(...args: unknown[]): void;\n error(...args: unknown[]): void;\n}\n\nexport interface PreferenceResolverOptions {\n /** Lazily resolve the data engine; `undefined` ⇒ fail-open (deliver all). */\n getData(): IDataEngine | undefined;\n logger: PreferenceResolverLogger;\n /**\n * Topics that bypass preferences entirely (security/system alerts users\n * must not be able to mute). An entry ending in `.` is a prefix match\n * (`security.` matches `security.breach`); otherwise it is an exact match.\n */\n mandatoryTopics?: readonly string[];\n /** Object name override (default {@link PREFERENCE_OBJECT}). */\n objectName?: string;\n}\n\nexport interface PreferenceContext {\n topic: string;\n organizationId?: string;\n /** Event severity — `critical` bypasses quiet-hours deferral (P3b). */\n severity?: string;\n /** \"Now\" reference (epoch ms) for quiet-hours math. Defaults to Date.now(). */\n now?: number;\n}\n\n/** A recipient with the channels they accept for this notification. */\nexport interface PreferenceTarget {\n recipient: string;\n channels: string[];\n /**\n * Earliest dispatch time (epoch ms) when the recipient is deferred — by\n * quiet-hours *or* a digest window (P3b). Absent ⇒ send now. Applies to all\n * of this recipient's channels. Honored only on the durable outbox path;\n * inline best-effort fan-out ignores it.\n */\n notBefore?: number;\n /**\n * P3b-2 digest: present when the recipient batches this topic. `window` is a\n * stable label for the accumulation window (e.g. the local date for `daily`,\n * the week-start date for `weekly`); the dispatcher collapses all\n * `${recipient}|${channel}|${window}` deliveries into one message. Pairs with\n * `notBefore` = the window's dispatch time.\n */\n digest?: { window: string };\n}\n\n/** Quiet-hours window declared on a preference row. */\nexport interface QuietHours {\n tz?: string;\n start?: string; // 'HH:MM'\n end?: string; // 'HH:MM'\n}\n\n/** Digest cadence declared on a preference row (P3b-2). `none` ⇒ no batching. */\nexport type DigestCadence = 'daily' | 'weekly';\n\nconst WILDCARD = '*';\n\n/**\n * PreferenceResolver — the ADR-0030 Layer-3 preference filter (P2).\n *\n * Given the resolved recipients and the requested channels, returns, per\n * recipient, the channels they actually accept for `topic`. Resolution is\n * most-specific-wins over `sys_notification_preference` rows with `*` wildcards\n * for user / topic / channel; a real-user row overrides the `user_id='*'`\n * admin-global default; the built-in default is **on**.\n *\n * Two safety rules:\n * - **Mandatory topics bypass** the matrix (all channels kept).\n * - **Fail-open**: no data engine, or a lookup error, keeps all channels — a\n * preference outage must never silently swallow notifications.\n */\nexport class PreferenceResolver {\n private readonly objectName: string;\n private readonly mandatory: readonly string[];\n\n constructor(private readonly opts: PreferenceResolverOptions) {\n this.objectName = opts.objectName ?? PREFERENCE_OBJECT;\n this.mandatory = opts.mandatoryTopics ?? [];\n }\n\n /** Whether a topic bypasses preferences (exact or `prefix.` match). */\n isMandatory(topic: string): boolean {\n return this.mandatory.some((m) =>\n m.endsWith('.') ? topic.startsWith(m) : topic === m,\n );\n }\n\n /**\n * Filter `(recipient × channel)` by preference. Recipients left with no\n * accepted channel are dropped from the result.\n */\n async filter(\n recipients: string[],\n channels: string[],\n ctx: PreferenceContext,\n ): Promise<PreferenceTarget[]> {\n const all = (): PreferenceTarget[] => recipients.map((r) => ({ recipient: r, channels: [...channels] }));\n if (recipients.length === 0 || channels.length === 0) return [];\n if (this.isMandatory(ctx.topic)) return all();\n\n const data = this.opts.getData();\n if (!data) return all(); // fail-open\n\n let rows: Record<string, unknown>[];\n try {\n rows = await this.loadRows(data, ctx);\n } catch (err) {\n this.opts.logger.warn(\n `[preferences] lookup for topic '${ctx.topic}' failed (${msg(err)}); delivering all (fail-open)`,\n );\n return all();\n }\n\n // Index rows by `${user}|${topic}|${channel}` → { enabled, quietHours }.\n const recipientSet = new Set(recipients);\n const index = new Map<string, PrefRowLite>();\n for (const r of rows) {\n const user = String(r.user_id ?? '');\n if (user !== WILDCARD && !recipientSet.has(user)) continue; // ignore unrelated users\n const topic = String(r.topic ?? WILDCARD);\n const channel = String(r.channel ?? WILDCARD);\n index.set(`${user}|${topic}|${channel}`, {\n enabled: asBool(r.enabled),\n quietHours: parseQuietHours(r.quiet_hours),\n digest: parseDigest(r.digest),\n });\n }\n\n const nowMs = ctx.now ?? Date.now();\n const critical = ctx.severity === 'critical';\n const targets: PreferenceTarget[] = [];\n for (const recipient of recipients) {\n const accepted = channels.filter(\n (channel) => this.resolveRow(index, recipient, ctx.topic, channel)?.enabled ?? true,\n );\n if (accepted.length === 0) continue;\n // Deferral (per person; declared on a channel-wildcard row). Critical\n // events bypass both. A digest cadence takes precedence over\n // quiet-hours: batching already defers to a window boundary, and it\n // additionally tags the target so the dispatcher can collapse the\n // window's deliveries into one message.\n let notBefore: number | undefined;\n let digest: { window: string } | undefined;\n if (!critical) {\n const cadence = this.resolveDigest(index, recipient, ctx.topic);\n if (cadence) {\n const d = digestDeferral(cadence, nowMs, this.resolveQuietHours(index, recipient, ctx.topic)?.tz);\n notBefore = d.notBefore;\n digest = { window: d.window };\n } else {\n const qh = this.resolveQuietHours(index, recipient, ctx.topic);\n notBefore = qh ? quietHoursDeferral(qh, nowMs) : undefined;\n }\n }\n const target: PreferenceTarget = { recipient, channels: accepted };\n if (notBefore != null) target.notBefore = notBefore;\n if (digest) target.digest = digest;\n targets.push(target);\n }\n return targets;\n }\n\n /** Load the candidate rows (topic-specific + wildcard-topic), org-scoped. */\n private async loadRows(data: IDataEngine, ctx: PreferenceContext): Promise<Record<string, unknown>[]> {\n // Two equality queries (topic and the '*' wildcard) avoid relying on\n // driver-specific IN support; user filtering is done in memory.\n const base: Record<string, unknown> = {};\n if (ctx.organizationId) base.organization_id = ctx.organizationId;\n const [specific, wildcard] = await Promise.all([\n data.find(this.objectName, { where: { ...base, topic: ctx.topic }, limit: 10000 }),\n data.find(this.objectName, { where: { ...base, topic: WILDCARD }, limit: 10000 }),\n ]);\n return [...(specific ?? []), ...(wildcard ?? [])];\n }\n\n /**\n * Most-specific-wins lookup for (user, topic, channel). User-specific beats\n * the `*` user; topic/channel specific beats their wildcards.\n */\n private resolveRow(index: Map<string, PrefRowLite>, user: string, topic: string, channel: string): PrefRowLite | undefined {\n for (const u of [user, WILDCARD]) {\n for (const t of [topic, WILDCARD]) {\n for (const c of [channel, WILDCARD]) {\n const hit = index.get(`${u}|${t}|${c}`);\n if (hit !== undefined) return hit;\n }\n }\n }\n return undefined; // built-in default handled by callers (opted in)\n }\n\n /**\n * Resolve a recipient's quiet-hours window. Declared on a channel-wildcard\n * row (`(user, *, *)` or `(user, topic, *)`) — quiet hours are a per-person,\n * channel-agnostic setting. Most-specific user/topic wins.\n */\n private resolveQuietHours(index: Map<string, PrefRowLite>, user: string, topic: string): QuietHours | undefined {\n for (const u of [user, WILDCARD]) {\n for (const t of [topic, WILDCARD]) {\n const hit = index.get(`${u}|${t}|${WILDCARD}`);\n if (hit?.quietHours) return hit.quietHours;\n }\n }\n return undefined;\n }\n\n /**\n * Resolve a recipient's digest cadence. Like quiet-hours, declared on a\n * channel-wildcard row (`(user, *, *)` or `(user, topic, *)`) — batching is a\n * per-person, channel-agnostic setting. Most-specific user/topic wins.\n */\n private resolveDigest(index: Map<string, PrefRowLite>, user: string, topic: string): DigestCadence | undefined {\n for (const u of [user, WILDCARD]) {\n for (const t of [topic, WILDCARD]) {\n const hit = index.get(`${u}|${t}|${WILDCARD}`);\n if (hit?.digest) return hit.digest;\n }\n }\n return undefined;\n }\n}\n\ninterface PrefRowLite {\n enabled: boolean;\n quietHours?: QuietHours;\n digest?: DigestCadence;\n}\n\nfunction asBool(v: unknown): boolean {\n return v === true || v === 1 || v === '1' || v === 'true';\n}\n\nfunction parseQuietHours(v: unknown): QuietHours | undefined {\n let o: any = v;\n if (typeof o === 'string') {\n try { o = JSON.parse(o); } catch { return undefined; }\n }\n if (!o || typeof o !== 'object') return undefined;\n if (o.start == null || o.end == null) return undefined;\n return { tz: o.tz, start: String(o.start), end: String(o.end) };\n}\n\n/** Parse the `digest` preference column; only `daily`/`weekly` enable batching. */\nfunction parseDigest(v: unknown): DigestCadence | undefined {\n return v === 'daily' || v === 'weekly' ? v : undefined;\n}\n\n/**\n * Compute the digest dispatch target for `cadence` relative to `now`:\n * - `notBefore` (epoch ms) — when the accumulating window is flushed:\n * `daily` → next local midnight; `weekly` → next local Monday 00:00.\n * - `window` — a stable label for the *current* accumulating window so all\n * deliveries emitted within it collapse together: the local date for `daily`,\n * the week-start (Monday) date for `weekly`.\n *\n * Wall-clock is read in `tz` (default UTC). Day length is treated as 1440 min;\n * a DST transition can shift the boundary by an hour — acceptable for a batch\n * window, and the dispatcher reconciles against the real `next_attempt_at`. The\n * `tz`→`sys_user` fallback is a separate follow-up (ADR-0030 §remaining).\n */\nexport function digestDeferral(\n cadence: DigestCadence,\n nowMs: number,\n tz?: string,\n): { notBefore: number; window: string } {\n const zone = tz ?? 'UTC';\n const { minutesOfDay, isoWeekday, date } = wallClockInTz(nowMs, zone);\n if (cadence === 'daily') {\n const minsToMidnight = 1440 - minutesOfDay;\n return { notBefore: nowMs + minsToMidnight * 60_000, window: date };\n }\n // weekly: flush at the next Monday 00:00; window keyed by this week's Monday.\n const daysToNextMonday = ((8 - isoWeekday) % 7) || 7;\n const minsToTarget = daysToNextMonday * 1440 - minutesOfDay;\n const mondayMs = nowMs - (isoWeekday - 1) * 86_400_000;\n return { notBefore: nowMs + minsToTarget * 60_000, window: wallClockInTz(mondayMs, zone).date };\n}\n\n/**\n * Compute the deferral target (epoch ms) when `now` falls inside the quiet-hours\n * window, else `undefined`. Times are `HH:MM` in `quietHours.tz` (default UTC).\n * Supports overnight windows (start > end, e.g. 22:00–08:00). Uses `Intl` to read\n * the wall-clock minutes in the tz; returns `now + minutesUntilWindowEnd`.\n */\nexport function quietHoursDeferral(quietHours: QuietHours, nowMs: number): number | undefined {\n const start = parseHHMM(quietHours.start);\n const end = parseHHMM(quietHours.end);\n if (start == null || end == null || start === end) return undefined;\n\n const cur = minutesOfDayInTz(nowMs, quietHours.tz ?? 'UTC');\n let untilEnd: number | undefined;\n if (start < end) {\n if (cur >= start && cur < end) untilEnd = end - cur;\n } else {\n // Overnight window wrapping midnight.\n if (cur >= start) untilEnd = 1440 - cur + end;\n else if (cur < end) untilEnd = end - cur;\n }\n return untilEnd == null ? undefined : nowMs + untilEnd * 60_000;\n}\n\nfunction parseHHMM(s?: string): number | undefined {\n if (!s) return undefined;\n const m = /^(\\d{1,2}):(\\d{2})$/.exec(s.trim());\n if (!m) return undefined;\n const h = Number(m[1]);\n const min = Number(m[2]);\n if (h > 23 || min > 59) return undefined;\n return h * 60 + min;\n}\n\nfunction minutesOfDayInTz(nowMs: number, tz: string): number {\n try {\n const parts = new Intl.DateTimeFormat('en-US', {\n hour12: false,\n hour: '2-digit',\n minute: '2-digit',\n timeZone: tz,\n }).formatToParts(new Date(nowMs));\n const hour = Number(parts.find((p) => p.type === 'hour')?.value ?? '0') % 24; // '24' → 0\n const minute = Number(parts.find((p) => p.type === 'minute')?.value ?? '0');\n return hour * 60 + minute;\n } catch {\n // Unknown tz → treat as UTC.\n const d = new Date(nowMs);\n return d.getUTCHours() * 60 + d.getUTCMinutes();\n }\n}\n\n/**\n * Wall-clock view of `nowMs` in `tz`: minutes since local midnight, ISO weekday\n * (1=Mon … 7=Sun), and the local calendar date as `YYYY-MM-DD`. Falls back to\n * UTC for an unknown tz.\n */\nfunction wallClockInTz(nowMs: number, tz: string): { minutesOfDay: number; isoWeekday: number; date: string } {\n try {\n const parts = new Intl.DateTimeFormat('en-US', {\n hour12: false, year: 'numeric', month: '2-digit', day: '2-digit',\n hour: '2-digit', minute: '2-digit', timeZone: tz,\n }).formatToParts(new Date(nowMs));\n const get = (t: string) => parts.find((p) => p.type === t)?.value ?? '0';\n const y = Number(get('year'));\n const mo = Number(get('month'));\n const d = Number(get('day'));\n const hour = Number(get('hour')) % 24; // '24' → 0\n const minute = Number(get('minute'));\n const dow = new Date(Date.UTC(y, mo - 1, d)).getUTCDay(); // 0=Sun\n return { minutesOfDay: hour * 60 + minute, isoWeekday: dow === 0 ? 7 : dow, date: `${y}-${pad(mo)}-${pad(d)}` };\n } catch {\n const dt = new Date(nowMs);\n const dow = dt.getUTCDay();\n return {\n minutesOfDay: dt.getUTCHours() * 60 + dt.getUTCMinutes(),\n isoWeekday: dow === 0 ? 7 : dow,\n date: dt.toISOString().slice(0, 10),\n };\n }\n}\n\nfunction pad(n: number): string {\n return String(n).padStart(2, '0');\n}\n\nfunction msg(err: unknown): string {\n return (err as Error)?.message ?? String(err);\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport type {\n Delivery,\n ErrorClass,\n MessagingChannel,\n MessagingChannelContext,\n SendResult,\n} from './channel.js';\n\n/** The object the inbox channel writes rows to. */\nexport const INBOX_OBJECT = 'sys_inbox_message';\n\n/** The receipt object the inbox channel writes a `delivered` row to (ADR-0030). */\nexport const RECEIPT_OBJECT = 'sys_notification_receipt';\n\nexport interface InboxChannelOptions {\n /**\n * Resolve the runtime data engine. Returns `undefined` when no data layer\n * is registered (e.g. a minimal test stack) — the channel then warns and\n * reports a no-op success rather than throwing, matching the platform's\n * built-in CRUD-node degradation.\n */\n getData(): IDataEngine | undefined;\n /** Object name override (default {@link INBOX_OBJECT}). */\n objectName?: string;\n /** Receipt object name override (default {@link RECEIPT_OBJECT}). */\n receiptObject?: string;\n /** Clock injection for deterministic tests. Defaults to `new Date()`. */\n now?(): string;\n}\n\n/**\n * The always-on `inbox` channel (ADR-0012 §4).\n *\n * Unlike email/webhook/push, inbox is direction-reversed: there is no outbound\n * call — we write a `sys_inbox_message` row in our own DB and the user's client\n * pulls it. So it needs no connector/transport. One delivery → one row keyed by\n * the recipient user id.\n *\n * Recipients arrive already resolved to user ids by the `RecipientResolver`\n * (ADR-0030 P1) — the email→id fallback that used to live here moved up to the\n * single resolution home, so the channel just keys the row by `recipient`.\n */\nexport function createInboxChannel(opts: InboxChannelOptions): MessagingChannel {\n const objectName = opts.objectName ?? INBOX_OBJECT;\n const receiptObject = opts.receiptObject ?? RECEIPT_OBJECT;\n const now = opts.now ?? (() => new Date().toISOString());\n\n /**\n * Write the `delivered` receipt for an inbox materialization. Best-effort:\n * receipts are the read-state spine but a failure here must never turn a\n * delivered message into a failed one — we log and move on. Skipped when the\n * event id is absent (a synthetic/minimal stack with nothing to key on).\n */\n async function writeDeliveredReceipt(\n ctx: MessagingChannelContext,\n data: IDataEngine,\n r: { notificationId?: string; userId: string; organizationId?: string; at: string },\n ): Promise<void> {\n if (!r.notificationId) return;\n try {\n await data.insert(receiptObject, {\n notification_id: r.notificationId,\n delivery_id: null,\n user_id: r.userId,\n channel: 'inbox',\n state: 'delivered',\n at: r.at,\n organization_id: r.organizationId ?? null,\n created_at: r.at,\n });\n } catch (err) {\n ctx.logger.warn(\n `[inbox] delivered receipt write failed for '${r.userId}' (${(err as Error).message}); inbox row stands`,\n );\n }\n }\n\n return {\n id: 'inbox',\n\n async send(ctx: MessagingChannelContext, delivery: Delivery): Promise<SendResult> {\n const data = opts.getData();\n const n = delivery.notification;\n\n if (!data) {\n ctx.logger.warn(\n `[inbox] no data engine registered; inbox row for '${delivery.recipient}' not persisted`,\n );\n return { ok: true };\n }\n\n const userId = delivery.recipient;\n const at = now();\n\n const row: Record<string, unknown> = {\n user_id: userId,\n notification_id: n.notificationId ?? null,\n topic: n.topic,\n title: n.title,\n body_md: n.body,\n severity: n.severity ?? 'info',\n action_url: n.actionUrl,\n organization_id: n.organizationId ?? null,\n created_at: at,\n };\n\n let inboxId: string | undefined;\n try {\n const created = await data.insert(objectName, row);\n const id = Array.isArray(created) ? created[0]?.id : created?.id ?? created;\n inboxId = id != null ? String(id) : undefined;\n } catch (err) {\n return { ok: false, error: `inbox insert failed: ${(err as Error).message}` };\n }\n\n // Read-state lives in the receipt (ADR-0030), not on the inbox row.\n // Best-effort: a missing receipt must not fail a delivered message.\n await writeDeliveredReceipt(ctx, data, {\n notificationId: n.notificationId,\n userId,\n organizationId: n.organizationId,\n at,\n });\n\n return { ok: true, externalId: inboxId };\n },\n\n classifyError(_err: unknown): ErrorClass {\n // A failed local insert is almost always transient (lock/timeout).\n return 'retryable';\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport type {\n MessagingChannel,\n MessagingChannelContext,\n Notification,\n} from './channel.js';\nimport { RecipientResolver } from './recipient-resolver.js';\nimport { PreferenceResolver, type PreferenceTarget } from './preference-resolver.js';\nimport type { INotificationOutbox } from './outbox.js';\nimport type { EnqueueHttpInput, HttpDelivery, HttpDeliveryStatus, IHttpOutbox } from './http-outbox.js';\nimport { INBOX_OBJECT, RECEIPT_OBJECT } from './inbox-channel.js';\n\n/** The L2 event object every `emit()` writes one row to (ADR-0030). */\nexport const NOTIFICATION_EVENT_OBJECT = 'sys_notification';\n\n/** Receipt states that count as \"read\" for the inbox unread badge (ADR-0030). */\nconst READ_RECEIPT_STATES = new Set(['read', 'clicked', 'dismissed']);\n\n/**\n * Whether a driver error is a unique/primary-key constraint violation. Spans the\n * SQL drivers we ship: SQLite (`UNIQUE constraint failed`), Postgres (`23505` /\n * `duplicate key`), and MySQL (`ER_DUP_ENTRY` / `Duplicate entry`). Used to turn\n * a lost check-then-act race on a unique index into a fallback update.\n */\nfunction isUniqueViolation(err: unknown): boolean {\n const e = err as { code?: string | number; message?: string } | undefined;\n if (!e) return false;\n if (e.code === '23505' || e.code === 'ER_DUP_ENTRY' || e.code === 'SQLITE_CONSTRAINT_UNIQUE') return true;\n const msg = String(e.message ?? '').toLowerCase();\n return (\n msg.includes('unique constraint failed') ||\n msg.includes('duplicate key') ||\n msg.includes('duplicate entry')\n );\n}\n\n/**\n * One row of the inbox list REST response — the `Notification` shape in the API\n * spec (`NotificationSchema`). `id` is the notification's event id\n * (`notification_id`), which keys its read-state receipt, falling back to the\n * inbox row id for synthetic/legacy rows that carry no event id.\n */\nexport interface InboxNotificationView {\n id: string;\n type: string;\n title: string;\n body: string;\n read: boolean;\n actionUrl?: string;\n createdAt: string;\n}\n\n/**\n * Audience selector for {@link EmitInput}. P0 resolves explicit user ids and\n * email-shaped recipients inline (email→id is finished at the inbox channel);\n * `role:` / `team:` / `owner_of:` selectors are forwarded but only fully\n * expanded by the `RecipientResolver` in P1.\n */\nexport type AudienceSpec =\n | string // user id | email | 'role:x' | 'team:x'\n | { ownerOf: { object: string; id: string } };\n\nexport type Audience = AudienceSpec | readonly AudienceSpec[];\n\n/**\n * The single notification ingress (ADR-0030 §3). Every producer — the flow\n * `notify` node, collaboration `@mention`, record assignment, system alerts —\n * calls `emit()` with this shape. No producer writes a per-user inbox row\n * directly; the in-app inbox is a *materialization of delivery*.\n */\nexport interface EmitInput {\n /** Topic id, e.g. `task.assigned`, `collab.mention`. */\n readonly topic: string;\n /** Who should receive it. See {@link Audience}. */\n readonly audience: Audience;\n /** Template inputs carried to channels (title/body/url/actor/source/…). */\n readonly payload?: Record<string, unknown>;\n /** Severity hint for rendering / filtering. */\n readonly severity?: 'info' | 'warning' | 'critical';\n /** Idempotency key within a topic window; a repeat `emit` is a no-op. */\n readonly dedupKey?: string;\n /** The record that triggered the event. */\n readonly source?: { object: string; id: string };\n /** User who caused the event (mentioner, assigner). */\n readonly actorId?: string;\n /** Tenant stamp for sudo/background paths so RLS matches recipients. */\n readonly organizationId?: string;\n /** Channels to fan out to. Defaults to `['inbox']` (always on). */\n readonly channels?: string[];\n}\n\n/** Per-delivery outcome record returned from {@link MessagingService.emit}. */\nexport interface DeliveryOutcome {\n readonly channel: string;\n readonly recipient: string;\n readonly ok: boolean;\n readonly externalId?: string;\n readonly error?: string;\n}\n\n/** Aggregate result of fanning one notification out to its channels. */\nexport interface EmitResult {\n /** Id of the L2 `sys_notification` event written (or matched, on dedup). */\n readonly notificationId: string;\n /** True when `dedupKey` matched an existing event and fan-out was skipped. */\n readonly deduped: boolean;\n readonly deliveries: DeliveryOutcome[];\n readonly delivered: number;\n readonly failed: number;\n}\n\n/** Context the service needs: a logger, plus data access for the L2 event. */\nexport interface MessagingServiceContext extends MessagingChannelContext {\n /**\n * Resolve the runtime data engine used to persist the L2 event. Returns\n * `undefined` on a minimal/test stack with no data layer — `emit()` then\n * uses a synthetic id and warns rather than throwing, matching the\n * platform's CRUD-node degradation.\n */\n getData?(): IDataEngine | undefined;\n /** Clock injection for deterministic tests. Defaults to `new Date()`. */\n now?(): string;\n /** Override the recipient resolver (tests). Defaults to a data-backed one. */\n recipientResolver?: RecipientResolver;\n /** Override the preference resolver (tests). Defaults to a data-backed one. */\n preferenceResolver?: PreferenceResolver;\n /**\n * Topics that bypass the per-user preference matrix (ADR-0030 P2) — e.g.\n * security/system alerts users must not be able to mute. Exact match, or a\n * `prefix.` entry for prefix match.\n */\n mandatoryTopics?: readonly string[];\n /**\n * Durable delivery outbox (ADR-0030 P1). When present, `emit()` enqueues a\n * `pending` delivery row per `(recipient × channel)` and the\n * `NotificationDispatcher` performs the send + retries. When absent, `emit()`\n * fans out inline (best-effort, no retry) — the P0 behavior.\n */\n outbox?: INotificationOutbox;\n}\n\n/**\n * MessagingService — the notification dispatcher (ADR-0012 / ADR-0030).\n *\n * Holds the `MessagingChannel` registry and implements the single ingress\n * `emit(EmitInput)`:\n * 1. write the L2 `sys_notification` event (idempotent on `dedupKey`);\n * 2. resolve the audience to recipient ids (inline in P0; `RecipientResolver`\n * owns role/team/owner_of expansion in P1);\n * 3. fan `(channel × recipient)` deliveries and call each channel's `send()`,\n * which materializes the artifact (inbox row + receipt, email, …).\n *\n * Deliberately *not* in this phase (ADR-0030 P1+): the durable outbox, retry\n * schedule, cluster-lock, dead-letter, the per-user preference matrix,\n * renderers/templates, and digest middleware. `emit()` is best-effort fan-out;\n * failures are reported in the result. The seams are shaped so those land\n * without breaking callers.\n */\nexport class MessagingService {\n private readonly channels = new Map<string, MessagingChannel>();\n private readonly now: () => string;\n private readonly resolver: RecipientResolver;\n private readonly preferences: PreferenceResolver;\n private outbox?: INotificationOutbox;\n private httpOutbox?: IHttpOutbox;\n\n constructor(private readonly ctx: MessagingServiceContext) {\n this.now = ctx.now ?? (() => new Date().toISOString());\n this.resolver =\n ctx.recipientResolver ??\n new RecipientResolver({ getData: () => ctx.getData?.(), logger: ctx.logger });\n this.preferences =\n ctx.preferenceResolver ??\n new PreferenceResolver({\n getData: () => ctx.getData?.(),\n logger: ctx.logger,\n mandatoryTopics: ctx.mandatoryTopics,\n });\n this.outbox = ctx.outbox;\n }\n\n /**\n * Attach the durable delivery outbox after construction. The plugin wires\n * this once the data engine is resolvable (kernel:ready), switching `emit()`\n * from inline fan-out to the reliable enqueue → dispatcher path.\n */\n setOutbox(outbox: INotificationOutbox): void {\n this.outbox = outbox;\n }\n\n /**\n * Attach the generic outbound-HTTP delivery outbox (ADR-0018 M3). Wired by\n * the plugin at `kernel:ready` once the data engine is resolvable. Once set,\n * {@link enqueueHttp} persists durable rows the {@link HttpDispatcher}\n * drains with retry / dead-letter; the Flow `http` node enqueues through it.\n */\n setHttpOutbox(outbox: IHttpOutbox): void {\n this.httpOutbox = outbox;\n }\n\n /**\n * Whether durable HTTP delivery is available. Callers (e.g. the `http` node)\n * fall back to a direct, non-durable send when this is `false`.\n */\n isHttpDeliveryReady(): boolean {\n return this.httpOutbox !== undefined;\n }\n\n /**\n * Enqueue a durable outbound-HTTP delivery (ADR-0018 M3). Returns the row id.\n * Throws if no HTTP outbox is wired — guard with {@link isHttpDeliveryReady}.\n */\n async enqueueHttp(input: EnqueueHttpInput): Promise<string> {\n if (!this.httpOutbox) {\n throw new Error('messaging: HTTP delivery outbox not configured (no data engine / reliableDelivery off)');\n }\n return this.httpOutbox.enqueue(input);\n }\n\n /**\n * Reset a terminal HTTP delivery row back to `pending` so the dispatcher\n * re-sends it (ADR-0018 M3). Backs the webhook redeliver admin endpoint.\n * Throws if no HTTP outbox is wired, or `HttpRedeliverError` for a missing /\n * non-terminal row.\n */\n async redeliverHttp(id: string): Promise<HttpDelivery> {\n if (!this.httpOutbox) {\n throw new Error('messaging: HTTP delivery outbox not configured');\n }\n return this.httpOutbox.redeliver(id);\n }\n\n /** List HTTP delivery rows (admin/tests). Empty when no outbox is wired. */\n async listHttp(filter?: { status?: HttpDeliveryStatus; source?: string }): Promise<HttpDelivery[]> {\n if (!this.httpOutbox) return [];\n return this.httpOutbox.list(filter);\n }\n\n /** Register a channel implementation. A duplicate id warns and replaces. */\n registerChannel(channel: MessagingChannel): void {\n if (this.channels.has(channel.id)) {\n this.ctx.logger.warn(`[messaging] channel '${channel.id}' already registered; replacing`);\n }\n this.channels.set(channel.id, channel);\n this.ctx.logger.info(`[messaging] channel registered: ${channel.id}`);\n }\n\n /** Remove a channel. No-op when absent. */\n unregisterChannel(id: string): void {\n this.channels.delete(id);\n }\n\n /** Look up a channel by id. */\n getChannel(id: string): MessagingChannel | undefined {\n return this.channels.get(id);\n }\n\n /** All registered channel ids. */\n getRegisteredChannels(): string[] {\n return [...this.channels.keys()];\n }\n\n /* ------------------------------------------------------------------ */\n /* Inbox read API (ADR-0030) — backs /api/v1/notifications */\n /* */\n /* The REST notification surface reads the L5 `sys_inbox_message` */\n /* materialization joined with the `sys_notification_receipt` */\n /* read-state spine — NOT the re-modeled `sys_notification` L2 event */\n /* (which carries no recipient/read columns). Mark-read upserts the */\n /* receipt, keyed `(notification_id, user_id, channel:'inbox')`. */\n /* ------------------------------------------------------------------ */\n\n /**\n * List the signed-in user's in-app inbox, joined with read-state.\n *\n * A message is unread until its event has a `read`/`clicked`/`dismissed`\n * receipt; the `read` filter (when given) is applied in-memory after the\n * join. `unreadCount` is computed over the fetched window (bounded by\n * `limit`, like the Console bell's poll). Returns the REST contract shape\n * (`ListNotificationsResponseSchema`): `{ notifications, unreadCount }`.\n */\n async listInbox(\n userId: string,\n opts: { read?: boolean; type?: string; limit?: number } = {},\n ): Promise<{ notifications: InboxNotificationView[]; unreadCount: number }> {\n const data = this.ctx.getData?.();\n if (!data || !userId) return { notifications: [], unreadCount: 0 };\n\n const limit = Math.min(Math.max(opts.limit ?? 50, 1), 200);\n const where: Record<string, unknown> = { user_id: userId };\n if (opts.type) where.topic = opts.type;\n\n const [rows, receipts] = await Promise.all([\n data.find(INBOX_OBJECT, { where, orderBy: [{ field: 'created_at', order: 'desc' }], limit }) as Promise<Array<Record<string, unknown>>>,\n // Read-state spine. Best-effort: if receipts are unavailable the\n // inbox still lists (everything reads as unread) rather than erroring.\n (data.find(RECEIPT_OBJECT, { where: { user_id: userId, channel: 'inbox' } }) as Promise<Array<Record<string, unknown>>>)\n .catch(() => [] as Array<Record<string, unknown>>),\n ]);\n\n // notification_id → most-advanced receipt state (read/clicked/dismissed\n // wins over a plain delivered one).\n const stateByNotif = new Map<string, string>();\n for (const r of receipts) {\n const nid = r?.notification_id != null ? String(r.notification_id) : '';\n if (!nid) continue;\n const state = String(r.state ?? 'delivered');\n const prev = stateByNotif.get(nid);\n if (!prev || (!READ_RECEIPT_STATES.has(prev) && READ_RECEIPT_STATES.has(state))) {\n stateByNotif.set(nid, state);\n }\n }\n\n let unreadCount = 0;\n const all: InboxNotificationView[] = rows.map((m) => {\n const nid = m?.notification_id != null ? String(m.notification_id) : null;\n const state = nid ? stateByNotif.get(nid) : undefined;\n const read = state ? READ_RECEIPT_STATES.has(state) : false;\n if (!read) unreadCount += 1;\n return {\n id: nid ?? String(m.id),\n type: (m.topic as string) ?? 'notification',\n title: (m.title as string) ?? '',\n body: (m.body_md as string) ?? '',\n read,\n actionUrl: (m.action_url as string) ?? undefined,\n createdAt: (m.created_at as string) ?? this.now(),\n };\n });\n\n const notifications = opts.read === undefined ? all : all.filter((n) => n.read === opts.read);\n return { notifications, unreadCount };\n }\n\n /**\n * Mark specific notifications read by upserting their inbox receipts to\n * `read`. Updates the existing `delivered` receipt in place (keyed\n * `(notification_id, user_id, channel:'inbox')`); inserts one only when\n * absent. `ids` are notification (event) ids. Returns the REST contract\n * shape (`MarkNotificationsReadResponseSchema`): `{ success, readCount }`.\n */\n async markRead(userId: string, ids: readonly string[]): Promise<{ success: boolean; readCount: number }> {\n const data = this.ctx.getData?.();\n if (!data || !userId || !ids?.length) return { success: true, readCount: 0 };\n const at = this.now();\n let readCount = 0;\n for (const id of ids) {\n const nid = String(id ?? '');\n if (!nid) continue;\n try {\n readCount += await this.upsertReadReceipt(data, userId, nid, at);\n } catch (err) {\n this.ctx.logger.warn(`[messaging] markRead failed for '${nid}': ${(err as Error).message}`);\n }\n }\n return { success: true, readCount };\n }\n\n /**\n * Mark every currently-unread inbox message for the user as read. Returns\n * `{ success, readCount }` (`MarkAllNotificationsReadResponseSchema`).\n */\n async markAllRead(userId: string): Promise<{ success: boolean; readCount: number }> {\n const data = this.ctx.getData?.();\n if (!data || !userId) return { success: true, readCount: 0 };\n const { notifications } = await this.listInbox(userId, { read: false, limit: 200 });\n return this.markRead(userId, notifications.map((n) => n.id));\n }\n\n /** Upsert a `read` receipt for one notification; returns 1 when it persisted. */\n private async upsertReadReceipt(\n data: IDataEngine,\n userId: string,\n notificationId: string,\n at: string,\n ): Promise<number> {\n const where = { notification_id: notificationId, user_id: userId, channel: 'inbox' };\n const flipToRead = async (): Promise<boolean> => {\n const existing = await data.findOne(RECEIPT_OBJECT, { where, fields: ['id'] });\n if (!existing?.id) return false;\n await data.update(RECEIPT_OBJECT, { state: 'read', at }, { where: { id: existing.id } } as never);\n return true;\n };\n\n if (await flipToRead()) return 1;\n\n // No receipt yet — insert one. findOne→insert is check-then-act, so a\n // concurrent mark-read (or the best-effort `delivered` write still in\n // flight) can win the (notification_id, user_id, channel) unique index\n // between our read and write. Treat that collision as \"someone else\n // created it\" and flip the now-present row to `read` instead of failing.\n try {\n await data.insert(RECEIPT_OBJECT, {\n notification_id: notificationId,\n delivery_id: null,\n user_id: userId,\n channel: 'inbox',\n state: 'read',\n at,\n created_at: at,\n });\n return 1;\n } catch (err) {\n if (isUniqueViolation(err) && (await flipToRead())) return 1;\n throw err;\n }\n }\n\n /**\n * The single notification ingress. Writes the L2 event, resolves the\n * audience, and fans the result out to its channels. An unregistered\n * channel, or a channel that throws, is reported as a failed delivery — it\n * never aborts the rest of the fan-out. A `dedupKey` that matches an\n * existing event short-circuits: the event id is returned and no new\n * deliveries are produced.\n */\n async emit(input: EmitInput): Promise<EmitResult> {\n const data = this.ctx.getData?.();\n\n // 1) Idempotency — a prior event with the same dedup_key is a no-op.\n if (input.dedupKey && data) {\n const existing = await this.findEventByDedupKey(data, input.dedupKey);\n if (existing) {\n this.ctx.logger.info(\n `[messaging] emit: dedupKey '${input.dedupKey}' already emitted (${existing}); skipping`,\n );\n return { notificationId: existing, deduped: true, deliveries: [], delivered: 0, failed: 0 };\n }\n }\n\n // 2) Write the L2 event (or synthesize an id when there is no data layer).\n // The check at (1) is a fast-path. Where the driver materializes the\n // UNIQUE(dedup_key) index, it is the real guard: a concurrent emit\n // that raced past (1) and inserted first makes our insert hit the\n // unique violation — we catch it and converge to the winner's event\n // (treated as a dedup hit), so a record-change storm can't produce\n // duplicate notifications. Mirrors the delivery outbox's enqueue\n // convergence. (Drivers that don't enforce the index fall back to the\n // best-effort fast-path — the catch is then simply never taken.)\n let notificationId: string;\n try {\n notificationId = await this.writeEvent(data, input);\n } catch (err) {\n if (input.dedupKey && data) {\n const winner = await this.findEventByDedupKey(data, input.dedupKey);\n if (winner) {\n this.ctx.logger.info(\n `[messaging] emit: dedupKey '${input.dedupKey}' raced; converged to ${winner}`,\n );\n return { notificationId: winner, deduped: true, deliveries: [], delivered: 0, failed: 0 };\n }\n }\n throw err;\n }\n\n // 3) Resolve the audience to recipient user ids (RecipientResolver owns\n // role:/team:/owner_of:/email→id expansion).\n const recipients = await this.resolver.resolve(input.audience, {\n organizationId: input.organizationId,\n });\n if (recipients.length === 0) {\n this.ctx.logger.warn(`[messaging] emit: topic '${input.topic}' resolved to 0 recipients`);\n return { notificationId, deduped: false, deliveries: [], delivered: 0, failed: 0 };\n }\n\n // 3b) Preference filter (ADR-0030 P2): drop the (recipient × channel)\n // pairs the user muted. Mandatory topics bypass; fail-open on error.\n const payload = input.payload ?? {};\n const channels = input.channels?.length ? input.channels : ['inbox'];\n const targets = await this.preferences.filter(recipients, channels, {\n topic: input.topic,\n organizationId: input.organizationId,\n severity: input.severity,\n });\n if (targets.length === 0) {\n this.ctx.logger.info(`[messaging] emit: topic '${input.topic}' suppressed for all recipients by preference`);\n return { notificationId, deduped: false, deliveries: [], delivered: 0, failed: 0 };\n }\n\n // 4) Either enqueue durable deliveries (P1 outbox) or fan out inline (P0).\n if (this.outbox) {\n const deliveries = await this.enqueueDeliveries(this.outbox, notificationId, targets, input, payload);\n const delivered = deliveries.filter((d) => d.ok).length;\n return { notificationId, deduped: false, deliveries, delivered, failed: deliveries.length - delivered };\n }\n\n const notification: Notification = {\n notificationId,\n organizationId: input.organizationId,\n topic: input.topic,\n title: str(payload.title) ?? input.topic,\n body: str(payload.body) ?? '',\n severity: input.severity ?? 'info',\n recipients,\n channels: input.channels,\n actionUrl: actionUrlFor(input, payload),\n payload: input.payload,\n };\n\n const { deliveries, delivered, failed } = await this.fanOut(notification, targets);\n return { notificationId, deduped: false, deliveries, delivered, failed };\n }\n\n /**\n * Enqueue one `pending` delivery row per `(channel × recipient)`. The\n * dispatcher does the actual send + retry; here `ok` means \"accepted for\n * delivery\" (enqueued), not yet delivered — progress is observable on the\n * `sys_notification_delivery` row.\n */\n private async enqueueDeliveries(\n outbox: INotificationOutbox,\n notificationId: string,\n targets: PreferenceTarget[],\n input: EmitInput,\n payload: Record<string, unknown>,\n ): Promise<DeliveryOutcome[]> {\n // Snapshot the rendered content onto each delivery so a later event edit\n // can't rewrite an in-flight send.\n const deliveryPayload = {\n ...payload,\n title: str(payload.title) ?? input.topic,\n body: str(payload.body) ?? '',\n severity: input.severity ?? 'info',\n actionUrl: actionUrlFor(input, payload),\n };\n const deliveries: DeliveryOutcome[] = [];\n for (const { recipient, channels, notBefore, digest } of targets) {\n for (const channel of channels) {\n try {\n const id = await outbox.enqueue({\n notificationId,\n recipientId: recipient,\n channel,\n topic: input.topic,\n payload: deliveryPayload,\n organizationId: input.organizationId,\n // Quiet-hours / digest deferral (P3b): the dispatcher won't\n // claim this row until `notBefore`. Absent ⇒ immediate.\n notBefore,\n // P3b-2: tag batched deliveries so the dispatcher collapses\n // a `(recipient, channel, window)` group into one message.\n digestKey: digest ? `${recipient}|${channel}|${digest.window}` : undefined,\n });\n deliveries.push({ channel, recipient, ok: true, externalId: id });\n } catch (err) {\n deliveries.push({ channel, recipient, ok: false, error: (err as Error)?.message ?? String(err) });\n }\n }\n }\n return deliveries;\n }\n\n /** Find an existing event id by its dedup key, tolerating lookup failure. */\n private async findEventByDedupKey(data: IDataEngine, dedupKey: string): Promise<string | undefined> {\n try {\n const row = await data.findOne(NOTIFICATION_EVENT_OBJECT, {\n where: { dedup_key: dedupKey },\n fields: ['id'],\n });\n const id = row?.id;\n return id != null && String(id).length > 0 ? String(id) : undefined;\n } catch (err) {\n this.ctx.logger.warn(`[messaging] dedup lookup failed (${(err as Error).message}); proceeding`);\n return undefined;\n }\n }\n\n /**\n * Persist the L2 event and return its id. With no data layer (minimal/test\n * stacks) we warn and synthesize an id so fan-out can still be exercised.\n */\n private async writeEvent(data: IDataEngine | undefined, input: EmitInput): Promise<string> {\n if (!data) {\n this.ctx.logger.warn('[messaging] no data engine registered; event not persisted');\n return `evt_${Math.random().toString(36).slice(2)}`;\n }\n const row: Record<string, unknown> = {\n topic: input.topic,\n payload: input.payload ?? null,\n severity: input.severity ?? 'info',\n dedup_key: input.dedupKey ?? null,\n // Normalize empty strings to null so the (source_object, source_id)\n // index keys on real ids, never '' (producers may pass a bare object\n // with no id — e.g. a comment thread_id with no record part).\n source_object: str(input.source?.object) ?? null,\n source_id: str(input.source?.id) ?? null,\n actor_id: input.actorId ?? null,\n organization_id: input.organizationId ?? null,\n created_at: this.now(),\n };\n const created = await data.insert(NOTIFICATION_EVENT_OBJECT, row);\n const id = Array.isArray(created) ? created[0]?.id : created?.id ?? created;\n return id != null ? String(id) : `evt_${Math.random().toString(36).slice(2)}`;\n }\n\n /**\n * Fan a notification out to each recipient's accepted channels. Each\n * `(recipient, channel)` pair becomes one `send()` call. An unregistered\n * channel, or a channel that throws, is reported as a failed delivery — it\n * never aborts the rest of the fan-out.\n */\n private async fanOut(\n notification: Notification,\n targets: PreferenceTarget[],\n ): Promise<{ deliveries: DeliveryOutcome[]; delivered: number; failed: number }> {\n const deliveries: DeliveryOutcome[] = [];\n\n for (const { recipient, channels } of targets) {\n for (const channelId of channels) {\n const channel = this.channels.get(channelId);\n if (!channel) {\n deliveries.push({\n channel: channelId,\n recipient,\n ok: false,\n error: `channel '${channelId}' not registered`,\n });\n this.ctx.logger.warn(`[messaging] emit: channel '${channelId}' not registered`);\n continue;\n }\n try {\n const result = await channel.send(this.ctx, { notification, channel: channelId, recipient });\n deliveries.push({\n channel: channelId,\n recipient,\n ok: result.ok,\n externalId: result.externalId,\n error: result.error,\n });\n } catch (err) {\n deliveries.push({\n channel: channelId,\n recipient,\n ok: false,\n error: (err as Error)?.message ?? String(err),\n });\n }\n }\n }\n\n const delivered = deliveries.filter((d) => d.ok).length;\n return { deliveries, delivered, failed: deliveries.length - delivered };\n }\n}\n\n/** Coerce a payload value to a non-empty string, else `undefined`. */\nfunction str(v: unknown): string | undefined {\n if (v == null) return undefined;\n const s = String(v);\n return s.length > 0 ? s : undefined;\n}\n\n/**\n * The deep-link the in-app materialization should carry. An explicit\n * `payload.url`/`payload.actionUrl` wins; otherwise, when the emit names a\n * `source` record, synthesize an app-relative `/{object}/{id}` link so the\n * materialization is self-sufficient for navigation (the bell no longer has the\n * L2 event's `source_object/source_id` to fall back on — ADR-0030 L5). Returns\n * `undefined` when there is nothing to link to.\n */\nfunction actionUrlFor(input: EmitInput, payload: Record<string, unknown>): string | undefined {\n const explicit = str(payload.url) ?? str(payload.actionUrl);\n if (explicit) return explicit;\n const obj = str(input.source?.object);\n const id = str(input.source?.id);\n return obj && id ? `/${obj}/${id}` : undefined;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { randomUUID } from 'node:crypto';\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport type {\n AckResult,\n ClaimOptions,\n DeliveryStatus,\n EnqueueDeliveryInput,\n INotificationOutbox,\n NotificationDeliveryRecord,\n} from './outbox.js';\nimport { hashPartition } from './backoff.js';\nimport { toEpochMs } from './audit-timestamp.js';\n\nexport const DELIVERY_OBJECT = 'sys_notification_delivery';\n\nexport interface SqlNotificationOutboxOptions {\n /** Total partitions — MUST match the dispatcher's `partitionCount`. */\n partitionCount: number;\n /** Object name override (default {@link DELIVERY_OBJECT}). */\n objectName?: string;\n}\n\ninterface DeliveryRow {\n id: string;\n notification_id: string;\n recipient_id: string;\n channel: string;\n topic?: string | null;\n payload?: unknown; // json column — engine returns object or string per driver\n organization_id?: string | null;\n partition_key: number;\n status: DeliveryStatus;\n attempts: number;\n claimed_by?: string | null;\n claimed_at?: number | null;\n next_attempt_at?: number | null;\n last_attempted_at?: number | null;\n error?: string | null;\n digest_key?: string | null;\n // Builtin audit columns (native TIMESTAMP on Postgres/MySQL): WRITTEN as\n // `Date`s. Read-back form is dialect-dependent (epoch-ms on SQLite, a\n // `Date`/ISO string on Postgres); `toRecord` normalises via `toEpochMs`.\n created_at: number | string | Date;\n updated_at: number | string | Date;\n}\n\n/**\n * Durable {@link INotificationOutbox} over ObjectQL — the production store.\n * Driver-agnostic (no `FOR UPDATE SKIP LOCKED`): safety comes from the\n * dispatcher's per-partition cluster lock plus the atomic\n * `UPDATE … WHERE status='pending'` claim. `partition_key` is precomputed on\n * enqueue (ObjectQL has no portable `hash()` in WHERE). Mirrors\n * `SqlWebhookOutbox`.\n */\nexport class SqlNotificationOutbox implements INotificationOutbox {\n private readonly objectName: string;\n private readonly partitionCount: number;\n\n constructor(private readonly engine: IDataEngine, opts: SqlNotificationOutboxOptions) {\n if (opts.partitionCount <= 0) throw new Error('SqlNotificationOutbox: partitionCount must be > 0');\n this.objectName = opts.objectName ?? DELIVERY_OBJECT;\n this.partitionCount = opts.partitionCount;\n }\n\n async enqueue(input: EnqueueDeliveryInput): Promise<string> {\n const dedup = {\n notification_id: input.notificationId,\n recipient_id: input.recipientId,\n channel: input.channel,\n };\n const existing = await this.engine.findOne(this.objectName, { where: dedup, fields: ['id'] });\n if (existing?.id) return String(existing.id);\n\n const id = randomUUID();\n // `Date`, not epoch-ms: `created_at` / `updated_at` are native TIMESTAMP\n // columns and a real timestamp column rejects a bare number on Postgres.\n const now = new Date();\n const row: DeliveryRow = {\n id,\n notification_id: input.notificationId,\n recipient_id: input.recipientId,\n channel: input.channel,\n topic: input.topic ?? null,\n payload: input.payload ?? {},\n organization_id: input.organizationId ?? null,\n // Digest rows partition by their group key so a window's rows land in\n // one partition and a single node collapses them under its lock.\n partition_key: hashPartition(input.digestKey ?? input.notificationId, this.partitionCount),\n status: 'pending',\n attempts: 0,\n // Deferred dispatch (quiet-hours / digest, P3): claim() skips pending\n // rows whose next_attempt_at is in the future.\n next_attempt_at: input.notBefore ?? null,\n digest_key: input.digestKey ?? null,\n created_at: now,\n updated_at: now,\n };\n try {\n await this.engine.insert(this.objectName, row);\n return id;\n } catch (err) {\n // Unique-index collision (dedup race) → return the winner.\n const winner = await this.engine.findOne(this.objectName, { where: dedup, fields: ['id'] });\n if (winner?.id) return String(winner.id);\n throw err;\n }\n }\n\n async claim(opts: ClaimOptions): Promise<NotificationDeliveryRecord[]> {\n const now = opts.now ?? Date.now();\n\n // 1. Reap stale in_flight rows (visibility-timeout recovery).\n await this.engine.update(\n this.objectName,\n { status: 'pending', claimed_by: null, claimed_at: null, updated_at: now },\n { where: { status: 'in_flight', claimed_at: { $lt: now - opts.claimTtlMs } }, multi: true } as any,\n );\n\n // 2. Candidate ids: ready pending rows in our partition. Batched (digest)\n // rows are excluded — they drain via claimDigest so they collapse.\n const partitionFilter = opts.partition ? { partition_key: opts.partition.index } : {};\n const candidates = await this.engine.find(this.objectName, {\n where: {\n status: 'pending',\n digest_key: null,\n ...partitionFilter,\n $or: [{ next_attempt_at: null }, { next_attempt_at: { $lte: now } }],\n },\n fields: ['id'],\n limit: opts.limit,\n });\n if (!candidates.length) return [];\n const ids = (candidates as Array<{ id: string }>).map((c) => c.id);\n\n // 3. Atomic claim — WHERE status='pending' rejects rows another worker took.\n await this.engine.update(\n this.objectName,\n { status: 'in_flight', claimed_by: opts.nodeId, claimed_at: now, updated_at: now },\n { where: { id: { $in: ids }, status: 'pending' }, multi: true } as any,\n );\n\n // 4. Read back only the rows we own.\n const claimed = (await this.engine.find(this.objectName, {\n where: { id: { $in: ids }, claimed_by: opts.nodeId, claimed_at: now, status: 'in_flight' },\n })) as DeliveryRow[];\n return claimed.map((r) => this.toRecord(r));\n }\n\n async claimDigest(opts: ClaimOptions): Promise<NotificationDeliveryRecord[]> {\n const now = opts.now ?? Date.now();\n\n // 1. Reap stale in_flight (same as claim).\n await this.engine.update(\n this.objectName,\n { status: 'pending', claimed_by: null, claimed_at: null, updated_at: now },\n { where: { status: 'in_flight', claimed_at: { $lt: now - opts.claimTtlMs } }, multi: true } as any,\n );\n\n // 2. All DUE batched rows in our partition — a window is claimed whole, so\n // we don't apply `limit` (a generous cap guards a pathological backlog).\n const partitionFilter = opts.partition ? { partition_key: opts.partition.index } : {};\n const candidates = await this.engine.find(this.objectName, {\n where: {\n status: 'pending',\n digest_key: { $ne: null },\n ...partitionFilter,\n $or: [{ next_attempt_at: null }, { next_attempt_at: { $lte: now } }],\n },\n fields: ['id'],\n limit: 10000,\n });\n if (!candidates.length) return [];\n const ids = (candidates as Array<{ id: string }>).map((c) => c.id);\n\n // 3. Atomic claim.\n await this.engine.update(\n this.objectName,\n { status: 'in_flight', claimed_by: opts.nodeId, claimed_at: now, updated_at: now },\n { where: { id: { $in: ids }, status: 'pending' }, multi: true } as any,\n );\n\n // 4. Read back the rows we own.\n const claimed = (await this.engine.find(this.objectName, {\n where: { id: { $in: ids }, claimed_by: opts.nodeId, claimed_at: now, status: 'in_flight' },\n })) as DeliveryRow[];\n return claimed.map((r) => this.toRecord(r));\n }\n\n async ack(id: string, result: AckResult): Promise<void> {\n const current = (await this.engine.findOne(this.objectName, {\n where: { id },\n fields: ['attempts'],\n })) as { attempts?: number } | null;\n if (!current) return;\n\n const now = Date.now();\n let status: DeliveryStatus;\n let nextAttemptAt: number | null = null;\n let error: string | null = null;\n\n if (result.success) {\n status = 'success';\n } else if (result.suppressed) {\n status = 'suppressed';\n error = result.error ?? null;\n } else if (result.dead) {\n status = 'dead';\n error = result.error ?? null;\n } else {\n status = 'pending';\n nextAttemptAt = result.nextAttemptAt ?? null;\n error = result.error ?? null;\n }\n\n await this.engine.update(\n this.objectName,\n {\n status,\n attempts: (current.attempts ?? 0) + 1,\n last_attempted_at: now,\n claimed_by: null,\n claimed_at: null,\n next_attempt_at: nextAttemptAt,\n error,\n updated_at: now,\n },\n { where: { id }, multi: false } as any,\n );\n }\n\n async list(filter?: { status?: DeliveryStatus; notificationId?: string }): Promise<NotificationDeliveryRecord[]> {\n const where: Record<string, unknown> = {};\n if (filter?.status) where.status = filter.status;\n if (filter?.notificationId) where.notification_id = filter.notificationId;\n const rows = (await this.engine.find(this.objectName, { where })) as DeliveryRow[];\n return rows.map((r) => this.toRecord(r));\n }\n\n private toRecord(r: DeliveryRow): NotificationDeliveryRecord {\n let payload = r.payload ?? {};\n if (typeof payload === 'string') {\n try { payload = JSON.parse(payload); } catch { payload = {}; }\n }\n return {\n id: r.id,\n notificationId: r.notification_id,\n recipientId: r.recipient_id,\n channel: r.channel,\n topic: r.topic ?? undefined,\n payload: payload as NotificationDeliveryRecord['payload'],\n organizationId: r.organization_id ?? undefined,\n partitionKey: r.partition_key,\n status: r.status,\n attempts: r.attempts,\n claimedBy: r.claimed_by ?? undefined,\n claimedAt: r.claimed_at ?? undefined,\n nextAttemptAt: r.next_attempt_at ?? undefined,\n lastAttemptedAt: r.last_attempted_at ?? undefined,\n error: r.error ?? undefined,\n digestKey: r.digest_key ?? undefined,\n createdAt: toEpochMs(r.created_at),\n updatedAt: toEpochMs(r.updated_at),\n };\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { AckResult } from './outbox.js';\nimport type { SendResult, ErrorClass } from './channel.js';\n\n/**\n * Stable, framework-free partition hash (32-bit FNV-1a). Both the dispatcher\n * and the outbox `claim()` filter on it, so it must be a single shared helper.\n * Same implementation as `plugin-webhooks`.\n */\nexport function hashPartition(key: string, count: number): number {\n if (count <= 0) throw new Error('partition count must be > 0');\n let h = 0x811c9dc5;\n for (let i = 0; i < key.length; i++) {\n h ^= key.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return Math.abs(h | 0) % count;\n}\n\n/**\n * Exponential retry schedule with jitter. Returns the delay (ms) before the\n * next attempt given how many attempts have already happened, or `null` once\n * the budget is exhausted (→ dead).\n *\n * attempt 1 fails → ~1s · 2 → ~10s · 3 → ~1m · 4 → ~10m · 5 → ~1h · 6+ → dead\n */\nexport function nextRetryDelayMs(attemptsSoFar: number, rng: () => number = Math.random): number | null {\n const SCHEDULE = [1_000, 10_000, 60_000, 600_000, 3_600_000];\n if (attemptsSoFar < 1 || attemptsSoFar > SCHEDULE.length) return null;\n const base = SCHEDULE[attemptsSoFar - 1];\n const jitter = 0.8 + rng() * 0.4; // ∈ [0.8, 1.2)\n return Math.floor(base * jitter);\n}\n\n/**\n * Turn a channel `send()` outcome into an {@link AckResult}, applying the retry\n * schedule on retriable failures.\n *\n * - `ok` → success.\n * - `errorClass` of `permanent` → dead immediately (no point retrying).\n * - `errorClass` of `invalid_recipient` → suppressed (not our transport's fault).\n * - otherwise (retryable / unknown) → schedule a retry, or dead once the budget\n * is exhausted.\n */\nexport function classifyDeliveryAttempt(\n result: SendResult,\n errorClass: ErrorClass | undefined,\n attemptsSoFar: number,\n now: number = Date.now(),\n rng?: () => number,\n): AckResult {\n if (result.ok) return { success: true };\n\n if (errorClass === 'invalid_recipient') {\n return { success: false, error: result.error, suppressed: true };\n }\n if (errorClass === 'permanent') {\n return { success: false, error: result.error, dead: true };\n }\n\n const delay = nextRetryDelayMs(attemptsSoFar + 1, rng);\n if (delay === null) {\n return { success: false, error: result.error, dead: true };\n }\n return { success: false, error: result.error, nextAttemptAt: now + delay };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Normalise a builtin `created_at` / `updated_at` value to epoch milliseconds.\n *\n * Those two columns are provisioned as native `TIMESTAMP` columns by the SQL\n * driver (Postgres/MySQL), so the outboxes WRITE them as `Date`s — never a bare\n * epoch-ms number, which a real timestamp column rejects (the bug that broke the\n * `sys_notification_delivery` retention sweep on Postgres). The read-back form is\n * therefore dialect-dependent: epoch-ms on SQLite, a `Date` (or ISO string) on\n * Postgres. This collapses all of those back to epoch-ms so the outbox record\n * contract (`createdAt` / `updatedAt: number`) stays driver-independent.\n */\nexport function toEpochMs(value: unknown): number {\n if (typeof value === 'number') return value;\n if (value instanceof Date) return value.getTime();\n if (typeof value === 'string') {\n const parsed = Date.parse(value);\n if (Number.isFinite(parsed)) return parsed;\n const numeric = Number(value);\n if (Number.isFinite(numeric)) return numeric;\n }\n return 0;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { randomUUID } from 'node:crypto';\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport { hashPartition } from './backoff.js';\nimport { toEpochMs } from './audit-timestamp.js';\nimport {\n HttpRedeliverError,\n type EnqueueHttpInput,\n type HttpAckResult,\n type HttpClaimOptions,\n type HttpDelivery,\n type HttpDeliveryStatus,\n type IHttpOutbox,\n} from './http-outbox.js';\nimport { SYS_HTTP_DELIVERY } from './objects/http-delivery.object.js';\n\nexport interface SqlHttpOutboxOptions {\n /**\n * Total partition count — MUST match the dispatcher's `partitionCount`.\n * Used at enqueue time to precompute `partition_key`.\n */\n partitionCount: number;\n /** Object name to read/write. Defaults to `sys_http_delivery`. */\n objectName?: string;\n}\n\ninterface DeliveryRow {\n id: string;\n source: string;\n ref_id: string;\n dedup_key: string;\n label?: string | null;\n url: string;\n method?: string | null;\n headers_json?: string | null;\n signing_secret?: string | null;\n timeout_ms?: number | null;\n payload_json: string;\n partition_key: number;\n status: HttpDeliveryStatus;\n attempts: number;\n claimed_by?: string | null;\n claimed_at?: number | null;\n next_retry_at?: number | null;\n last_attempted_at?: number | null;\n response_code?: number | null;\n response_body?: string | null;\n error?: string | null;\n // Builtin audit columns (native TIMESTAMP on Postgres/MySQL): WRITTEN as\n // `Date`s. Read-back form is dialect-dependent; `toRecord` normalises via\n // `toEpochMs`.\n created_at: number | string | Date;\n updated_at: number | string | Date;\n}\n\n/**\n * Durable {@link IHttpOutbox} backed by ObjectQL — the production storage impl\n * for the generic outbound-HTTP outbox (ADR-0018 M3). Works against any\n * registered driver through the driver-agnostic `IDataEngine` API.\n *\n * Mirrors `SqlWebhookOutbox` exactly (cluster-lock + atomic\n * `UPDATE WHERE status='pending'` for the exactly-once claim; precomputed\n * `partition_key`; SELECT-then-INSERT dedup converging on the unique index).\n * Dedup uniqueness is `(source, dedup_key)`; partition affinity is on `ref_id`.\n */\nexport class SqlHttpOutbox implements IHttpOutbox {\n private readonly objectName: string;\n private readonly partitionCount: number;\n\n constructor(\n private readonly engine: IDataEngine,\n opts: SqlHttpOutboxOptions,\n ) {\n if (opts.partitionCount <= 0) {\n throw new Error('SqlHttpOutbox: partitionCount must be > 0');\n }\n this.objectName = opts.objectName ?? SYS_HTTP_DELIVERY;\n this.partitionCount = opts.partitionCount;\n }\n\n async enqueue(input: EnqueueHttpInput): Promise<string> {\n const existing = await this.engine.findOne(this.objectName, {\n where: { source: input.source, dedup_key: input.dedupKey },\n fields: ['id'],\n });\n if (existing?.id) return existing.id as string;\n\n const id = randomUUID();\n // `Date`, not epoch-ms: `created_at` / `updated_at` are native TIMESTAMP\n // columns and a real timestamp column rejects a bare number on Postgres.\n const now = new Date();\n const row: DeliveryRow = {\n id,\n source: input.source,\n ref_id: input.refId,\n dedup_key: input.dedupKey,\n label: input.label,\n url: input.url,\n method: input.method ?? 'POST',\n headers_json: input.headers ? JSON.stringify(input.headers) : undefined,\n signing_secret: input.signingSecret,\n timeout_ms: input.timeoutMs,\n payload_json: JSON.stringify(input.payload ?? null),\n partition_key: hashPartition(input.refId, this.partitionCount),\n status: 'pending',\n attempts: 0,\n created_at: now,\n updated_at: now,\n };\n try {\n await this.engine.insert(this.objectName, row);\n return id;\n } catch (err) {\n const winner = await this.engine.findOne(this.objectName, {\n where: { source: input.source, dedup_key: input.dedupKey },\n fields: ['id'],\n });\n if (winner?.id) return winner.id as string;\n throw err;\n }\n }\n\n async claim(opts: HttpClaimOptions): Promise<HttpDelivery[]> {\n const now = opts.now ?? Date.now();\n\n // 1. Reap stale in_flight rows — visibility-timeout recovery.\n await this.engine.update(\n this.objectName,\n { status: 'pending', claimed_by: null, claimed_at: null, updated_at: now },\n {\n where: {\n status: 'in_flight',\n claimed_at: { $lt: now - opts.claimTtlMs },\n },\n multi: true,\n },\n );\n\n // 2. Pick candidate ids.\n const partitionFilter = opts.partition ? { partition_key: opts.partition.index } : {};\n const candidates = await this.engine.find(this.objectName, {\n where: {\n status: 'pending',\n ...partitionFilter,\n $or: [{ next_retry_at: null }, { next_retry_at: { $lte: now } }],\n },\n fields: ['id'],\n limit: opts.limit,\n });\n if (candidates.length === 0) return [];\n\n const ids = (candidates as Array<{ id: string }>).map((c) => c.id);\n\n // 3. Atomic claim. WHERE status='pending' rejects rows another worker took.\n await this.engine.update(\n this.objectName,\n { status: 'in_flight', claimed_by: opts.nodeId, claimed_at: now, updated_at: now },\n { where: { id: { $in: ids }, status: 'pending' }, multi: true },\n );\n\n // 4. Read back the rows we actually own.\n const claimed = (await this.engine.find(this.objectName, {\n where: { id: { $in: ids }, claimed_by: opts.nodeId, claimed_at: now, status: 'in_flight' },\n })) as DeliveryRow[];\n\n return claimed.map((r) => this.toDelivery(r));\n }\n\n async ack(id: string, result: HttpAckResult): Promise<void> {\n const current = (await this.engine.findOne(this.objectName, {\n where: { id },\n fields: ['attempts'],\n })) as { attempts?: number } | null;\n if (!current) return;\n\n const now = Date.now();\n let status: HttpDeliveryStatus;\n let nextRetryAt: number | null;\n let error: string | null;\n\n if (result.success) {\n status = 'success';\n nextRetryAt = null;\n error = null;\n } else if (result.dead) {\n status = 'dead';\n nextRetryAt = null;\n error = result.error ?? null;\n } else {\n status = 'pending';\n nextRetryAt = result.nextRetryAt ?? null;\n error = result.error ?? null;\n }\n\n await this.engine.update(\n this.objectName,\n {\n status,\n attempts: (current.attempts ?? 0) + 1,\n last_attempted_at: now,\n claimed_by: null,\n claimed_at: null,\n response_code: result.httpStatus ?? null,\n response_body: result.responseBody ?? null,\n next_retry_at: nextRetryAt,\n error,\n updated_at: now,\n },\n { where: { id }, multi: false },\n );\n }\n\n async list(filter?: { status?: HttpDeliveryStatus; source?: string }): Promise<HttpDelivery[]> {\n const where: Record<string, unknown> = {};\n if (filter?.status) where.status = filter.status;\n if (filter?.source) where.source = filter.source;\n const rows = (await this.engine.find(this.objectName, { where })) as DeliveryRow[];\n return rows.map((r) => this.toDelivery(r));\n }\n\n async redeliver(id: string): Promise<HttpDelivery> {\n const current = (await this.engine.findOne(this.objectName, { where: { id } })) as DeliveryRow | null;\n if (!current) {\n throw new HttpRedeliverError(`Delivery row '${id}' not found`, 'not_found');\n }\n if (current.status !== 'success' && current.status !== 'failed' && current.status !== 'dead') {\n throw new HttpRedeliverError(\n `Delivery row '${id}' is '${current.status}', expected one of: success, failed, dead`,\n 'not_eligible',\n );\n }\n const now = Date.now();\n await this.engine.update(\n this.objectName,\n {\n status: 'pending',\n attempts: 0,\n claimed_by: null,\n claimed_at: null,\n next_retry_at: null,\n last_attempted_at: null,\n response_code: null,\n response_body: null,\n error: null,\n updated_at: now,\n },\n { where: { id, status: { $in: ['success', 'failed', 'dead'] } }, multi: false },\n );\n const after = (await this.engine.findOne(this.objectName, { where: { id } })) as DeliveryRow | null;\n if (!after || after.status !== 'pending') {\n throw new HttpRedeliverError(`Delivery row '${id}' state changed during redeliver`, 'not_eligible');\n }\n return this.toDelivery(after);\n }\n\n private toDelivery(r: DeliveryRow): HttpDelivery {\n return {\n id: r.id,\n source: r.source,\n refId: r.ref_id,\n dedupKey: r.dedup_key,\n label: r.label ?? undefined,\n url: r.url,\n method: r.method ?? undefined,\n headers: r.headers_json ? JSON.parse(r.headers_json) : undefined,\n signingSecret: r.signing_secret ?? undefined,\n timeoutMs: r.timeout_ms ?? undefined,\n payload: JSON.parse(r.payload_json),\n status: r.status,\n attempts: r.attempts,\n claimedBy: r.claimed_by ?? undefined,\n claimedAt: r.claimed_at ?? undefined,\n nextRetryAt: r.next_retry_at ?? undefined,\n lastAttemptedAt: r.last_attempted_at ?? undefined,\n responseCode: r.response_code ?? undefined,\n responseBody: r.response_body ?? undefined,\n error: r.error ?? undefined,\n createdAt: toEpochMs(r.created_at),\n updatedAt: toEpochMs(r.updated_at),\n };\n }\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Generic outbound-HTTP delivery outbox (ADR-0018 M3).\n *\n * This is the *raw HTTP callout* counterpart to the notification outbox\n * (`outbox.ts`, which is recipient/channel-centric). It stores rows that must\n * be POSTed (or any method) exactly once — modulo at-least-once + receiver-side\n * idempotency — with retry / backoff / dead-letter handled by the shared\n * {@link HttpDispatcher}.\n *\n * It generalises the original `plugin-webhooks` outbox so two callers share one\n * reliable substrate:\n * - the Flow `http` node executor (`@objectstack/service-automation`), and\n * - webhook fan-out (`@objectstack/plugin-webhooks`),\n *\n * which is exactly the \"build the reliability machinery once, reuse it\n * everywhere\" decision in ADR-0018 §4. Webhook-specific concepts collapse onto\n * generic fields: `webhookId`→`refId`, `eventId`→`dedupKey`, `eventType`→`label`,\n * `secret`→`signingSecret`.\n */\n\nexport type HttpDeliveryStatus =\n | 'pending'\n | 'in_flight'\n | 'success'\n | 'failed'\n | 'dead';\n\nexport interface HttpDelivery {\n /** UUID — also doubles as the receiver-side idempotency key (`X-Objectstack-Delivery`). */\n id: string;\n /**\n * Provenance domain — e.g. `'webhook'` | `'flow'`. Combined with `dedupKey`\n * for the uniqueness constraint, and used (with `refId`) for partition\n * affinity so rows from one source/anchor stay in-order.\n */\n source: string;\n /**\n * Partition / ordering anchor within `source` — the webhook id, the flow id,\n * etc. `hash(refId) mod partitionCount` picks the partition.\n */\n refId: string;\n /** UNIQUE(source, dedup_key) prevents double-enqueue. */\n dedupKey: string;\n /**\n * Human/diagnostic label, e.g. an event type (`data.record.created`) or a\n * `flow:node` id. Surfaced on the `X-Objectstack-Event` header when present.\n */\n label?: string;\n /** Destination URL (snapshotted on enqueue — config edits don't rewrite live rows). */\n url: string;\n /** HTTP method — defaults to POST. */\n method?: string;\n /** Custom headers. */\n headers?: Record<string, string>;\n /** HMAC-SHA256 secret. If present, an `X-Objectstack-Signature` header is added. */\n signingSecret?: string;\n /** Per-request timeout in ms. */\n timeoutMs?: number;\n /** JSON-serialisable body. */\n payload: unknown;\n\n /** Lifecycle state. */\n status: HttpDeliveryStatus;\n /** Number of attempts made so far (0 before first attempt). */\n attempts: number;\n /** Node id currently working on this row, when `status = in_flight`. */\n claimedBy?: string;\n /** Wall-clock ms when the row was claimed. */\n claimedAt?: number;\n /** Earliest ms at which this row becomes eligible for the next attempt. */\n nextRetryAt?: number;\n /** Wall-clock ms of the last attempt (success or fail). */\n lastAttemptedAt?: number;\n /** HTTP status code from the most recent attempt. */\n responseCode?: number;\n /** Truncated response body for diagnostics. */\n responseBody?: string;\n /** Last transport / timeout error message. */\n error?: string;\n\n createdAt: number;\n updatedAt: number;\n}\n\nexport interface EnqueueHttpInput {\n source: string;\n refId: string;\n dedupKey: string;\n label?: string;\n url: string;\n method?: string;\n headers?: Record<string, string>;\n signingSecret?: string;\n timeoutMs?: number;\n payload: unknown;\n}\n\nexport interface HttpClaimOptions {\n /** Identifier of the node doing the claim (for `claimedBy`). */\n nodeId: string;\n /** Max rows to claim per call. */\n limit: number;\n /**\n * Partition assignment for this worker. Only rows whose\n * `hash(refId) mod count === index` are claimed. Omit to claim across all\n * partitions (single-node mode).\n */\n partition?: { index: number; count: number };\n /** Visibility timeout — claimed rows revert to pending after this many ms. */\n claimTtlMs: number;\n /** \"Now\" reference, ms since epoch. Defaults to Date.now(). */\n now?: number;\n}\n\nexport interface HttpAckSuccess {\n success: true;\n httpStatus: number;\n responseBody?: string;\n durationMs: number;\n}\n\nexport interface HttpAckFailure {\n success: false;\n httpStatus?: number;\n responseBody?: string;\n error?: string;\n durationMs: number;\n /** Computed by the dispatcher per the retry schedule, or undefined for dead. */\n nextRetryAt?: number;\n /** Marks the row terminal — no more attempts. */\n dead?: boolean;\n}\n\nexport type HttpAckResult = HttpAckSuccess | HttpAckFailure;\n\n/**\n * Error raised by `IHttpOutbox.redeliver` when the requested row is either\n * missing or in a non-terminal state.\n */\nexport class HttpRedeliverError extends Error {\n constructor(\n message: string,\n readonly code: 'not_found' | 'not_eligible',\n ) {\n super(message);\n this.name = 'HttpRedeliverError';\n }\n}\n\n/**\n * Pluggable storage backend for outbound-HTTP delivery rows. Implementations\n * MUST make `claim()` atomic across concurrent callers — that property is the\n * exactly-once guarantee.\n */\nexport interface IHttpOutbox {\n /**\n * Insert a new delivery row. Implementations MUST treat `(source, dedupKey)`\n * as unique and silently converge duplicates. Returns the row id (existing\n * or new).\n */\n enqueue(input: EnqueueHttpInput): Promise<string>;\n\n /**\n * Atomically claim up to `limit` rows whose `nextRetryAt <= now` (or null)\n * and matching the partition predicate. Claimed rows MUST be marked\n * `in_flight` so concurrent claimers don't see them.\n */\n claim(opts: HttpClaimOptions): Promise<HttpDelivery[]>;\n\n /** Record the outcome of an attempt. */\n ack(id: string, result: HttpAckResult): Promise<void>;\n\n /** Snapshot accessor for tests / admin tooling. */\n list(filter?: { status?: HttpDeliveryStatus; source?: string }): Promise<HttpDelivery[]>;\n\n /**\n * Reset a terminal row (`success` / `failed` / `dead`) back to `pending` so\n * the dispatcher re-sends it. Resets `attempts=0`; URL / payload / secret are\n * NOT touched (byte-for-byte replay). Throws {@link HttpRedeliverError}.\n */\n redeliver(id: string): Promise<HttpDelivery>;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Field, ObjectSchema } from '@objectstack/spec/data';\n\n/**\n * `sys_http_delivery` — durable outbox row for one outbound-HTTP attempt\n * (ADR-0018 M3).\n *\n * The raw-callout counterpart to `sys_notification_delivery` (recipient/channel\n * deliveries). Shared by the Flow `http` node executor and webhook fan-out so\n * both inherit retry / idempotency / dead-letter from one substrate. Generalises\n * `sys_webhook_delivery`: `webhook_id`→`ref_id`, `event_id`→`dedup_key`,\n * `event_type`→`label`, `secret`→`signing_secret`.\n *\n * Designed for the SqlHttpOutbox claim algorithm:\n * 1. Producers INSERT pending rows (dedup'd by `(source, dedup_key)`).\n * 2. The per-partition lock-holder runs:\n * SELECT id WHERE status='pending' AND partition_key=? AND (next_retry_at <= now OR null)\n * UPDATE SET status='in_flight' WHERE id IN (...) AND status='pending' ← atomic claim\n * POST to target URL\n * UPDATE SET status=success/pending/dead, attempts=attempts+1, ...\n *\n * `partition_key` is precomputed on enqueue (`hash(ref_id) mod N`) so the\n * dispatcher filters cheaply without DB-side hash functions.\n *\n * @namespace sys\n */\nexport const HttpDelivery = ObjectSchema.create({\n name: 'sys_http_delivery',\n label: 'HTTP Delivery',\n pluralLabel: 'HTTP Deliveries',\n icon: 'globe',\n isSystem: true,\n managedBy: 'system',\n userActions: { create: false, edit: false, delete: false, import: false },\n description:\n 'Durable outbox row for one outbound-HTTP attempt (ADR-0018). Managed by @objectstack/service-messaging; do not write directly.',\n displayNameField: 'id',\n titleFormat: '{label} → {url}',\n compactLayout: ['source', 'url', 'status', 'attempts', 'next_retry_at'],\n\n listViews: {\n recent: {\n type: 'grid',\n name: 'recent',\n label: 'Recent',\n data: { provider: 'object', object: 'sys_http_delivery' },\n columns: ['source', 'label', 'url', 'status', 'attempts', 'response_code', 'updated_at'],\n sort: [{ field: 'updated_at', order: 'desc' }],\n pagination: { pageSize: 50 },\n },\n failures: {\n type: 'grid',\n name: 'failures',\n label: 'Failures',\n data: { provider: 'object', object: 'sys_http_delivery' },\n columns: ['source', 'url', 'status', 'attempts', 'response_code', 'error', 'updated_at'],\n filter: [{ field: 'status', operator: 'in', value: ['failed', 'dead'] }],\n sort: [{ field: 'updated_at', order: 'desc' }],\n pagination: { pageSize: 50 },\n },\n pending: {\n type: 'grid',\n name: 'pending',\n label: 'Pending',\n data: { provider: 'object', object: 'sys_http_delivery' },\n columns: ['source', 'url', 'attempts', 'next_retry_at', 'updated_at'],\n filter: [{ field: 'status', operator: 'equals', value: 'pending' }],\n sort: [{ field: 'next_retry_at', order: 'asc' }],\n pagination: { pageSize: 50 },\n },\n },\n\n fields: {\n id: Field.text({\n label: 'Delivery ID',\n required: true,\n maxLength: 64,\n description: 'UUID — also doubles as the receiver-side idempotency key',\n }),\n\n source: Field.text({\n label: 'Source',\n required: true,\n maxLength: 32,\n description: \"Provenance domain, e.g. 'webhook' | 'flow'. UNIQUE(source, dedup_key).\",\n }),\n\n ref_id: Field.text({\n label: 'Ref ID',\n required: true,\n maxLength: 128,\n description: 'Partition/ordering anchor within source (webhook id, flow id, …)',\n }),\n\n dedup_key: Field.text({\n label: 'Dedup Key',\n required: true,\n maxLength: 191,\n description: 'UNIQUE(source, dedup_key) for at-most-once enqueue',\n }),\n\n label: Field.text({\n label: 'Label',\n required: false,\n maxLength: 191,\n description: 'Diagnostic label / event type — surfaced on X-Objectstack-Event',\n }),\n\n url: Field.text({\n label: 'Target URL',\n required: true,\n maxLength: 2048,\n description: 'Snapshotted at enqueue so config edits do not rewrite live rows',\n }),\n\n method: Field.text({ label: 'Method', required: false, maxLength: 10 }),\n headers_json: Field.textarea({ label: 'Headers JSON', required: false }),\n signing_secret: Field.text({ label: 'HMAC Secret', required: false, maxLength: 256 }),\n timeout_ms: Field.number({ label: 'Timeout (ms)', required: false }),\n payload_json: Field.textarea({ label: 'Payload JSON', required: true }),\n\n partition_key: Field.number({\n label: 'Partition',\n required: true,\n description: 'hash(ref_id) mod partitionCount — precomputed for cheap WHERE',\n }),\n\n status: Field.text({\n label: 'Status',\n required: true,\n defaultValue: 'pending',\n maxLength: 16,\n description: 'pending | in_flight | success | failed | dead',\n }),\n\n attempts: Field.number({\n label: 'Attempts',\n required: true,\n defaultValue: 0,\n description: 'Number of attempts made so far',\n }),\n\n claimed_by: Field.text({ label: 'Claimed By', required: false, maxLength: 128 }),\n claimed_at: Field.number({ label: 'Claimed At (ms)', required: false }),\n next_retry_at: Field.number({ label: 'Next Retry At (ms)', required: false }),\n last_attempted_at: Field.number({ label: 'Last Attempted At (ms)', required: false }),\n response_code: Field.number({ label: 'HTTP Status', required: false }),\n response_body: Field.textarea({ label: 'Response Body (capped)', required: false }),\n error: Field.textarea({ label: 'Error', required: false }),\n\n // Builtin audit columns are native TIMESTAMP columns (Postgres/MySQL),\n // so declare them `datetime` and write `Date`s (not epoch-ms numbers,\n // which a real timestamp column rejects). See SqlHttpOutbox.\n created_at: Field.datetime({ label: 'Created At', required: true }),\n updated_at: Field.datetime({ label: 'Updated At', required: true }),\n },\n\n indexes: [\n { fields: ['source', 'dedup_key'], unique: true },\n // Hot path: claim query\n { fields: ['status', 'partition_key', 'next_retry_at'] },\n // Reaper: scan stale in_flight rows by claimed_at\n { fields: ['status', 'claimed_at'] },\n { fields: ['source', 'ref_id'] },\n ],\n});\n\n/** Canonical object name — exported so SqlHttpOutbox callers can override. */\nexport const SYS_HTTP_DELIVERY = 'sys_http_delivery' as const;\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { NotificationDeliveryRecord } from './outbox.js';\n\n/**\n * One collapsed digest message assembled from a window's batched deliveries\n * (ADR-0030 P3b-2). `items` preserves the individual notifications so a\n * structured channel (inbox) can render a list, while `title`/`body` give a\n * flat rendering for plain channels (email text).\n */\nexport interface DigestRenderResult {\n title: string;\n body: string;\n severity: 'info';\n count: number;\n items: Array<{\n notificationId: string;\n title: string;\n body?: string;\n topic?: string;\n actionUrl?: string;\n }>;\n}\n\n/**\n * Collapse same-`(recipient, channel, window)` deliveries into a single message.\n * The caller guarantees `rows` is non-empty and shares a recipient + channel\n * (the digest group). Only non-`critical` notifications are ever batched, so the\n * digest severity is always `info`.\n */\nexport function renderDigest(rows: NotificationDeliveryRecord[]): DigestRenderResult {\n const items = rows.map((r) => {\n const p = r.payload ?? {};\n return {\n notificationId: r.notificationId,\n title: typeof p.title === 'string' && p.title ? p.title : (r.topic ?? 'Notification'),\n body: typeof p.body === 'string' && p.body ? p.body : undefined,\n topic: r.topic,\n actionUrl: typeof p.actionUrl === 'string' && p.actionUrl ? p.actionUrl : undefined,\n };\n });\n const count = items.length;\n const title = count === 1 ? items[0].title : `You have ${count} notifications`;\n const body = items.map((it) => `• ${it.title}`).join('\\n');\n return { title, body, severity: 'info', count, items };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { MessagingChannel, MessagingChannelContext, Notification, SendResult } from './channel.js';\nimport type { INotificationOutbox, NotificationDeliveryRecord } from './outbox.js';\nimport { classifyDeliveryAttempt } from './backoff.js';\nimport { renderDigest } from './digest-render.js';\n\n/** Minimal channel-registry surface the dispatcher needs (MessagingService satisfies it). */\nexport interface ChannelRegistry {\n getChannel(id: string): MessagingChannel | undefined;\n}\n\n/** A held lock; `release()` frees it, `isHeld()`/`renew()` mirror the cluster API. */\nexport interface DispatchLockHandle {\n release(): Promise<void> | void;\n isHeld?(): boolean;\n renew?(ttlMs: number): Promise<void> | void;\n}\n\n/** Just the slice of `IClusterService` the dispatcher uses. */\nexport interface DispatchCluster {\n lock: {\n acquire(key: string, opts: { ttlMs: number; waitMs: number }): Promise<DispatchLockHandle | null>;\n };\n}\n\n/**\n * Single-node fallback lock — always grants. Used when no cluster service is\n * registered, so the dispatcher runs correctly (just without cross-node\n * coordination). The per-partition serialization a single process needs is\n * already provided by the `inflightTick` guard + the outbox's atomic claim.\n */\nconst SINGLE_NODE_CLUSTER: DispatchCluster = {\n lock: {\n async acquire() {\n return { release() {}, isHeld: () => true, renew() {} };\n },\n },\n};\n\nexport interface NotificationDispatcherLogger {\n warn: (msg: string, meta?: any) => void;\n info?: (msg: string, meta?: any) => void;\n}\n\nexport interface NotificationDispatcherOptions {\n nodeId: string;\n outbox: INotificationOutbox;\n channels: ChannelRegistry;\n /** Context handed to each channel's `send()` (logger). */\n channelContext: MessagingChannelContext;\n /** Cross-node coordination. Defaults to a single-node always-grant lock. */\n cluster?: DispatchCluster;\n partitionCount?: number;\n batchSize?: number;\n intervalMs?: number;\n lockTtlMs?: number;\n claimTtlMs?: number;\n rng?: () => number;\n /** Injectable clock (ms) for deterministic tests. Defaults to Date.now. */\n now?: () => number;\n logger?: NotificationDispatcherLogger;\n /** Observability hook fired after every attempt. */\n onAttempt?: (delivery: NotificationDeliveryRecord, success: boolean) => void;\n}\n\n/**\n * NotificationDispatcher (ADR-0030 P1) — drains the `sys_notification_delivery`\n * outbox and sends each row through its channel, retrying with backoff and\n * dead-lettering once the budget is exhausted. Structurally mirrors\n * `WebhookDispatcher`: an interval loop walks `partitionCount` partitions, each\n * guarded by a per-partition cluster lock; within a held partition it claims a\n * batch (`pending → in_flight`), sends, and acks.\n *\n * At-least-once: if a channel send succeeds but the ack write fails, the row\n * reverts to pending after the claim TTL and is re-sent — the inbox channel's\n * receipt write is idempotent-friendly, and downstream channels should be too.\n */\nexport class NotificationDispatcher {\n private readonly opts: Required<\n Omit<NotificationDispatcherOptions, 'rng' | 'logger' | 'onAttempt' | 'cluster' | 'now'>\n > &\n Pick<NotificationDispatcherOptions, 'rng' | 'logger' | 'onAttempt' | 'now'> & { cluster: DispatchCluster };\n private timer: ReturnType<typeof setInterval> | undefined;\n private running = false;\n private inflightTick: Promise<void> | undefined;\n\n constructor(options: NotificationDispatcherOptions) {\n const intervalMs = options.intervalMs ?? 500;\n const lockTtlMs = options.lockTtlMs ?? intervalMs * 5;\n this.opts = {\n nodeId: options.nodeId,\n outbox: options.outbox,\n channels: options.channels,\n channelContext: options.channelContext,\n cluster: options.cluster ?? SINGLE_NODE_CLUSTER,\n partitionCount: options.partitionCount ?? 8,\n batchSize: options.batchSize ?? 32,\n intervalMs,\n lockTtlMs,\n claimTtlMs: options.claimTtlMs ?? lockTtlMs * 2,\n rng: options.rng,\n now: options.now,\n logger: options.logger,\n onAttempt: options.onAttempt,\n };\n }\n\n /** Begin the periodic loop. Idempotent. */\n start(): void {\n if (this.running) return;\n this.running = true;\n this.scheduleTick();\n this.timer = setInterval(() => this.scheduleTick(), this.opts.intervalMs);\n // Don't keep the event loop alive solely for the dispatcher.\n (this.timer as { unref?: () => void })?.unref?.();\n }\n\n /** Stop the loop and drain the in-flight tick. */\n async stop(): Promise<void> {\n if (!this.running) return;\n this.running = false;\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = undefined;\n }\n if (this.inflightTick) {\n try { await this.inflightTick; } catch { /* already logged */ }\n }\n }\n\n /** Run one full tick (all partitions). Exposed for deterministic tests. */\n async tick(): Promise<void> {\n await this.runTick();\n }\n\n private scheduleTick(): void {\n if (this.inflightTick) return;\n this.inflightTick = this.runTick()\n .catch((err) => {\n this.opts.logger?.warn?.('notification-dispatcher: tick failed', {\n nodeId: this.opts.nodeId,\n error: (err as Error)?.message ?? String(err),\n });\n })\n .finally(() => { this.inflightTick = undefined; });\n }\n\n private async runTick(): Promise<void> {\n const count = this.opts.partitionCount;\n const offset = stableNodeOffset(this.opts.nodeId, count);\n for (let step = 0; step < count; step++) {\n await this.runPartition((offset + step) % count);\n }\n }\n\n private async runPartition(index: number): Promise<void> {\n const handle = await this.opts.cluster.lock.acquire(`notify.dispatcher.partition.${index}`, {\n ttlMs: this.opts.lockTtlMs,\n waitMs: 0,\n });\n if (!handle) return;\n try {\n const claimed = await this.opts.outbox.claim({\n nodeId: this.opts.nodeId,\n limit: this.opts.batchSize,\n partition: { index, count: this.opts.partitionCount },\n claimTtlMs: this.opts.claimTtlMs,\n });\n if (claimed.length > 0) {\n await handle.renew?.(this.opts.lockTtlMs);\n for (const row of claimed) {\n if (handle.isHeld && !handle.isHeld()) break;\n await this.processRow(row);\n }\n }\n\n // P3b-2 digest pass: collapse due batched rows by group. Runs under\n // the same partition lock — a window's rows share a partition (keyed\n // on digest_key), so exactly one node assembles each digest.\n const digestRows = await this.opts.outbox.claimDigest({\n nodeId: this.opts.nodeId,\n limit: this.opts.batchSize,\n partition: { index, count: this.opts.partitionCount },\n claimTtlMs: this.opts.claimTtlMs,\n });\n if (digestRows.length > 0) {\n await handle.renew?.(this.opts.lockTtlMs);\n for (const group of groupByDigestKey(digestRows)) {\n if (handle.isHeld && !handle.isHeld()) break;\n await this.processDigestGroup(group);\n }\n }\n } finally {\n await handle.release();\n }\n }\n\n /**\n * Send one collapsed message for a `(recipient, channel, window)` group and\n * ack every row in it with that one outcome. On failure the whole group\n * re-defers together (each row keeps its own backoff via its `attempts`).\n */\n private async processDigestGroup(rows: NotificationDeliveryRecord[]): Promise<void> {\n const channelName = rows[0].channel;\n const recipient = rows[0].recipientId;\n const channel = this.opts.channels.getChannel(channelName);\n if (!channel) {\n for (const row of rows) {\n await this.opts.outbox.ack(row.id, { success: false, error: `channel '${channelName}' not registered`, dead: true });\n this.opts.onAttempt?.(row, false);\n }\n return;\n }\n\n const digest = renderDigest(rows);\n const notification: Notification = {\n notificationId: rows[0].notificationId, // representative event id\n organizationId: rows[0].organizationId,\n topic: rows[0].topic,\n title: digest.title,\n body: digest.body,\n severity: 'info',\n recipients: [recipient],\n channels: [channelName],\n payload: { digest: true, count: digest.count, items: digest.items },\n };\n\n let result: SendResult;\n try {\n result = await channel.send(this.opts.channelContext, { notification, channel: channelName, recipient });\n } catch (err) {\n result = { ok: false, error: (err as Error)?.message ?? String(err) };\n }\n\n const errorClass = !result.ok && channel.classifyError ? channel.classifyError(result.error) : undefined;\n const now = this.opts.now?.() ?? Date.now();\n for (const row of rows) {\n const ack = classifyDeliveryAttempt(result, errorClass, row.attempts, now, this.opts.rng);\n await this.opts.outbox.ack(row.id, ack);\n this.opts.onAttempt?.(row, result.ok);\n }\n }\n\n private async processRow(row: NotificationDeliveryRecord): Promise<void> {\n const channel = this.opts.channels.getChannel(row.channel);\n if (!channel) {\n // No transport for this channel → terminal, observable on the row.\n await this.opts.outbox.ack(row.id, {\n success: false,\n error: `channel '${row.channel}' not registered`,\n dead: true,\n });\n this.opts.onAttempt?.(row, false);\n return;\n }\n\n const p = row.payload ?? {};\n const notification: Notification = {\n notificationId: row.notificationId,\n organizationId: row.organizationId,\n topic: row.topic,\n title: typeof p.title === 'string' ? p.title : row.topic ?? '',\n body: typeof p.body === 'string' ? p.body : '',\n severity: (p.severity as Notification['severity']) ?? 'info',\n recipients: [row.recipientId],\n channels: [row.channel],\n actionUrl: typeof p.actionUrl === 'string' ? p.actionUrl : undefined,\n payload: p,\n };\n\n let result: SendResult;\n try {\n result = await channel.send(this.opts.channelContext, {\n notification,\n channel: row.channel,\n recipient: row.recipientId,\n });\n } catch (err) {\n result = { ok: false, error: (err as Error)?.message ?? String(err) };\n }\n\n const errorClass = !result.ok && channel.classifyError ? channel.classifyError(result.error) : undefined;\n const now = this.opts.now?.() ?? Date.now();\n const ack = classifyDeliveryAttempt(result, errorClass, row.attempts, now, this.opts.rng);\n await this.opts.outbox.ack(row.id, ack);\n this.opts.onAttempt?.(row, result.ok);\n }\n}\n\n/** Group claimed digest rows by their `digestKey` (insertion order preserved). */\nfunction groupByDigestKey(rows: NotificationDeliveryRecord[]): NotificationDeliveryRecord[][] {\n const groups = new Map<string, NotificationDeliveryRecord[]>();\n for (const r of rows) {\n const key = r.digestKey ?? r.id; // defensive — claimDigest only returns keyed rows\n let g = groups.get(key);\n if (!g) { g = []; groups.set(key, g); }\n g.push(r);\n }\n return [...groups.values()];\n}\n\n/** Spread the starting partition per node so contention rotates fairly. */\nfunction stableNodeOffset(nodeId: string, partitionCount: number): number {\n let h = 0;\n for (let i = 0; i < nodeId.length; i++) h = (h * 31 + nodeId.charCodeAt(i)) | 0;\n return Math.abs(h) % partitionCount;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { createHmac, randomUUID } from 'node:crypto';\nimport type { HttpAckResult, HttpDelivery } from './http-outbox.js';\n\n/**\n * Pure HTTP transport for the generic outbound-delivery outbox (ADR-0018 M3).\n *\n * Lifted and generalised from `plugin-webhooks/src/http-sender.ts`: a single\n * stateless attempt (`sendOnce`) plus the retry-schedule classifier\n * (`classifyAttempt`). The dispatcher owns claim/ack; this module owns the wire.\n */\n\n/** Default per-request timeout. */\nexport const DEFAULT_HTTP_TIMEOUT_MS = 15_000;\n\n/** Truncate response bodies to keep storage cost predictable. */\nconst RESPONSE_BODY_CAP = 16 * 1024;\n\nexport type FetchImpl = (\n input: string,\n init: {\n method: string;\n headers: Record<string, string>;\n body: string;\n signal: AbortSignal;\n },\n) => Promise<{\n ok: boolean;\n status: number;\n text(): Promise<string>;\n}>;\n\n/** Single HTTP attempt classified to an ack shape (without nextRetryAt). */\nexport type HttpAttemptOutcome =\n | { success: true; httpStatus: number; responseBody?: string; durationMs: number }\n | {\n success: false;\n retriable: boolean;\n httpStatus?: number;\n responseBody?: string;\n error?: string;\n durationMs: number;\n };\n\n/**\n * Send one HTTP attempt for the delivery. Pure (no DB writes) so the dispatcher\n * owns retry-schedule + ack logic.\n *\n * - 2xx → success\n * - 4xx (except 408/429) → permanent failure (retriable = false → dead)\n * - 408, 429, 5xx, transport → retriable\n */\nexport async function sendOnce(\n delivery: HttpDelivery,\n fetchImpl: FetchImpl,\n): Promise<HttpAttemptOutcome> {\n const body =\n typeof delivery.payload === 'string'\n ? delivery.payload\n : JSON.stringify(delivery.payload ?? null);\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'User-Agent': 'ObjectStack-Http/1.0',\n 'X-Objectstack-Delivery': delivery.id,\n 'X-Objectstack-Attempt': String(delivery.attempts + 1),\n ...(delivery.label ? { 'X-Objectstack-Event': delivery.label } : {}),\n ...(delivery.headers ?? {}),\n };\n if (delivery.signingSecret) {\n const sig = createHmac('sha256', delivery.signingSecret).update(body).digest('hex');\n headers['X-Objectstack-Signature'] = `sha256=${sig}`;\n }\n\n const timeoutMs = delivery.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const start = Date.now();\n try {\n const res = await fetchImpl(delivery.url, {\n method: delivery.method ?? 'POST',\n headers,\n body,\n signal: controller.signal,\n });\n clearTimeout(timer);\n const responseText = await safeReadBody(res);\n const durationMs = Date.now() - start;\n if (res.ok) {\n return { success: true, httpStatus: res.status, responseBody: responseText, durationMs };\n }\n const retriable = res.status === 408 || res.status === 429 || res.status >= 500;\n return {\n success: false,\n retriable,\n httpStatus: res.status,\n responseBody: responseText,\n error: `HTTP ${res.status}`,\n durationMs,\n };\n } catch (err: unknown) {\n clearTimeout(timer);\n const durationMs = Date.now() - start;\n const e = err as { name?: string; message?: string };\n const error = e?.name === 'AbortError' ? `timeout after ${timeoutMs}ms` : e?.message ?? String(err);\n return { success: false, retriable: true, error, durationMs };\n }\n}\n\nasync function safeReadBody(res: { text(): Promise<string> }): Promise<string | undefined> {\n try {\n const text = await res.text();\n return text.length > RESPONSE_BODY_CAP ? text.slice(0, RESPONSE_BODY_CAP) : text;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Stripe-style retry schedule. Returns the next delay (ms) given how many\n * attempts have already happened, or `null` once the budget is exhausted.\n *\n * 1→~1s · 2→~10s · 3→~1m · 4→~10m · 5→~1h · 6→~6h · 7→~24h · 8+→dead\n *\n * Each delay is multiplied by jitter ∈ [0.8, 1.2).\n */\nexport function nextHttpRetryDelayMs(\n attemptsSoFar: number,\n rng: () => number = Math.random,\n): number | null {\n const SCHEDULE = [1_000, 10_000, 60_000, 600_000, 3_600_000, 21_600_000, 86_400_000];\n if (attemptsSoFar < 1 || attemptsSoFar > SCHEDULE.length) return null;\n const base = SCHEDULE[attemptsSoFar - 1];\n const jitter = 0.8 + rng() * 0.4;\n return Math.floor(base * jitter);\n}\n\n/**\n * Compose an {@link HttpAckResult} from an outcome, applying the retry schedule\n * on retriable failures.\n */\nexport function classifyAttempt(\n outcome: HttpAttemptOutcome,\n attemptsSoFar: number,\n now: number = Date.now(),\n rng?: () => number,\n): HttpAckResult {\n if (outcome.success) return outcome;\n if (!outcome.retriable) {\n return {\n success: false,\n httpStatus: outcome.httpStatus,\n responseBody: outcome.responseBody,\n error: outcome.error,\n durationMs: outcome.durationMs,\n dead: true,\n };\n }\n const delay = nextHttpRetryDelayMs(attemptsSoFar + 1, rng);\n if (delay === null) {\n return {\n success: false,\n httpStatus: outcome.httpStatus,\n responseBody: outcome.responseBody,\n error: outcome.error,\n durationMs: outcome.durationMs,\n dead: true,\n };\n }\n return {\n success: false,\n httpStatus: outcome.httpStatus,\n responseBody: outcome.responseBody,\n error: outcome.error,\n durationMs: outcome.durationMs,\n nextRetryAt: now + delay,\n };\n}\n\n/** Generate a fresh delivery id (UUID v4). Exposed for tests. */\nexport function newDeliveryId(): string {\n return randomUUID();\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { DispatchCluster, DispatchLockHandle } from './dispatcher.js';\nimport { classifyAttempt, sendOnce, type FetchImpl } from './http-sender.js';\nimport type { HttpDelivery, IHttpOutbox } from './http-outbox.js';\n\n/**\n * HttpDispatcher (ADR-0018 M3) — drains the generic outbound-HTTP outbox\n * (`sys_http_delivery`) and POSTs each row, retrying with backoff and\n * dead-lettering once the budget is exhausted.\n *\n * Structurally identical to `NotificationDispatcher` / `WebhookDispatcher`: an\n * interval loop walks `partitionCount` partitions, each guarded by a\n * per-partition cluster lock; within a held partition it claims a batch\n * (`pending → in_flight`), sends, and acks. Partition affinity is on the\n * delivery's `refId`, preserving in-order delivery per source anchor.\n *\n * At-least-once: if the POST succeeds but the ack write fails, the row reverts\n * to pending after the claim TTL and is re-posted. Receivers MUST be idempotent\n * on the `X-Objectstack-Delivery` (== row id) header.\n */\n\nconst SINGLE_NODE_CLUSTER: DispatchCluster = {\n lock: {\n async acquire() {\n return { release() {}, isHeld: () => true, renew() {} };\n },\n },\n};\n\nexport interface HttpDispatcherLogger {\n warn: (msg: string, meta?: any) => void;\n info?: (msg: string, meta?: any) => void;\n}\n\nexport interface HttpDispatcherOptions {\n /** Stable id identifying this dispatcher node. */\n nodeId: string;\n /** Outbox backend. */\n outbox: IHttpOutbox;\n /** Cross-node coordination. Defaults to a single-node always-grant lock. */\n cluster?: DispatchCluster;\n /** Partitions to split work across (must match the outbox's). Default 8. */\n partitionCount?: number;\n /** Max rows to claim from each partition per tick. Default 32. */\n batchSize?: number;\n /** Tick interval in ms. Default 500. */\n intervalMs?: number;\n /** Per-partition lock TTL. Default = 5 × intervalMs. */\n lockTtlMs?: number;\n /** Visibility timeout for claimed rows. Default = 2 × lockTtlMs. */\n claimTtlMs?: number;\n /** Override `globalThis.fetch` (tests). */\n fetchImpl?: FetchImpl;\n /** RNG override for the retry-jitter schedule (tests). */\n rng?: () => number;\n /** Injectable clock (ms) for deterministic tests. Defaults to Date.now. */\n now?: () => number;\n /** Logger callback (optional). */\n logger?: HttpDispatcherLogger;\n /** Hook fired after every attempt — observability hook. */\n onAttempt?: (delivery: HttpDelivery, success: boolean) => void;\n}\n\nexport class HttpDispatcher {\n private readonly opts: Required<\n Omit<HttpDispatcherOptions, 'fetchImpl' | 'rng' | 'logger' | 'onAttempt' | 'cluster' | 'now'>\n > &\n Pick<HttpDispatcherOptions, 'fetchImpl' | 'rng' | 'logger' | 'onAttempt' | 'now'> & {\n cluster: DispatchCluster;\n };\n private timer: ReturnType<typeof setInterval> | undefined;\n private running = false;\n private inflightTick: Promise<void> | undefined;\n\n constructor(options: HttpDispatcherOptions) {\n const intervalMs = options.intervalMs ?? 500;\n const lockTtlMs = options.lockTtlMs ?? intervalMs * 5;\n this.opts = {\n nodeId: options.nodeId,\n outbox: options.outbox,\n cluster: options.cluster ?? SINGLE_NODE_CLUSTER,\n partitionCount: options.partitionCount ?? 8,\n batchSize: options.batchSize ?? 32,\n intervalMs,\n lockTtlMs,\n claimTtlMs: options.claimTtlMs ?? lockTtlMs * 2,\n fetchImpl: options.fetchImpl,\n rng: options.rng,\n now: options.now,\n logger: options.logger,\n onAttempt: options.onAttempt,\n };\n }\n\n /** Begin the periodic loop. Safe to call once; subsequent calls are no-ops. */\n start(): void {\n if (this.running) return;\n this.running = true;\n this.scheduleTick();\n this.timer = setInterval(() => this.scheduleTick(), this.opts.intervalMs);\n this.timer.unref?.();\n }\n\n /** Stop the loop and wait for the in-flight tick to drain. */\n async stop(): Promise<void> {\n if (!this.running) return;\n this.running = false;\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = undefined;\n }\n if (this.inflightTick) {\n try {\n await this.inflightTick;\n } catch {\n /* swallow — already logged */\n }\n }\n }\n\n /** Run one full tick (all partitions). Exposed for deterministic tests. */\n async tick(): Promise<void> {\n await this.runTick();\n }\n\n private scheduleTick(): void {\n if (this.inflightTick) return;\n this.inflightTick = this.runTick()\n .catch((err) => {\n this.opts.logger?.warn?.('http-dispatcher: tick failed', {\n nodeId: this.opts.nodeId,\n error: (err as Error)?.message ?? String(err),\n });\n })\n .finally(() => {\n this.inflightTick = undefined;\n });\n }\n\n private async runTick(): Promise<void> {\n const partitionCount = this.opts.partitionCount;\n const offset = stableNodeOffset(this.opts.nodeId, partitionCount);\n for (let step = 0; step < partitionCount; step++) {\n const i = (offset + step) % partitionCount;\n await this.runPartition(i);\n }\n }\n\n private async runPartition(index: number): Promise<void> {\n const key = `http.dispatcher.partition.${index}`;\n const handle: DispatchLockHandle | null = await this.opts.cluster.lock.acquire(key, {\n ttlMs: this.opts.lockTtlMs,\n waitMs: 0,\n });\n if (!handle) return;\n\n try {\n const claimed = await this.opts.outbox.claim({\n nodeId: this.opts.nodeId,\n limit: this.opts.batchSize,\n partition: { index, count: this.opts.partitionCount },\n claimTtlMs: this.opts.claimTtlMs,\n now: this.opts.now?.(),\n });\n if (claimed.length === 0) return;\n await handle.renew?.(this.opts.lockTtlMs);\n for (const row of claimed) {\n if (handle.isHeld && !handle.isHeld()) break;\n await this.processRow(row);\n }\n } finally {\n await handle.release();\n }\n }\n\n private async processRow(row: HttpDelivery): Promise<void> {\n const fetchImpl = (this.opts.fetchImpl ?? (globalThis.fetch as unknown as FetchImpl)) as\n | FetchImpl\n | undefined;\n if (!fetchImpl) {\n this.opts.logger?.warn?.('http-dispatcher: no fetch impl available', { rowId: row.id });\n await this.opts.outbox.ack(row.id, {\n success: false,\n error: 'no fetch implementation',\n durationMs: 0,\n dead: true,\n });\n return;\n }\n const outcome = await sendOnce(row, fetchImpl);\n const result = classifyAttempt(outcome, row.attempts, this.opts.now?.() ?? Date.now(), this.opts.rng);\n await this.opts.outbox.ack(row.id, result);\n this.opts.onAttempt?.(row, result.success);\n }\n}\n\n/** Spread starting partition per node so nodes don't serialise on partition 0. */\nfunction stableNodeOffset(nodeId: string, partitionCount: number): number {\n let h = 0;\n for (let i = 0; i < nodeId.length; i++) {\n h = (h * 31 + nodeId.charCodeAt(i)) | 0;\n }\n return Math.abs(h) % partitionCount;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport { NOTIFICATION_EVENT_OBJECT } from './messaging-service.js';\nimport { DELIVERY_OBJECT } from './sql-outbox.js';\nimport { INBOX_OBJECT, RECEIPT_OBJECT } from './inbox-channel.js';\n\n/**\n * Default retention window for the notification pipeline, in days. Default-on as\n * of GA (launch-readiness.md P1-2): 90 days keeps a quarter of in-app history\n * for the bell / audit while bounding `sys_notification` (+ delivery / inbox /\n * receipt) growth. Operators override via `MessagingServicePlugin` options;\n * `0` disables pruning entirely.\n */\nexport const DEFAULT_NOTIFICATION_RETENTION_DAYS = 90;\n\n/** Minimal logger surface (matches the channel/service context). */\ninterface RetentionLogger {\n info(msg: string): void;\n warn(msg: string): void;\n}\n\n/**\n * One object the sweeper prunes, plus the field that carries its age. Every\n * target's `created_at` is a builtin audit column — a native `TIMESTAMP` on\n * Postgres/MySQL — so the cutoff is always an ISO-8601 string. (An earlier\n * version passed an epoch-ms number for the delivery outbox; that compared a\n * bigint to a timestamp column and Postgres rejected it with \"date/time field\n * value out of range\". SQLite's lenient column affinity hid the bug.) The\n * driver coerces the ISO comparand to the column's storage form per dialect.\n */\nexport interface RetentionTarget {\n readonly object: string;\n readonly tsField: string;\n}\n\n/**\n * Default sweep set, ordered leaf-first (materializations/receipts/deliveries\n * before the event) so the log reads top-down even though there are no enforced\n * FKs. A notification ages out **wholesale** — event + delivery + materialization\n * + receipt past the cutoff are all removed — keeping the model consistent (no\n * dangling `notification_id`) and the bell (which only shows recent rows)\n * unaffected.\n */\nexport const DEFAULT_RETENTION_TARGETS: readonly RetentionTarget[] = [\n { object: RECEIPT_OBJECT, tsField: 'created_at' },\n { object: INBOX_OBJECT, tsField: 'created_at' },\n { object: DELIVERY_OBJECT, tsField: 'created_at' },\n { object: NOTIFICATION_EVENT_OBJECT, tsField: 'created_at' },\n];\n\nexport interface NotificationRetentionOptions {\n /** Resolve the data engine; `undefined` ⇒ prune is a no-op. */\n getData(): IDataEngine | undefined;\n logger: RetentionLogger;\n /** Override the swept objects (tests / custom deployments). */\n targets?: readonly RetentionTarget[];\n /** Clock injection for deterministic tests. Defaults to `Date.now()`. */\n now?(): number;\n}\n\n/** Per-object prune outcome. `deleted` is `undefined` when the driver doesn't report a count. */\nexport interface PruneOutcome {\n object: string;\n deleted?: number;\n error?: string;\n}\n\n/**\n * Retention sweeper for the notification pipeline (ADR-0030 hardening).\n *\n * Every `emit()` writes a `sys_notification` event row (plus delivery /\n * materialization / receipt rows), so a high-frequency periodic flow grows the\n * tables unbounded. {@link prune} deletes everything older than a cutoff across\n * all {@link RetentionTarget}s in one bulk `delete` per object, under a system\n * context (cross-tenant: retention is an operator policy). Each target is\n * isolated — one object's failure is logged and the sweep continues.\n *\n * Retention is **opt-in**: the plugin runs this only when `retentionDays` is\n * configured. The mechanism lives here so it's testable in isolation.\n */\nexport class NotificationRetention {\n private readonly now: () => number;\n private readonly targets: readonly RetentionTarget[];\n\n constructor(private readonly opts: NotificationRetentionOptions) {\n this.now = opts.now ?? (() => Date.now());\n this.targets = opts.targets ?? DEFAULT_RETENTION_TARGETS;\n }\n\n /**\n * Delete pipeline rows older than `retentionDays`. Returns one outcome per\n * swept object. No-op (empty result) when no data engine is available or\n * `retentionDays` is not a positive number.\n */\n async prune(retentionDays: number): Promise<PruneOutcome[]> {\n const data = this.opts.getData();\n if (!data) {\n this.opts.logger.warn('[messaging] retention: no data engine; prune skipped');\n return [];\n }\n if (!(retentionDays > 0)) {\n this.opts.logger.warn(`[messaging] retention: invalid retentionDays=${retentionDays}; prune skipped`);\n return [];\n }\n\n const cutoffIso = new Date(this.now() - retentionDays * 86_400_000).toISOString();\n const outcomes: PruneOutcome[] = [];\n\n for (const t of this.targets) {\n try {\n const res = await data.delete(t.object, {\n // ISO-8601 cutoff for every target: `created_at` is a native\n // timestamp column, which rejects a bare epoch-ms number on\n // Postgres. The driver coerces this per dialect on the way down.\n where: { [t.tsField]: { $lt: cutoffIso } },\n multi: true,\n // System context: retention is an operator policy that spans\n // tenants, so it must not be scoped by the caller's RLS.\n context: { isSystem: true },\n } as any);\n const deleted = countDeleted(res);\n outcomes.push({ object: t.object, deleted });\n if (deleted === undefined || deleted > 0) {\n this.opts.logger.info(\n `[messaging] retention: pruned ${deleted ?? '?'} ${t.object} rows older than ${cutoffIso}`,\n );\n }\n } catch (err) {\n const msg = (err as Error)?.message ?? String(err);\n this.opts.logger.warn(`[messaging] retention: prune of ${t.object} failed (${msg}); continuing`);\n outcomes.push({ object: t.object, error: msg });\n }\n }\n return outcomes;\n }\n}\n\n/** Best-effort row-count extraction from a driver's delete result. */\nfunction countDeleted(res: unknown): number | undefined {\n if (typeof res === 'number') return res;\n if (Array.isArray(res)) return res.length;\n if (res && typeof res === 'object') {\n const r = res as Record<string, unknown>;\n for (const k of ['deletedCount', 'deleted', 'count', 'affected', 'affectedRows']) {\n if (typeof r[k] === 'number') return r[k] as number;\n }\n }\n return undefined;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\n\n/** The object notification templates live in. */\nexport const TEMPLATE_OBJECT = 'sys_notification_template';\n\n/** Default locale used when a delivery carries none. */\nexport const DEFAULT_LOCALE = 'en';\n\n/** A loaded template row (the columns the renderer reads). */\nexport interface NotificationTemplateRow {\n subject?: string | null;\n body?: string | null;\n format?: 'markdown' | 'html' | 'text' | 'mjml' | null;\n}\n\n/** The rendered artifact a channel turns into its transport payload. */\nexport interface RenderedNotification {\n subject: string;\n /** Set when the template/format is HTML-ish (html/mjml). */\n html?: string;\n /** Set otherwise (markdown/text, or the no-template fallback). */\n text?: string;\n}\n\n/**\n * Render context: the event payload plus a few top-level conveniences so a\n * template can write `{{ title }}` as well as `{{ payload.title }}`.\n */\nexport interface RenderInput {\n topic: string;\n payload: Record<string, unknown>;\n /** Resolved title/body from the notification (fallback when no template). */\n title?: string;\n body?: string;\n}\n\nconst TOKEN = /\\{\\{\\s*([\\w.$]+)\\s*\\}\\}/g;\n\n/**\n * Declarative `{{ path.to.value }}` interpolation over a context object. No\n * logic, no conditionals — templates stay auditable metadata (a deliberate\n * low-code constraint). An unknown path renders to an empty string. This is the\n * single, linear-time substitution pass; it never evaluates template code.\n */\nexport function interpolate(template: string, context: Record<string, unknown>): string {\n if (!template) return '';\n return template.replace(TOKEN, (_m, path: string) => {\n const v = lookup(context, path);\n return v == null ? '' : String(v);\n });\n}\n\nfunction lookup(ctx: Record<string, unknown>, path: string): unknown {\n let cur: unknown = ctx;\n for (const key of path.split('.')) {\n if (cur == null || typeof cur !== 'object') return undefined;\n cur = (cur as Record<string, unknown>)[key];\n }\n return cur;\n}\n\n/**\n * Render a notification for a channel. With a template, `subject`/`body` are\n * interpolated and `body` is routed to `html` (html/mjml) or `text`\n * (markdown/text). With no template, fall back to the notification's\n * `title`/`body` (the P0/P1 behavior) as `subject`/`text`.\n */\nexport function renderNotification(\n template: NotificationTemplateRow | null | undefined,\n input: RenderInput,\n): RenderedNotification {\n const ctx: Record<string, unknown> = {\n ...input.payload,\n payload: input.payload,\n topic: input.topic,\n title: input.title ?? input.payload.title,\n body: input.body ?? input.payload.body,\n };\n\n if (template && (template.subject || template.body)) {\n const subject = interpolate(String(template.subject ?? ''), ctx) || String(ctx.title ?? input.topic);\n const renderedBody = interpolate(String(template.body ?? ''), ctx);\n const isHtml = template.format === 'html' || template.format === 'mjml';\n return isHtml ? { subject, html: renderedBody } : { subject, text: renderedBody };\n }\n\n // Generic fallback — no template for this (topic, channel, locale).\n return {\n subject: String(ctx.title ?? input.topic),\n text: String(ctx.body ?? ''),\n };\n}\n\nexport interface NotificationTemplateStoreOptions {\n getData(): IDataEngine | undefined;\n objectName?: string;\n}\n\n/**\n * Loads `sys_notification_template` rows by `(topic, channel, locale)`, with a\n * locale fallback (`en-US` → `en` → {@link DEFAULT_LOCALE}). Best-effort: no\n * data engine or a lookup error yields `null` (→ the renderer's generic\n * fallback), never a throw — a template outage must not block delivery.\n */\nexport class NotificationTemplateStore {\n private readonly objectName: string;\n constructor(private readonly opts: NotificationTemplateStoreOptions) {\n this.objectName = opts.objectName ?? TEMPLATE_OBJECT;\n }\n\n async load(topic: string, channel: string, locale?: string): Promise<NotificationTemplateRow | null> {\n const data = this.opts.getData();\n if (!data) return null;\n const candidates = localeCandidates(locale);\n for (const loc of candidates) {\n try {\n const row = await data.findOne(this.objectName, {\n where: { topic, channel, locale: loc, is_active: true },\n fields: ['subject', 'body', 'format'],\n });\n if (row) return row as NotificationTemplateRow;\n } catch {\n return null; // best-effort — fall back to generic rendering\n }\n }\n return null;\n }\n}\n\n/** `en-US` → ['en-US','en', DEFAULT_LOCALE]; dedups, keeps order. */\nfunction localeCandidates(locale?: string): string[] {\n const out: string[] = [];\n const push = (l?: string) => { if (l && !out.includes(l)) out.push(l); };\n push(locale);\n if (locale && locale.includes('-')) push(locale.split('-')[0]);\n push(DEFAULT_LOCALE);\n return out;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport type {\n Delivery,\n ErrorClass,\n MessagingChannel,\n MessagingChannelContext,\n SendResult,\n} from './channel.js';\nimport {\n NotificationTemplateStore,\n renderNotification,\n DEFAULT_LOCALE,\n} from './template-renderer.js';\n\n/** The user identity object a recipient id is resolved to an address against. */\nexport const USER_OBJECT = 'sys_user';\n\n/**\n * Structural view of the email service (`@objectstack/plugin-email`'s\n * `EmailService`), declared locally so service-messaging takes no runtime\n * dependency on it — the channel resolves whatever is registered under the\n * `email` service and sends through this shape (mirrors the `notify` node's\n * `MessagingServiceSurface` pattern).\n */\nexport interface EmailSenderSurface {\n send(input: {\n to: string | string[];\n subject: string;\n html?: string;\n text?: string;\n }): Promise<{ id?: string } | unknown>;\n}\n\nexport interface EmailChannelOptions {\n /** Resolve the email service; `undefined` ⇒ the channel no-ops (not installed). */\n getEmail(): EmailSenderSurface | undefined;\n /** Resolve the data engine (recipient address lookup). */\n getData(): IDataEngine | undefined;\n /** Template store for `(topic, 'email', locale)` rendering. */\n store: NotificationTemplateStore;\n /** User identity object override (default {@link USER_OBJECT}). */\n userObject?: string;\n /** Locale used when the delivery carries none (default {@link DEFAULT_LOCALE}). */\n defaultLocale?: string;\n}\n\nconst EMAIL_SHAPE = (s: string): boolean => {\n // Linear, non-backtracking \"looks like an email\" — same shape as the\n // recipient resolver's check (avoids the ReDoS-prone regex).\n if (!s || /\\s/.test(s)) return false;\n const at = s.indexOf('@');\n if (at <= 0 || at !== s.lastIndexOf('@') || at === s.length - 1) return false;\n const dot = s.slice(at + 1).indexOf('.');\n return dot > 0 && dot < s.length - at - 2;\n};\n\n/**\n * The `email` channel (ADR-0030 P3) — delivers a notification by email.\n *\n * It adds only the messaging semantics on top of the existing email transport\n * (ADR-0022 \"channel delegates transport to a sub-system\"): resolve the\n * recipient's address, render `(topic, 'email', locale)` from\n * `sys_notification_template` (fallback to `payload.title`/`body`), and hand the\n * subject/body to the `email` service. Retry/backoff/dead-letter come for free\n * from the P1 outbox dispatcher.\n *\n * Degrades like the inbox channel: no email service ⇒ logged no-op success\n * (capability not installed); a recipient with no resolvable address ⇒ a\n * reported failure (so the delivery row shows why).\n */\nexport function createEmailChannel(opts: EmailChannelOptions): MessagingChannel {\n const userObject = opts.userObject ?? USER_OBJECT;\n const defaultLocale = opts.defaultLocale ?? DEFAULT_LOCALE;\n\n async function resolveAddress(\n ctx: MessagingChannelContext,\n data: IDataEngine | undefined,\n recipient: string,\n ): Promise<string | undefined> {\n if (EMAIL_SHAPE(recipient)) return recipient; // already an address\n if (!data) return undefined;\n try {\n const user = await data.findOne(userObject, { where: { id: recipient }, fields: ['email'] });\n const email = user?.email;\n return typeof email === 'string' && EMAIL_SHAPE(email) ? email : undefined;\n } catch (err) {\n ctx.logger.warn(`[email] address lookup for '${recipient}' failed (${(err as Error).message})`);\n return undefined;\n }\n }\n\n return {\n id: 'email',\n\n async send(ctx: MessagingChannelContext, delivery: Delivery): Promise<SendResult> {\n const email = opts.getEmail();\n if (!email) {\n ctx.logger.warn(`[email] no email service registered; '${delivery.recipient}' not emailed`);\n return { ok: true }; // capability not installed — no-op, like inbox w/o data\n }\n\n const n = delivery.notification;\n const address = await resolveAddress(ctx, opts.getData(), delivery.recipient);\n if (!address) {\n return { ok: false, error: `no email address for recipient '${delivery.recipient}'` };\n }\n\n const payload = (n.payload ?? {}) as Record<string, unknown>;\n const locale = typeof payload.locale === 'string' ? payload.locale : defaultLocale;\n const template = await opts.store.load(n.topic ?? '', 'email', locale);\n const rendered = renderNotification(template, {\n topic: n.topic ?? '',\n payload,\n title: n.title,\n body: n.body,\n });\n\n try {\n const result: any = await email.send({\n to: address,\n subject: rendered.subject,\n ...(rendered.html !== undefined ? { html: rendered.html } : {}),\n ...(rendered.text !== undefined ? { text: rendered.text } : {}),\n });\n const id = result?.id;\n return { ok: true, externalId: id != null ? String(id) : undefined };\n } catch (err) {\n return { ok: false, error: `email send failed: ${(err as Error).message}` };\n }\n },\n\n classifyError(_err: unknown): ErrorClass {\n return 'retryable';\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_inbox_message` — user-facing in-app notification rows (ADR-0012 §4, §11;\n * ADR-0030 Layer 5).\n *\n * Written by the always-on `inbox` messaging channel, one row per\n * `(notification, recipient)`, as the **materialization** of an L2\n * `sys_notification` event delivered to the in-app channel. The Console bell\n * pulls these (ADR-0030 re-points the bell here from `sys_notification`);\n * `service-realtime` decides when to ping an online user. Belongs to\n * `service-messaging` per the \"protocol + service ownership\" pattern.\n *\n * Read-state is **not** stored here — it lives in `sys_notification_receipt`\n * (ADR-0030), so cross-channel read semantics stay reachable. `notification_id`\n * links back to the event; `delivery_id` links to the outbox row (P1).\n */\nexport const InboxMessage = ObjectSchema.create({\n name: 'sys_inbox_message',\n label: 'Inbox Message',\n pluralLabel: 'Inbox Messages',\n icon: 'inbox',\n description: 'User-facing in-app notification rows materialized by the inbox messaging channel.',\n titleFormat: '{title}',\n compactLayout: ['title', 'user_id', 'severity', 'created_at'],\n\n listViews: {\n mine: {\n type: 'grid',\n name: 'mine',\n label: 'Notifications',\n data: { provider: 'object', object: 'sys_inbox_message' },\n columns: ['title', 'topic', 'severity', 'created_at'],\n filter: [{ field: 'user_id', operator: 'equals', value: '{current_user_id}' }],\n sort: [{ field: 'created_at', order: 'desc' }],\n pagination: { pageSize: 50 },\n emptyState: { title: 'Inbox zero', message: 'No notifications.' },\n },\n },\n\n fields: {\n id: Field.text({\n label: 'Inbox Message ID',\n required: true,\n readonly: true,\n }),\n\n user_id: Field.text({\n label: 'Recipient User',\n required: true,\n searchable: true,\n }),\n\n notification_id: Field.text({\n label: 'Notification Event',\n searchable: true,\n description: 'FK → sys_notification (the L2 event this row materializes)',\n }),\n\n delivery_id: Field.text({\n label: 'Delivery',\n description: 'FK → sys_notification_delivery (outbox row); null until P1',\n }),\n\n topic: Field.text({\n label: 'Topic',\n searchable: true,\n }),\n\n title: Field.text({\n label: 'Title',\n required: true,\n }),\n\n body_md: Field.markdown({\n label: 'Body',\n }),\n\n severity: Field.select({\n label: 'Severity',\n options: [\n { label: 'Info', value: 'info' },\n { label: 'Warning', value: 'warning' },\n { label: 'Critical', value: 'critical' },\n ],\n }),\n\n action_url: Field.text({\n label: 'Action URL',\n }),\n\n created_at: Field.datetime({\n label: 'Created At',\n readonly: true,\n }),\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_notification_receipt` — per-recipient × channel delivery receipt\n * (ADR-0030 Layer 5).\n *\n * The single source of truth for **read-state**. When a channel materializes a\n * delivery it writes a `delivered` receipt; the UI flips it to `read` /\n * `clicked` / `dismissed`. Keeping read-state here (rather than on\n * `sys_inbox_message`) makes cross-channel semantics reachable later — e.g.\n * \"clicked the email → mark the inbox row read\" is a receipt update, not a\n * second source of truth.\n *\n * Keyed by `(notification_id, user_id, channel)`. `delivery_id` links to the\n * `sys_notification_delivery` outbox row once that lands (P1); it is nullable\n * in P0 where the inbox channel dispatches inline.\n *\n * Belongs to `service-messaging` (the owner of the materialization channels).\n */\nexport const NotificationReceipt = ObjectSchema.create({\n name: 'sys_notification_receipt',\n label: 'Notification Receipt',\n pluralLabel: 'Notification Receipts',\n icon: 'check-check',\n isSystem: true,\n managedBy: 'system',\n description: 'Per-recipient × channel receipt; the source of truth for notification read-state.',\n titleFormat: '{state}',\n compactLayout: ['notification_id', 'user_id', 'channel', 'state', 'at'],\n\n fields: {\n id: Field.text({\n label: 'Receipt ID',\n required: true,\n readonly: true,\n }),\n\n notification_id: Field.text({\n label: 'Notification Event',\n required: true,\n searchable: true,\n description: 'FK → sys_notification (L2 event)',\n }),\n\n delivery_id: Field.text({\n label: 'Delivery',\n required: false,\n description: 'FK → sys_notification_delivery (outbox row); null until P1',\n }),\n\n user_id: Field.text({\n label: 'Recipient User',\n required: true,\n searchable: true,\n }),\n\n channel: Field.text({\n label: 'Channel',\n required: true,\n description: 'Channel id this receipt is for (inbox / email / push / …)',\n }),\n\n state: Field.select(['delivered', 'read', 'clicked', 'dismissed'], {\n label: 'State',\n required: true,\n defaultValue: 'delivered',\n }),\n\n at: Field.datetime({\n label: 'At',\n required: false,\n description: 'When the receipt reached its current state',\n }),\n\n created_at: Field.datetime({\n label: 'Created At',\n readonly: true,\n }),\n },\n\n indexes: [\n { fields: ['notification_id', 'user_id', 'channel'], unique: true },\n { fields: ['user_id', 'state'] },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_notification_delivery` — the durable outbox (ADR-0030 Layer 4).\n *\n * One row per `(event × recipient × channel)`. The spine of reliable delivery:\n * `emit()` writes rows in `pending`; the `NotificationDispatcher` claims them\n * (`pending → in_flight`), sends via the channel, and acks the outcome\n * (`success` / back to `pending` with a `next_attempt_at` for retry / `dead`\n * once the retry budget is exhausted). Mirrors `sys_webhook_delivery`.\n *\n * `payload` snapshots the rendered notification content at enqueue time so a\n * later edit of the L2 event can't rewrite an in-flight delivery (and so the\n * dispatcher needs no second read to send). Scheduling fields (`claimed_at`,\n * `next_attempt_at`, `last_attempted_at`) are epoch ms; the builtin\n * `created_at` / `updated_at` audit columns are native timestamps.\n */\nexport const NotificationDelivery = ObjectSchema.create({\n name: 'sys_notification_delivery',\n label: 'Notification Delivery',\n pluralLabel: 'Notification Deliveries',\n icon: 'send',\n isSystem: true,\n managedBy: 'system',\n description: 'Durable per-recipient × channel delivery outbox (ADR-0030 Layer 4).',\n titleFormat: '{channel} → {recipient_id}',\n compactLayout: ['notification_id', 'recipient_id', 'channel', 'status', 'attempts'],\n\n fields: {\n id: Field.text({ label: 'Delivery ID', required: true, readonly: true }),\n\n notification_id: Field.text({\n label: 'Notification Event',\n required: true,\n searchable: true,\n description: 'FK → sys_notification (L2 event)',\n }),\n recipient_id: Field.text({ label: 'Recipient User', required: true, searchable: true }),\n channel: Field.text({ label: 'Channel', required: true }),\n topic: Field.text({ label: 'Topic', searchable: true }),\n\n // P3b-2 digest: when the recipient's preference batches this channel\n // (`digest: daily|weekly`), the row enqueues deferred to the next window\n // and carries `${recipient}|${channel}|${window}` here. The dispatcher's\n // digest pass collapses all same-key rows into ONE rendered message at\n // window time. Null ⇒ an ordinary (immediate / quiet-hours) delivery.\n digest_key: Field.text({ label: 'Digest Key', searchable: true,\n description: 'recipient|channel|window grouping key for batched (digest) deliveries; null for normal sends.' }),\n\n payload: Field.json({\n label: 'Payload',\n description: 'Snapshot of the rendered notification content for dispatch.',\n }),\n\n status: Field.select(['pending', 'in_flight', 'success', 'failed', 'dead', 'suppressed'], {\n label: 'Status',\n required: true,\n defaultValue: 'pending',\n }),\n\n attempts: Field.number({ label: 'Attempts', defaultValue: 0 }),\n partition_key: Field.number({ label: 'Partition Key', defaultValue: 0 }),\n\n claimed_by: Field.text({ label: 'Claimed By', description: 'Node id while in_flight' }),\n claimed_at: Field.number({ label: 'Claimed At (ms)' }),\n next_attempt_at: Field.number({ label: 'Next Attempt At (ms)' }),\n last_attempted_at: Field.number({ label: 'Last Attempted At (ms)' }),\n error: Field.textarea({ label: 'Error' }),\n\n // Builtin audit columns: the SQL driver provisions `created_at` /\n // `updated_at` as native TIMESTAMP columns (Postgres/MySQL), so they are\n // declared `datetime` and written as `Date`s — a bare epoch-ms number is\n // rejected by a real timestamp column. See SqlNotificationOutbox.\n created_at: Field.datetime({ label: 'Created At', readonly: true }),\n updated_at: Field.datetime({ label: 'Updated At' }),\n },\n\n indexes: [\n // Dedup: one delivery per (event, recipient, channel).\n { fields: ['notification_id', 'recipient_id', 'channel'], unique: true },\n // The hot claim query.\n { fields: ['status', 'partition_key', 'next_attempt_at'] },\n // Stale-in_flight reaper.\n { fields: ['status', 'claimed_at'] },\n { fields: ['notification_id'] },\n // P3b-2: the digest collapse pass — claim due batched rows by group.\n { fields: ['digest_key', 'status', 'next_attempt_at'] },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_notification_preference` — per-user × topic × channel delivery toggle\n * (ADR-0030 Layer 3).\n *\n * The mute/allow matrix the preference filter consults before fan-out. A row\n * declares whether `user_id` wants `topic` on `channel`. Resolution is\n * most-specific-wins with wildcards:\n *\n * (user, topic, channel) → (user, topic, *) → (user, *, channel) →\n * (user, *, *) → ('*', topic, channel) → … → ('*', '*', '*') → default ON\n *\n * `user_id = '*'` rows are the **admin global default**; a real-user row\n * **overrides** it. `topic = '*'` / `channel = '*'` are wildcards. Mandatory\n * topics (configured on the service) bypass this object entirely.\n *\n * Belongs to `service-messaging` (owner of the delivery pipeline).\n */\nexport const NotificationPreference = ObjectSchema.create({\n name: 'sys_notification_preference',\n label: 'Notification Preference',\n pluralLabel: 'Notification Preferences',\n icon: 'bell-ring',\n isSystem: true,\n managedBy: 'system',\n description: 'Per-user × topic × channel notification toggle (mute/allow), with admin-global defaults.',\n titleFormat: '{user_id} · {topic} · {channel}',\n compactLayout: ['user_id', 'topic', 'channel', 'enabled', 'digest'],\n\n fields: {\n id: Field.text({ label: 'Preference ID', required: true, readonly: true }),\n\n user_id: Field.text({\n label: 'User',\n required: true,\n searchable: true,\n description: \"Recipient user id, or '*' for the admin-global default.\",\n }),\n\n topic: Field.text({\n label: 'Topic',\n required: true,\n searchable: true,\n defaultValue: '*',\n description: \"Notification topic, or '*' for all topics.\",\n }),\n\n channel: Field.text({\n label: 'Channel',\n required: true,\n defaultValue: '*',\n description: \"Channel id (inbox/email/push/…), or '*' for all channels.\",\n }),\n\n enabled: Field.boolean({\n label: 'Enabled',\n defaultValue: true,\n description: 'When false, this (user, topic, channel) is muted.',\n }),\n\n digest: Field.select(['none', 'daily', 'weekly'], {\n label: 'Digest',\n required: false,\n defaultValue: 'none',\n description: 'Batch cadence (P3 digest middleware).',\n }),\n\n quiet_hours: Field.json({\n label: 'Quiet Hours',\n required: false,\n description: 'Optional { tz, start, end } window (P3 quiet-hours middleware).',\n }),\n\n created_at: Field.datetime({ label: 'Created At', readonly: true }),\n updated_at: Field.datetime({ label: 'Updated At', required: false }),\n },\n\n indexes: [\n { fields: ['user_id', 'topic', 'channel'], unique: true },\n { fields: ['topic'] },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_notification_subscription` — who is subscribed to a topic (ADR-0030\n * Layer 3).\n *\n * Declares standing interest in a `topic` by a `principal` (`role:x`, `team:x`,\n * `user:id`, or a bare user id). Where a producer emits with `audience:\n * 'subscribers'` (or no explicit audience), the resolver expands the topic's\n * subscriptions into recipients — the opt-in counterpart to the explicit\n * audience most producers pass today.\n *\n * Distinct from `sys_notification_preference`: a subscription says \"include me\n * for this topic\"; a preference says \"but mute it on this channel\".\n *\n * Belongs to `service-messaging`.\n */\nexport const NotificationSubscription = ObjectSchema.create({\n name: 'sys_notification_subscription',\n label: 'Notification Subscription',\n pluralLabel: 'Notification Subscriptions',\n icon: 'rss',\n isSystem: true,\n managedBy: 'system',\n description: 'Standing subscription of a principal (role/team/user) to a notification topic.',\n titleFormat: '{principal} · {topic}',\n compactLayout: ['topic', 'principal', 'enabled', 'created_at'],\n\n fields: {\n id: Field.text({ label: 'Subscription ID', required: true, readonly: true }),\n\n topic: Field.text({\n label: 'Topic',\n required: true,\n searchable: true,\n description: 'Notification topic this principal subscribes to.',\n }),\n\n principal: Field.text({\n label: 'Principal',\n required: true,\n searchable: true,\n description: \"Subscriber selector: 'role:x' | 'team:x' | 'user:id' | bare user id.\",\n }),\n\n enabled: Field.boolean({\n label: 'Enabled',\n defaultValue: true,\n description: 'When false, the subscription is inactive.',\n }),\n\n created_at: Field.datetime({ label: 'Created At', readonly: true }),\n },\n\n indexes: [\n { fields: ['topic', 'principal'], unique: true },\n { fields: ['topic'] },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_notification_template` — channel-agnostic render template (ADR-0030\n * cross-cutting / P3).\n *\n * One row per `(topic, channel, locale)` carrying the `subject`/`body` a channel\n * renders from the event `payload` (declarative `{{ payload.x }}` interpolation\n * — see `template-renderer.ts`). `format` tells the channel how to treat `body`\n * (markdown/html/text). When no template matches, channels fall back to\n * `payload.title` / `payload.body` (the P0/P1 behavior), so templates are purely\n * additive.\n *\n * Studio-configurable (contributed to the Setup → Configuration nav). Belongs to\n * `service-messaging`.\n */\nexport const NotificationTemplate = ObjectSchema.create({\n name: 'sys_notification_template',\n label: 'Notification Template',\n pluralLabel: 'Notification Templates',\n icon: 'file-text',\n isSystem: true,\n managedBy: 'system',\n description: 'Per (topic × channel × locale) render template for notifications.',\n titleFormat: '{topic} · {channel} · {locale}',\n compactLayout: ['topic', 'channel', 'locale', 'is_active'],\n\n fields: {\n id: Field.text({ label: 'Template ID', required: true, readonly: true }),\n\n topic: Field.text({ label: 'Topic', required: true, searchable: true }),\n\n channel: Field.text({\n label: 'Channel',\n required: true,\n defaultValue: 'email',\n description: 'Channel id this template renders for (email/inbox/push/…).',\n }),\n\n locale: Field.text({\n label: 'Locale',\n required: true,\n defaultValue: 'en',\n description: \"BCP-47 locale, e.g. 'en' / 'en-US' / 'zh-CN'.\",\n }),\n\n version: Field.number({\n label: 'Version',\n required: false,\n defaultValue: 1,\n }),\n\n subject: Field.text({\n label: 'Subject / Title',\n required: false,\n description: 'Rendered into the email subject / inbox title. Supports {{ payload.x }}.',\n }),\n\n body: Field.markdown({\n label: 'Body',\n required: false,\n description: 'Template body. Supports {{ payload.x }}. Interpreted per `format`.',\n }),\n\n format: Field.select(['markdown', 'html', 'text', 'mjml'], {\n label: 'Body Format',\n required: false,\n defaultValue: 'markdown',\n }),\n\n is_active: Field.boolean({\n label: 'Active',\n defaultValue: true,\n description: 'Only active templates are selected at render time.',\n }),\n\n created_at: Field.datetime({ label: 'Created At', readonly: true }),\n updated_at: Field.datetime({ label: 'Updated At', required: false }),\n },\n\n indexes: [\n { fields: ['topic', 'channel', 'locale'] },\n { fields: ['topic'] },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { randomUUID } from 'node:crypto';\nimport type {\n AckResult,\n ClaimOptions,\n DeliveryStatus,\n EnqueueDeliveryInput,\n INotificationOutbox,\n NotificationDeliveryRecord,\n} from './outbox.js';\nimport { hashPartition } from './backoff.js';\n\n/**\n * In-memory {@link INotificationOutbox} — the test/minimal-stack backend.\n * Single-process, so `claim()` is trivially atomic (no DB round-trips). Same\n * semantics as {@link SqlNotificationOutbox} for dedup, reaping, and acks.\n */\nexport class MemoryNotificationOutbox implements INotificationOutbox {\n private readonly rows = new Map<string, NotificationDeliveryRecord>();\n\n constructor(\n private readonly partitionCount = 8,\n /** Injectable clock (ms) for deterministic tests. Defaults to Date.now. */\n private readonly clock: () => number = () => Date.now(),\n ) {}\n\n async enqueue(input: EnqueueDeliveryInput): Promise<string> {\n for (const r of this.rows.values()) {\n if (\n r.notificationId === input.notificationId &&\n r.recipientId === input.recipientId &&\n r.channel === input.channel\n ) {\n return r.id; // dedup\n }\n }\n const id = randomUUID();\n const now = this.clock();\n this.rows.set(id, {\n id,\n notificationId: input.notificationId,\n recipientId: input.recipientId,\n channel: input.channel,\n topic: input.topic,\n payload: input.payload ?? {},\n organizationId: input.organizationId,\n // Digest rows partition by their group key so a window's rows land in\n // one partition and a single node collapses them under its lock.\n partitionKey: hashPartition(input.digestKey ?? input.notificationId, this.partitionCount),\n status: 'pending',\n attempts: 0,\n // Deferred dispatch (quiet-hours / digest, P3): claim() skips pending\n // rows whose nextAttemptAt is still in the future.\n nextAttemptAt: input.notBefore,\n digestKey: input.digestKey,\n createdAt: now,\n updatedAt: now,\n });\n return id;\n }\n\n async claim(opts: ClaimOptions): Promise<NotificationDeliveryRecord[]> {\n const now = opts.now ?? this.clock();\n // Reap stale in_flight.\n for (const r of this.rows.values()) {\n if (r.status === 'in_flight' && (r.claimedAt ?? 0) < now - opts.claimTtlMs) {\n r.status = 'pending';\n r.claimedBy = undefined;\n r.claimedAt = undefined;\n r.updatedAt = now;\n }\n }\n const out: NotificationDeliveryRecord[] = [];\n for (const r of this.rows.values()) {\n if (out.length >= opts.limit) break;\n if (r.status !== 'pending') continue;\n if (r.digestKey != null) continue; // batched rows drain via claimDigest\n if (opts.partition && r.partitionKey !== opts.partition.index) continue;\n if (r.nextAttemptAt != null && r.nextAttemptAt > now) continue;\n r.status = 'in_flight';\n r.claimedBy = opts.nodeId;\n r.claimedAt = now;\n r.updatedAt = now;\n out.push({ ...r });\n }\n return out;\n }\n\n async claimDigest(opts: ClaimOptions): Promise<NotificationDeliveryRecord[]> {\n const now = opts.now ?? this.clock();\n // Reap stale in_flight (same as claim).\n for (const r of this.rows.values()) {\n if (r.status === 'in_flight' && (r.claimedAt ?? 0) < now - opts.claimTtlMs) {\n r.status = 'pending';\n r.claimedBy = undefined;\n r.claimedAt = undefined;\n r.updatedAt = now;\n }\n }\n // Claim every DUE batched row in the partition — a window must be taken\n // whole, so `limit` does not truncate a group here.\n const out: NotificationDeliveryRecord[] = [];\n for (const r of this.rows.values()) {\n if (r.status !== 'pending') continue;\n if (r.digestKey == null) continue;\n if (opts.partition && r.partitionKey !== opts.partition.index) continue;\n if (r.nextAttemptAt != null && r.nextAttemptAt > now) continue;\n r.status = 'in_flight';\n r.claimedBy = opts.nodeId;\n r.claimedAt = now;\n r.updatedAt = now;\n out.push({ ...r });\n }\n return out;\n }\n\n async ack(id: string, result: AckResult): Promise<void> {\n const r = this.rows.get(id);\n if (!r) return;\n const now = this.clock();\n r.attempts += 1;\n r.lastAttemptedAt = now;\n r.claimedBy = undefined;\n r.claimedAt = undefined;\n r.updatedAt = now;\n if (result.success) {\n r.status = 'success';\n r.nextAttemptAt = undefined;\n r.error = undefined;\n } else if (result.suppressed) {\n r.status = 'suppressed';\n r.error = result.error;\n } else if (result.dead) {\n r.status = 'dead';\n r.error = result.error;\n } else {\n r.status = 'pending';\n r.nextAttemptAt = result.nextAttemptAt;\n r.error = result.error;\n }\n }\n\n async list(filter?: { status?: DeliveryStatus; notificationId?: string }): Promise<NotificationDeliveryRecord[]> {\n let rows = [...this.rows.values()];\n if (filter?.status) rows = rows.filter((r) => r.status === filter.status);\n if (filter?.notificationId) rows = rows.filter((r) => r.notificationId === filter.notificationId);\n return rows.map((r) => ({ ...r }));\n }\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { randomUUID } from 'node:crypto';\nimport { hashPartition } from './backoff.js';\nimport {\n HttpRedeliverError,\n type EnqueueHttpInput,\n type HttpAckResult,\n type HttpClaimOptions,\n type HttpDelivery,\n type HttpDeliveryStatus,\n type IHttpOutbox,\n} from './http-outbox.js';\n\n/**\n * In-memory {@link IHttpOutbox} for tests and single-process development.\n * Mirrors `MemoryWebhookOutbox`: atomic-claim semantics come for free from the\n * single-threaded event loop operating on one `Map`. Two instances do NOT share\n * state — pass the same instance to both dispatchers to simulate one DB.\n */\nexport class MemoryHttpOutbox implements IHttpOutbox {\n private readonly rows = new Map<string, HttpDelivery>();\n /** Dedup index keyed by `${source}::${dedupKey}` -> row id. */\n private readonly dedup = new Map<string, string>();\n\n async enqueue(input: EnqueueHttpInput): Promise<string> {\n const dedupKey = `${input.source}::${input.dedupKey}`;\n const existing = this.dedup.get(dedupKey);\n if (existing) return existing;\n\n const id = randomUUID();\n const now = Date.now();\n const row: HttpDelivery = {\n id,\n source: input.source,\n refId: input.refId,\n dedupKey: input.dedupKey,\n label: input.label,\n url: input.url,\n method: input.method ?? 'POST',\n headers: input.headers,\n signingSecret: input.signingSecret,\n timeoutMs: input.timeoutMs,\n payload: input.payload,\n status: 'pending',\n attempts: 0,\n createdAt: now,\n updatedAt: now,\n };\n this.rows.set(id, row);\n this.dedup.set(dedupKey, id);\n return id;\n }\n\n async claim(opts: HttpClaimOptions): Promise<HttpDelivery[]> {\n const now = opts.now ?? Date.now();\n const claimed: HttpDelivery[] = [];\n\n for (const row of this.rows.values()) {\n if (\n row.status === 'in_flight' &&\n row.claimedAt !== undefined &&\n now - row.claimedAt > opts.claimTtlMs\n ) {\n row.status = 'pending';\n row.claimedBy = undefined;\n row.claimedAt = undefined;\n row.updatedAt = now;\n }\n }\n\n for (const row of this.rows.values()) {\n if (claimed.length >= opts.limit) break;\n if (row.status !== 'pending') continue;\n if (row.nextRetryAt !== undefined && row.nextRetryAt > now) continue;\n if (opts.partition) {\n const p = hashPartition(row.refId, opts.partition.count);\n if (p !== opts.partition.index) continue;\n }\n row.status = 'in_flight';\n row.claimedBy = opts.nodeId;\n row.claimedAt = now;\n row.updatedAt = now;\n claimed.push({ ...row });\n }\n return claimed;\n }\n\n async ack(id: string, result: HttpAckResult): Promise<void> {\n const row = this.rows.get(id);\n if (!row) return;\n const now = Date.now();\n row.attempts += 1;\n row.lastAttemptedAt = now;\n row.updatedAt = now;\n row.claimedBy = undefined;\n row.claimedAt = undefined;\n row.responseCode = result.httpStatus;\n row.responseBody = result.responseBody;\n\n let status: HttpDeliveryStatus;\n if (result.success) {\n status = 'success';\n row.nextRetryAt = undefined;\n row.error = undefined;\n } else if (result.dead) {\n status = 'dead';\n row.error = result.error;\n row.nextRetryAt = undefined;\n } else {\n status = 'pending';\n row.error = result.error;\n row.nextRetryAt = result.nextRetryAt;\n }\n row.status = status;\n }\n\n async list(filter?: { status?: HttpDeliveryStatus; source?: string }): Promise<HttpDelivery[]> {\n let all = Array.from(this.rows.values()).map((r) => ({ ...r }));\n if (filter?.status) all = all.filter((r) => r.status === filter.status);\n if (filter?.source) all = all.filter((r) => r.source === filter.source);\n return all;\n }\n\n async redeliver(id: string): Promise<HttpDelivery> {\n const row = this.rows.get(id);\n if (!row) {\n throw new HttpRedeliverError(`Delivery row '${id}' not found`, 'not_found');\n }\n if (row.status !== 'success' && row.status !== 'failed' && row.status !== 'dead') {\n throw new HttpRedeliverError(\n `Delivery row '${id}' is '${row.status}', expected one of: success, failed, dead`,\n 'not_eligible',\n );\n }\n const now = Date.now();\n row.status = 'pending';\n row.attempts = 0;\n row.claimedBy = undefined;\n row.claimedAt = undefined;\n row.nextRetryAt = undefined;\n row.error = undefined;\n row.responseCode = undefined;\n row.responseBody = undefined;\n row.updatedAt = now;\n return { ...row };\n }\n}\n"],"mappings":"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","names":["randomUUID","msg","msg","deliveries","delivered","randomUUID","randomUUID","randomUUID","SINGLE_NODE_CLUSTER","stableNodeOffset","msg","USER_OBJECT","ObjectSchema","Field","ObjectSchema","Field","ObjectSchema","Field","ObjectSchema","Field","ObjectSchema","Field","ObjectSchema","Field","randomUUID","randomUUID","randomUUID","randomUUID","randomUUID"]}
1
+ {"version":3,"sources":["../src/messaging-service-plugin.ts","../src/recipient-resolver.ts","../src/preference-resolver.ts","../src/inbox-channel.ts","../src/messaging-service.ts","../src/sql-outbox.ts","../src/backoff.ts","../src/audit-timestamp.ts","../src/sql-http-outbox.ts","../src/http-outbox.ts","../src/objects/http-delivery.object.ts","../src/digest-render.ts","../src/dispatcher.ts","../src/http-sender.ts","../src/http-dispatcher.ts","../src/retention.ts","../src/template-renderer.ts","../src/email-channel.ts","../src/objects/inbox-message.object.ts","../src/objects/notification-receipt.object.ts","../src/objects/notification-delivery.object.ts","../src/objects/notification-preference.object.ts","../src/objects/notification-subscription.object.ts","../src/objects/notification-template.object.ts","../src/memory-outbox.ts","../src/memory-http-outbox.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { randomUUID } from 'node:crypto';\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport { MessagingService } from './messaging-service.js';\nimport { createInboxChannel } from './inbox-channel.js';\nimport { SqlNotificationOutbox } from './sql-outbox.js';\nimport { SqlHttpOutbox } from './sql-http-outbox.js';\nimport { NotificationDispatcher, type DispatchCluster } from './dispatcher.js';\nimport { HttpDispatcher } from './http-dispatcher.js';\nimport { NotificationRetention, DEFAULT_NOTIFICATION_RETENTION_DAYS } from './retention.js';\nimport { createEmailChannel } from './email-channel.js';\nimport { NotificationTemplateStore } from './template-renderer.js';\nimport {\n InboxMessage,\n NotificationReceipt,\n NotificationDelivery,\n NotificationPreference,\n NotificationSubscription,\n NotificationTemplate,\n HttpDelivery,\n} from './objects/index.js';\n\nexport interface MessagingServicePluginOptions {\n /**\n * Register the always-on `inbox` channel during init (default `true`).\n * Set `false` only for tests that want an empty registry.\n */\n registerInbox?: boolean;\n /**\n * Run the durable delivery outbox + dispatcher (ADR-0030 P1) when a data\n * engine is available (default `true`). When off (or no engine), `emit()`\n * fans out inline best-effort (P0 behavior).\n */\n reliableDelivery?: boolean;\n /** Outbox/dispatcher partition count (default 8). */\n partitionCount?: number;\n /** Dispatcher tick interval in ms (default 500). */\n dispatchIntervalMs?: number;\n /**\n * Topics that bypass the per-user preference matrix (ADR-0030 P2) — e.g.\n * security/system alerts users must not be able to mute. Exact match, or a\n * `prefix.` entry for a prefix match (default none).\n */\n mandatoryTopics?: readonly string[];\n /**\n * Retention window in days for the notification pipeline (ADR-0030\n * hardening). When > 0, a periodic sweep prunes events, deliveries, inbox\n * materializations and receipts older than this — bounding the event-log\n * growth from high-frequency periodic flows.\n *\n * **Default-on** at {@link DEFAULT_NOTIFICATION_RETENTION_DAYS} as of GA\n * (launch-readiness.md P1-2): an unbounded notification log is a slow leak,\n * so retention ships enabled rather than opt-in. Set to `0` to disable\n * retention entirely (notification history kept forever; operator owns\n * cleanup). Raise/lower the number to widen/narrow the window.\n */\n retentionDays?: number;\n /** Retention sweep interval in ms (default 1 hour). Only used when `retentionDays` is set. */\n retentionSweepMs?: number;\n}\n\n/**\n * MessagingServicePlugin — registers the `messaging` service (ADR-0012 /\n * ADR-0030).\n *\n * After bootstrap, `kernel.getService('messaging')` is a {@link MessagingService}\n * with the always-on `inbox` channel registered. The baseline `notify` flow\n * node dispatches through it; flows therefore stop being no-ops once this\n * plugin is installed. Other channels (email/webhook/push/IM) register\n * themselves on this same service.\n *\n * At `kernel:ready` (engine available) the plugin wires the reliable-delivery\n * path: a `SqlNotificationOutbox` over `sys_notification_delivery` plus a\n * `NotificationDispatcher` that drains it with retry/backoff/dead-letter.\n * `emit()` then enqueues durable deliveries instead of fanning out inline.\n *\n * @example\n * ```ts\n * const kernel = new ObjectKernel();\n * kernel.use(new AutomationServicePlugin()); // ships the `notify` node\n * kernel.use(new MessagingServicePlugin()); // backs it with delivery\n * await kernel.bootstrap();\n * ```\n */\nexport class MessagingServicePlugin implements Plugin {\n name = 'com.objectstack.service.messaging';\n version = '1.0.0';\n type = 'standard' as const;\n dependencies = ['com.objectstack.engine.objectql'];\n\n private readonly options: Required<MessagingServicePluginOptions>;\n private dispatcher?: NotificationDispatcher;\n private httpDispatcher?: HttpDispatcher;\n private retentionTimer?: ReturnType<typeof setInterval>;\n\n constructor(options: MessagingServicePluginOptions = {}) {\n this.options = {\n registerInbox: true,\n reliableDelivery: true,\n partitionCount: 8,\n dispatchIntervalMs: 500,\n mandatoryTopics: [],\n retentionDays: DEFAULT_NOTIFICATION_RETENTION_DAYS,\n retentionSweepMs: 3_600_000,\n ...options,\n };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n // Shared lazy data-engine resolver — used to persist the L2\n // `sys_notification` event in `emit()`, by the inbox channel to\n // materialize rows, and (at kernel:ready) to back the outbox. Resolved\n // lazily so it works regardless of plugin init order.\n const getData = (): IDataEngine | undefined => {\n try {\n return ctx.getService<IDataEngine>('data') ?? ctx.getService<IDataEngine>('objectql');\n } catch {\n return undefined;\n }\n };\n\n const service = new MessagingService({\n logger: ctx.logger,\n getData,\n mandatoryTopics: this.options.mandatoryTopics,\n });\n\n if (this.options.registerInbox) {\n service.registerChannel(createInboxChannel({ getData }));\n }\n\n ctx.registerService('messaging', service);\n\n // ADR-0030: the messaging service also backs the `notification` core\n // service slot — it owns the in-app inbox + receipts, so it answers the\n // `/api/v1/notifications` REST surface (list / mark-read / mark-all-read)\n // via its inbox read API. Registering it here makes the dispatcher\n // resolve + advertise those routes (`hasNotification`). The legacy\n // INotificationService `send()` abstraction is unused; nothing consumes\n // the slot expecting it.\n ctx.registerService('notification', service);\n\n // Register the messaging objects so their rows can be written. The\n // preference/subscription objects (ADR-0030 P2) are Studio-configurable,\n // so contribute them to the Setup app's Configuration slot (ADR-0029 D7)\n // — they appear in nav only when this plugin is installed.\n ctx.getService<{ register(m: unknown): void }>('manifest').register({\n id: 'com.objectstack.service.messaging',\n name: 'Messaging Service',\n version: '1.0.0',\n type: 'plugin',\n scope: 'system',\n objects: [\n InboxMessage,\n NotificationReceipt,\n NotificationDelivery,\n NotificationPreference,\n NotificationSubscription,\n NotificationTemplate,\n HttpDelivery,\n ],\n navigationContributions: [\n {\n app: 'setup',\n group: 'group_configuration',\n priority: 120,\n items: [\n { id: 'nav_notification_preferences', type: 'object', label: 'Notification Preferences', objectName: 'sys_notification_preference', icon: 'bell-ring', requiresObject: 'sys_notification_preference' },\n { id: 'nav_notification_subscriptions', type: 'object', label: 'Notification Subscriptions', objectName: 'sys_notification_subscription', icon: 'rss', requiresObject: 'sys_notification_subscription' },\n { id: 'nav_notification_templates', type: 'object', label: 'Notification Templates', objectName: 'sys_notification_template', icon: 'file-text', requiresObject: 'sys_notification_template' },\n ],\n },\n ],\n });\n\n // Provision the physical tables for this service's system objects\n // up-front, once the engine is ready. The inbox channel materializes\n // sys_inbox_message + sys_notification_receipt rows on first delivery,\n // so the tables are otherwise lazy-created on first WRITE — a freshly\n // provisioned env that READS the inbox / notifications before any\n // message has been delivered hits \"no such table\", logged as a\n // `Find operation failed` ERROR on every page load. Runs independently\n // of `reliableDelivery` (the inbox tables are needed either way) and is\n // idempotent. See {@link provisionSystemTables}.\n if (typeof ctx.hook === 'function') {\n ctx.hook('kernel:ready', async () => {\n const engine = getData();\n if (engine) await this.provisionSystemTables(engine, ctx);\n });\n }\n\n // Email channel (ADR-0030 P3): register when an `email` service is\n // present. Resolved at kernel:ready so init order with the email plugin\n // doesn't matter; absent email ⇒ no channel (a notify(channels:['email'])\n // then reports \"not registered\" rather than silently no-opping). The\n // dispatcher looks channels up dynamically, so registering after it is fine.\n if (typeof ctx.hook === 'function') {\n const templateStore = new NotificationTemplateStore({ getData });\n const getEmail = () => {\n try {\n return ctx.getService<import('./email-channel.js').EmailSenderSurface>('email');\n } catch {\n return undefined;\n }\n };\n ctx.hook('kernel:ready', async () => {\n if (getEmail()) {\n service.registerChannel(createEmailChannel({ getEmail, getData, store: templateStore }));\n ctx.logger.info('[messaging] email channel registered (renders sys_notification_template)');\n }\n });\n }\n\n // Reliable delivery (P1): wire the outbox + dispatcher once the engine\n // is resolvable. Until then `emit()` runs inline best-effort.\n if (this.options.reliableDelivery && typeof ctx.hook === 'function') {\n ctx.hook('kernel:ready', async () => {\n const engine = getData();\n if (!engine) {\n ctx.logger.warn('[messaging] no data engine at kernel:ready — reliable delivery disabled (inline fan-out)');\n return;\n }\n const outbox = new SqlNotificationOutbox(engine, { partitionCount: this.options.partitionCount });\n service.setOutbox(outbox);\n\n let cluster: DispatchCluster | undefined;\n try {\n cluster = ctx.getService<DispatchCluster>('cluster');\n } catch {\n cluster = undefined; // single-node fallback in the dispatcher\n }\n\n this.dispatcher = new NotificationDispatcher({\n nodeId: `notify-${process.pid}-${randomUUID().slice(0, 8)}`,\n outbox,\n channels: service,\n channelContext: { logger: ctx.logger },\n cluster,\n partitionCount: this.options.partitionCount,\n intervalMs: this.options.dispatchIntervalMs,\n logger: ctx.logger,\n });\n this.dispatcher.start();\n ctx.logger.info(\n `[messaging] reliable delivery on (outbox + dispatcher, ${this.options.partitionCount} partitions${cluster ? ', clustered' : ', single-node'})`,\n );\n\n // ADR-0018 M3: generic outbound-HTTP outbox + dispatcher. Backs\n // the Flow `http` node (and, going forward, webhook fan-out) with\n // the same retry / dead-letter substrate as notifications.\n const httpOutbox = new SqlHttpOutbox(engine, { partitionCount: this.options.partitionCount });\n service.setHttpOutbox(httpOutbox);\n this.httpDispatcher = new HttpDispatcher({\n nodeId: `http-${process.pid}-${randomUUID().slice(0, 8)}`,\n outbox: httpOutbox,\n cluster,\n partitionCount: this.options.partitionCount,\n intervalMs: this.options.dispatchIntervalMs,\n logger: ctx.logger,\n });\n this.httpDispatcher.start();\n ctx.logger.info(\n `[messaging] HTTP delivery on (sys_http_delivery outbox + dispatcher, ${this.options.partitionCount} partitions)`,\n );\n });\n }\n\n // Retention sweep (ADR-0030 hardening): opt-in pruning of the\n // notification pipeline so the event log can't grow unbounded. Runs once\n // at ready then on a low-frequency interval; the timer is unref'd so it\n // never keeps the process alive.\n if (this.options.retentionDays > 0 && typeof ctx.hook === 'function') {\n ctx.hook('kernel:ready', async () => {\n const retention = new NotificationRetention({ getData, logger: ctx.logger });\n const days = this.options.retentionDays;\n const sweep = () => {\n void retention.prune(days).catch((err) =>\n ctx.logger.warn(`[messaging] retention sweep failed: ${(err as Error)?.message ?? err}`),\n );\n };\n sweep();\n this.retentionTimer = setInterval(sweep, this.options.retentionSweepMs);\n this.retentionTimer.unref?.();\n ctx.logger.info(\n `[messaging] retention on (prune > ${days}d every ${Math.round(this.options.retentionSweepMs / 1000)}s)`,\n );\n });\n }\n\n ctx.logger.info(\n `[messaging] service registered with channels: ${service.getRegisteredChannels().join(', ') || '(none)'}`,\n );\n }\n\n /**\n * Provision the physical tables for this service's system objects up-front.\n *\n * These objects are lazy-created on first WRITE (the SQL driver issues DDL\n * when the first row is inserted), so an env that READS them first — the\n * Console bell / inbox queries sys_inbox_message + sys_notification_receipt\n * before any notification has been delivered — hits \"no such table\", which\n * the engine logs as a `Find operation failed` ERROR on every page load.\n * Creating the tables at kernel:ready makes a new env consistent from the\n * start. Idempotent (the driver only creates a table when absent), so it is\n * safe on every boot; per-object failures are isolated.\n */\n private async provisionSystemTables(engine: IDataEngine, ctx: PluginContext): Promise<void> {\n // `syncObjectSchema` lives on the concrete ObjectQL engine, not the\n // IDataEngine contract; engines without on-demand DDL skip provisioning.\n const sync = (engine as unknown as { syncObjectSchema?: (name: string) => Promise<void> }).syncObjectSchema;\n if (typeof sync !== 'function') return;\n const objects = [\n InboxMessage,\n NotificationReceipt,\n NotificationDelivery,\n NotificationPreference,\n NotificationSubscription,\n NotificationTemplate,\n HttpDelivery,\n ];\n for (const obj of objects) {\n try {\n await sync.call(engine, (obj as { name: string }).name);\n } catch (err) {\n ctx.logger.warn(`[messaging] could not provision ${(obj as { name: string }).name} storage — ${(err as Error)?.message ?? err}`);\n }\n }\n }\n\n /** Stop the dispatcher loop + retention sweep on shutdown. */\n async stop(): Promise<void> {\n await this.dispatcher?.stop();\n this.dispatcher = undefined;\n await this.httpDispatcher?.stop();\n this.httpDispatcher = undefined;\n if (this.retentionTimer) {\n clearInterval(this.retentionTimer);\n this.retentionTimer = undefined;\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport type { Audience, AudienceSpec } from './messaging-service.js';\n\n/**\n * Cheap \"looks like an email\" heuristic so we attempt id resolution. Hand-rolled\n * (no regex) to stay strictly linear — the obvious `^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$`\n * has overlapping quantifiers (the `.` is also in `[^\\s@]`) and backtracks\n * polynomially on adversarial input (ReDoS). This is O(n).\n */\nfunction looksLikeEmail(s: string): boolean {\n if (!s || /\\s/.test(s)) return false; // single char-class test — linear\n const at = s.indexOf('@');\n if (at <= 0 || at !== s.lastIndexOf('@') || at === s.length - 1) return false;\n const domain = s.slice(at + 1);\n const dot = domain.indexOf('.');\n // a dot in the domain, not first or last char\n return dot > 0 && dot < domain.length - 1;\n}\n\n/** The user identity object an email-shaped recipient is resolved against. */\nexport const USER_OBJECT = 'sys_user';\n/** Tenant-membership object backing `role:` expansion (`sys_member.role`). */\nexport const MEMBER_OBJECT = 'sys_member';\n/** Team-membership object backing `team:` expansion (`sys_team_member.team_id`). */\nexport const TEAM_MEMBER_OBJECT = 'sys_team_member';\n\n/**\n * Conventional owner/assignee field names tried, in order, for `owner_of:`\n * audience resolution. Mirrors the audit writer's `OWNER_FIELDS`.\n */\nconst DEFAULT_OWNER_FIELDS = ['owner_id', 'assigned_to', 'assignee_id', 'owner', 'assignee'];\n\nexport interface RecipientResolverLogger {\n warn(...args: unknown[]): void;\n info?(...args: unknown[]): void;\n}\n\nexport interface RecipientResolverOptions {\n /** Resolve the runtime data engine. `undefined` on a minimal/test stack. */\n getData(): IDataEngine | undefined;\n logger: RecipientResolverLogger;\n /** Identity object for email→id resolution (default {@link USER_OBJECT}). */\n userObject?: string;\n /** Membership object for `role:` (default {@link MEMBER_OBJECT}). */\n memberObject?: string;\n /** Membership object for `team:` (default {@link TEAM_MEMBER_OBJECT}). */\n teamMemberObject?: string;\n /** Owner field candidates for `owner_of:` (default {@link DEFAULT_OWNER_FIELDS}). */\n ownerFields?: string[];\n}\n\nexport interface ResolveContext {\n /** Tenant scope applied to `role:` expansion. */\n organizationId?: string;\n}\n\n/**\n * RecipientResolver (ADR-0030 P1) — expands an {@link Audience} into a flat,\n * de-duplicated list of recipient **user ids**.\n *\n * Reuses the platform's existing identity/membership object model (the same\n * `sys_member.role` / `sys_team_member.team_id` graph `plugin-sharing`'s\n * `TeamGraphService` reads) by querying it directly through the data engine —\n * so `service-messaging` gains no backward dependency on a plugin. This is the\n * single home for recipient resolution: the inbox channel no longer does its\n * own email→id fallback (that moved here per ADR-0030 P1).\n *\n * Supported audience specs:\n * - `'<userId>'` → the id verbatim\n * - `'user:<id>'` → the id (prefix stripped)\n * - `'<email>'` → `sys_user` lookup by email → id (verbatim on miss)\n * - `'role:<name>'` → `sys_member` where `role = name` (tenant-scoped)\n * - `'team:<id>'` → `sys_team_member` where `team_id = id`\n * - `'owner_of:<obj>:<id>'` → the owner field of that record\n * - `{ ownerOf: { object, id } }` → same, structured form\n *\n * Every lookup is best-effort: a failed query resolves to no recipients for\n * that spec (logged) rather than throwing — emit() must never fail because a\n * directory lookup hiccupped.\n */\nexport class RecipientResolver {\n private readonly userObject: string;\n private readonly memberObject: string;\n private readonly teamMemberObject: string;\n private readonly ownerFields: string[];\n\n constructor(private readonly opts: RecipientResolverOptions) {\n this.userObject = opts.userObject ?? USER_OBJECT;\n this.memberObject = opts.memberObject ?? MEMBER_OBJECT;\n this.teamMemberObject = opts.teamMemberObject ?? TEAM_MEMBER_OBJECT;\n this.ownerFields = opts.ownerFields ?? DEFAULT_OWNER_FIELDS;\n }\n\n /** Expand an audience to a de-duplicated list of recipient user ids. */\n async resolve(audience: Audience, ctx: ResolveContext = {}): Promise<string[]> {\n const specs = Array.isArray(audience) ? audience : [audience as AudienceSpec];\n const data = this.opts.getData();\n const out: string[] = [];\n\n for (const spec of specs) {\n for (const id of await this.resolveOne(spec, data, ctx)) {\n if (id) out.push(id);\n }\n }\n // De-dup while preserving order.\n return [...new Set(out)];\n }\n\n private async resolveOne(\n spec: AudienceSpec,\n data: IDataEngine | undefined,\n ctx: ResolveContext,\n ): Promise<string[]> {\n if (typeof spec !== 'string') {\n // Structured `{ ownerOf: { object, id } }`.\n if (spec && typeof spec === 'object' && 'ownerOf' in spec) {\n return this.resolveOwnerOf(spec.ownerOf.object, spec.ownerOf.id, data);\n }\n this.opts.logger.warn(`[recipients] unrecognized audience spec ${JSON.stringify(spec)}; skipped`);\n return [];\n }\n\n const value = spec.trim();\n if (!value) return [];\n\n if (value.startsWith('user:')) return [value.slice(5)].filter(Boolean);\n if (value.startsWith('role:')) return this.resolveRole(value.slice(5), data, ctx);\n if (value.startsWith('team:')) return this.resolveTeam(value.slice(5), data);\n if (value.startsWith('owner_of:')) {\n // `owner_of:<object>:<id>` — id may itself contain ':' (rare), so\n // split only on the first two segments.\n const rest = value.slice('owner_of:'.length);\n const sep = rest.indexOf(':');\n if (sep > 0) return this.resolveOwnerOf(rest.slice(0, sep), rest.slice(sep + 1), data);\n this.opts.logger.warn(`[recipients] malformed owner_of spec '${value}'; skipped`);\n return [];\n }\n if (looksLikeEmail(value)) return [await this.resolveEmail(value, data)];\n // Bare user id.\n return [value];\n }\n\n /** `role:` → `sys_member` rows with that role in the tenant. */\n private async resolveRole(role: string, data: IDataEngine | undefined, ctx: ResolveContext): Promise<string[]> {\n if (!role || !data) return [];\n const where: Record<string, unknown> = { role };\n if (ctx.organizationId) where.organization_id = ctx.organizationId;\n try {\n const rows = await data.find(this.memberObject, { where, fields: ['user_id'], limit: 10000 });\n return userIds(rows);\n } catch (err) {\n this.opts.logger.warn(`[recipients] role '${role}' lookup failed (${msg(err)}); 0 recipients`);\n return [];\n }\n }\n\n /** `team:` → `sys_team_member` rows for that team. */\n private async resolveTeam(teamId: string, data: IDataEngine | undefined): Promise<string[]> {\n if (!teamId || !data) return [];\n try {\n const rows = await data.find(this.teamMemberObject, {\n where: { team_id: teamId },\n fields: ['user_id'],\n limit: 10000,\n });\n return userIds(rows);\n } catch (err) {\n this.opts.logger.warn(`[recipients] team '${teamId}' lookup failed (${msg(err)}); 0 recipients`);\n return [];\n }\n }\n\n /** `owner_of:` → the owner/assignee field of the referenced record. */\n private async resolveOwnerOf(object: string, id: string, data: IDataEngine | undefined): Promise<string[]> {\n if (!object || !id || !data) return [];\n try {\n const rec = await data.findOne(object, { where: { id }, fields: ['id', ...this.ownerFields] });\n if (!rec) return [];\n for (const f of this.ownerFields) {\n const v = rec[f];\n if (typeof v === 'string' && v.length > 0) return [v];\n }\n return [];\n } catch (err) {\n this.opts.logger.warn(`[recipients] owner_of '${object}:${id}' lookup failed (${msg(err)}); 0 recipients`);\n return [];\n }\n }\n\n /**\n * Resolve an email-shaped recipient to its user id. Falls back to the email\n * verbatim on no match or lookup error (a downstream channel may still key\n * a row by it — never lose the recipient on a directory miss).\n */\n private async resolveEmail(email: string, data: IDataEngine | undefined): Promise<string> {\n if (!data) return email;\n try {\n const user = await data.findOne(this.userObject, { where: { email }, fields: ['id'] });\n const id = user?.id;\n if (id != null && String(id).length > 0) return String(id);\n this.opts.logger.warn(`[recipients] no '${this.userObject}' matched email '${email}'; keeping verbatim`);\n return email;\n } catch (err) {\n this.opts.logger.warn(`[recipients] email '${email}' lookup failed (${msg(err)}); keeping verbatim`);\n return email;\n }\n }\n}\n\n/** Pull distinct non-empty `user_id`s from a row set. */\nfunction userIds(rows: unknown): string[] {\n if (!Array.isArray(rows)) return [];\n return [...new Set(rows.map((r: any) => String(r?.user_id ?? '')).filter(Boolean))];\n}\n\nfunction msg(err: unknown): string {\n return (err as Error)?.message ?? String(err);\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\n\n/** The object the preference matrix lives in. */\nexport const PREFERENCE_OBJECT = 'sys_notification_preference';\n\nexport interface PreferenceResolverLogger {\n info(...args: unknown[]): void;\n warn(...args: unknown[]): void;\n error(...args: unknown[]): void;\n}\n\nexport interface PreferenceResolverOptions {\n /** Lazily resolve the data engine; `undefined` ⇒ fail-open (deliver all). */\n getData(): IDataEngine | undefined;\n logger: PreferenceResolverLogger;\n /**\n * Topics that bypass preferences entirely (security/system alerts users\n * must not be able to mute). An entry ending in `.` is a prefix match\n * (`security.` matches `security.breach`); otherwise it is an exact match.\n */\n mandatoryTopics?: readonly string[];\n /** Object name override (default {@link PREFERENCE_OBJECT}). */\n objectName?: string;\n}\n\nexport interface PreferenceContext {\n topic: string;\n organizationId?: string;\n /** Event severity — `critical` bypasses quiet-hours deferral (P3b). */\n severity?: string;\n /** \"Now\" reference (epoch ms) for quiet-hours math. Defaults to Date.now(). */\n now?: number;\n}\n\n/** A recipient with the channels they accept for this notification. */\nexport interface PreferenceTarget {\n recipient: string;\n channels: string[];\n /**\n * Earliest dispatch time (epoch ms) when the recipient is deferred — by\n * quiet-hours *or* a digest window (P3b). Absent ⇒ send now. Applies to all\n * of this recipient's channels. Honored only on the durable outbox path;\n * inline best-effort fan-out ignores it.\n */\n notBefore?: number;\n /**\n * P3b-2 digest: present when the recipient batches this topic. `window` is a\n * stable label for the accumulation window (e.g. the local date for `daily`,\n * the week-start date for `weekly`); the dispatcher collapses all\n * `${recipient}|${channel}|${window}` deliveries into one message. Pairs with\n * `notBefore` = the window's dispatch time.\n */\n digest?: { window: string };\n}\n\n/** Quiet-hours window declared on a preference row. */\nexport interface QuietHours {\n tz?: string;\n start?: string; // 'HH:MM'\n end?: string; // 'HH:MM'\n}\n\n/** Digest cadence declared on a preference row (P3b-2). `none` ⇒ no batching. */\nexport type DigestCadence = 'daily' | 'weekly';\n\nconst WILDCARD = '*';\n\n/**\n * PreferenceResolver — the ADR-0030 Layer-3 preference filter (P2).\n *\n * Given the resolved recipients and the requested channels, returns, per\n * recipient, the channels they actually accept for `topic`. Resolution is\n * most-specific-wins over `sys_notification_preference` rows with `*` wildcards\n * for user / topic / channel; a real-user row overrides the `user_id='*'`\n * admin-global default; the built-in default is **on**.\n *\n * Two safety rules:\n * - **Mandatory topics bypass** the matrix (all channels kept).\n * - **Fail-open**: no data engine, or a lookup error, keeps all channels — a\n * preference outage must never silently swallow notifications.\n */\nexport class PreferenceResolver {\n private readonly objectName: string;\n private readonly mandatory: readonly string[];\n\n constructor(private readonly opts: PreferenceResolverOptions) {\n this.objectName = opts.objectName ?? PREFERENCE_OBJECT;\n this.mandatory = opts.mandatoryTopics ?? [];\n }\n\n /** Whether a topic bypasses preferences (exact or `prefix.` match). */\n isMandatory(topic: string): boolean {\n return this.mandatory.some((m) =>\n m.endsWith('.') ? topic.startsWith(m) : topic === m,\n );\n }\n\n /**\n * Filter `(recipient × channel)` by preference. Recipients left with no\n * accepted channel are dropped from the result.\n */\n async filter(\n recipients: string[],\n channels: string[],\n ctx: PreferenceContext,\n ): Promise<PreferenceTarget[]> {\n const all = (): PreferenceTarget[] => recipients.map((r) => ({ recipient: r, channels: [...channels] }));\n if (recipients.length === 0 || channels.length === 0) return [];\n if (this.isMandatory(ctx.topic)) return all();\n\n const data = this.opts.getData();\n if (!data) return all(); // fail-open\n\n let rows: Record<string, unknown>[];\n try {\n rows = await this.loadRows(data, ctx);\n } catch (err) {\n this.opts.logger.warn(\n `[preferences] lookup for topic '${ctx.topic}' failed (${msg(err)}); delivering all (fail-open)`,\n );\n return all();\n }\n\n // Index rows by `${user}|${topic}|${channel}` → { enabled, quietHours }.\n const recipientSet = new Set(recipients);\n const index = new Map<string, PrefRowLite>();\n for (const r of rows) {\n const user = String(r.user_id ?? '');\n if (user !== WILDCARD && !recipientSet.has(user)) continue; // ignore unrelated users\n const topic = String(r.topic ?? WILDCARD);\n const channel = String(r.channel ?? WILDCARD);\n index.set(`${user}|${topic}|${channel}`, {\n enabled: asBool(r.enabled),\n quietHours: parseQuietHours(r.quiet_hours),\n digest: parseDigest(r.digest),\n });\n }\n\n const nowMs = ctx.now ?? Date.now();\n const critical = ctx.severity === 'critical';\n const targets: PreferenceTarget[] = [];\n for (const recipient of recipients) {\n const accepted = channels.filter(\n (channel) => this.resolveRow(index, recipient, ctx.topic, channel)?.enabled ?? true,\n );\n if (accepted.length === 0) continue;\n // Deferral (per person; declared on a channel-wildcard row). Critical\n // events bypass both. A digest cadence takes precedence over\n // quiet-hours: batching already defers to a window boundary, and it\n // additionally tags the target so the dispatcher can collapse the\n // window's deliveries into one message.\n let notBefore: number | undefined;\n let digest: { window: string } | undefined;\n if (!critical) {\n const cadence = this.resolveDigest(index, recipient, ctx.topic);\n if (cadence) {\n const d = digestDeferral(cadence, nowMs, this.resolveQuietHours(index, recipient, ctx.topic)?.tz);\n notBefore = d.notBefore;\n digest = { window: d.window };\n } else {\n const qh = this.resolveQuietHours(index, recipient, ctx.topic);\n notBefore = qh ? quietHoursDeferral(qh, nowMs) : undefined;\n }\n }\n const target: PreferenceTarget = { recipient, channels: accepted };\n if (notBefore != null) target.notBefore = notBefore;\n if (digest) target.digest = digest;\n targets.push(target);\n }\n return targets;\n }\n\n /** Load the candidate rows (topic-specific + wildcard-topic), org-scoped. */\n private async loadRows(data: IDataEngine, ctx: PreferenceContext): Promise<Record<string, unknown>[]> {\n // Two equality queries (topic and the '*' wildcard) avoid relying on\n // driver-specific IN support; user filtering is done in memory.\n const base: Record<string, unknown> = {};\n if (ctx.organizationId) base.organization_id = ctx.organizationId;\n const [specific, wildcard] = await Promise.all([\n data.find(this.objectName, { where: { ...base, topic: ctx.topic }, limit: 10000 }),\n data.find(this.objectName, { where: { ...base, topic: WILDCARD }, limit: 10000 }),\n ]);\n return [...(specific ?? []), ...(wildcard ?? [])];\n }\n\n /**\n * Most-specific-wins lookup for (user, topic, channel). User-specific beats\n * the `*` user; topic/channel specific beats their wildcards.\n */\n private resolveRow(index: Map<string, PrefRowLite>, user: string, topic: string, channel: string): PrefRowLite | undefined {\n for (const u of [user, WILDCARD]) {\n for (const t of [topic, WILDCARD]) {\n for (const c of [channel, WILDCARD]) {\n const hit = index.get(`${u}|${t}|${c}`);\n if (hit !== undefined) return hit;\n }\n }\n }\n return undefined; // built-in default handled by callers (opted in)\n }\n\n /**\n * Resolve a recipient's quiet-hours window. Declared on a channel-wildcard\n * row (`(user, *, *)` or `(user, topic, *)`) — quiet hours are a per-person,\n * channel-agnostic setting. Most-specific user/topic wins.\n */\n private resolveQuietHours(index: Map<string, PrefRowLite>, user: string, topic: string): QuietHours | undefined {\n for (const u of [user, WILDCARD]) {\n for (const t of [topic, WILDCARD]) {\n const hit = index.get(`${u}|${t}|${WILDCARD}`);\n if (hit?.quietHours) return hit.quietHours;\n }\n }\n return undefined;\n }\n\n /**\n * Resolve a recipient's digest cadence. Like quiet-hours, declared on a\n * channel-wildcard row (`(user, *, *)` or `(user, topic, *)`) — batching is a\n * per-person, channel-agnostic setting. Most-specific user/topic wins.\n */\n private resolveDigest(index: Map<string, PrefRowLite>, user: string, topic: string): DigestCadence | undefined {\n for (const u of [user, WILDCARD]) {\n for (const t of [topic, WILDCARD]) {\n const hit = index.get(`${u}|${t}|${WILDCARD}`);\n if (hit?.digest) return hit.digest;\n }\n }\n return undefined;\n }\n}\n\ninterface PrefRowLite {\n enabled: boolean;\n quietHours?: QuietHours;\n digest?: DigestCadence;\n}\n\nfunction asBool(v: unknown): boolean {\n return v === true || v === 1 || v === '1' || v === 'true';\n}\n\nfunction parseQuietHours(v: unknown): QuietHours | undefined {\n let o: any = v;\n if (typeof o === 'string') {\n try { o = JSON.parse(o); } catch { return undefined; }\n }\n if (!o || typeof o !== 'object') return undefined;\n if (o.start == null || o.end == null) return undefined;\n return { tz: o.tz, start: String(o.start), end: String(o.end) };\n}\n\n/** Parse the `digest` preference column; only `daily`/`weekly` enable batching. */\nfunction parseDigest(v: unknown): DigestCadence | undefined {\n return v === 'daily' || v === 'weekly' ? v : undefined;\n}\n\n/**\n * Compute the digest dispatch target for `cadence` relative to `now`:\n * - `notBefore` (epoch ms) — when the accumulating window is flushed:\n * `daily` → next local midnight; `weekly` → next local Monday 00:00.\n * - `window` — a stable label for the *current* accumulating window so all\n * deliveries emitted within it collapse together: the local date for `daily`,\n * the week-start (Monday) date for `weekly`.\n *\n * Wall-clock is read in `tz` (default UTC). Day length is treated as 1440 min;\n * a DST transition can shift the boundary by an hour — acceptable for a batch\n * window, and the dispatcher reconciles against the real `next_attempt_at`. The\n * `tz`→`sys_user` fallback is a separate follow-up (ADR-0030 §remaining).\n */\nexport function digestDeferral(\n cadence: DigestCadence,\n nowMs: number,\n tz?: string,\n): { notBefore: number; window: string } {\n const zone = tz ?? 'UTC';\n const { minutesOfDay, isoWeekday, date } = wallClockInTz(nowMs, zone);\n if (cadence === 'daily') {\n const minsToMidnight = 1440 - minutesOfDay;\n return { notBefore: nowMs + minsToMidnight * 60_000, window: date };\n }\n // weekly: flush at the next Monday 00:00; window keyed by this week's Monday.\n const daysToNextMonday = ((8 - isoWeekday) % 7) || 7;\n const minsToTarget = daysToNextMonday * 1440 - minutesOfDay;\n const mondayMs = nowMs - (isoWeekday - 1) * 86_400_000;\n return { notBefore: nowMs + minsToTarget * 60_000, window: wallClockInTz(mondayMs, zone).date };\n}\n\n/**\n * Compute the deferral target (epoch ms) when `now` falls inside the quiet-hours\n * window, else `undefined`. Times are `HH:MM` in `quietHours.tz` (default UTC).\n * Supports overnight windows (start > end, e.g. 22:00–08:00). Uses `Intl` to read\n * the wall-clock minutes in the tz; returns `now + minutesUntilWindowEnd`.\n */\nexport function quietHoursDeferral(quietHours: QuietHours, nowMs: number): number | undefined {\n const start = parseHHMM(quietHours.start);\n const end = parseHHMM(quietHours.end);\n if (start == null || end == null || start === end) return undefined;\n\n const cur = minutesOfDayInTz(nowMs, quietHours.tz ?? 'UTC');\n let untilEnd: number | undefined;\n if (start < end) {\n if (cur >= start && cur < end) untilEnd = end - cur;\n } else {\n // Overnight window wrapping midnight.\n if (cur >= start) untilEnd = 1440 - cur + end;\n else if (cur < end) untilEnd = end - cur;\n }\n return untilEnd == null ? undefined : nowMs + untilEnd * 60_000;\n}\n\nfunction parseHHMM(s?: string): number | undefined {\n if (!s) return undefined;\n const m = /^(\\d{1,2}):(\\d{2})$/.exec(s.trim());\n if (!m) return undefined;\n const h = Number(m[1]);\n const min = Number(m[2]);\n if (h > 23 || min > 59) return undefined;\n return h * 60 + min;\n}\n\nfunction minutesOfDayInTz(nowMs: number, tz: string): number {\n try {\n const parts = new Intl.DateTimeFormat('en-US', {\n hour12: false,\n hour: '2-digit',\n minute: '2-digit',\n timeZone: tz,\n }).formatToParts(new Date(nowMs));\n const hour = Number(parts.find((p) => p.type === 'hour')?.value ?? '0') % 24; // '24' → 0\n const minute = Number(parts.find((p) => p.type === 'minute')?.value ?? '0');\n return hour * 60 + minute;\n } catch {\n // Unknown tz → treat as UTC.\n const d = new Date(nowMs);\n return d.getUTCHours() * 60 + d.getUTCMinutes();\n }\n}\n\n/**\n * Wall-clock view of `nowMs` in `tz`: minutes since local midnight, ISO weekday\n * (1=Mon … 7=Sun), and the local calendar date as `YYYY-MM-DD`. Falls back to\n * UTC for an unknown tz.\n */\nfunction wallClockInTz(nowMs: number, tz: string): { minutesOfDay: number; isoWeekday: number; date: string } {\n try {\n const parts = new Intl.DateTimeFormat('en-US', {\n hour12: false, year: 'numeric', month: '2-digit', day: '2-digit',\n hour: '2-digit', minute: '2-digit', timeZone: tz,\n }).formatToParts(new Date(nowMs));\n const get = (t: string) => parts.find((p) => p.type === t)?.value ?? '0';\n const y = Number(get('year'));\n const mo = Number(get('month'));\n const d = Number(get('day'));\n const hour = Number(get('hour')) % 24; // '24' → 0\n const minute = Number(get('minute'));\n const dow = new Date(Date.UTC(y, mo - 1, d)).getUTCDay(); // 0=Sun\n return { minutesOfDay: hour * 60 + minute, isoWeekday: dow === 0 ? 7 : dow, date: `${y}-${pad(mo)}-${pad(d)}` };\n } catch {\n const dt = new Date(nowMs);\n const dow = dt.getUTCDay();\n return {\n minutesOfDay: dt.getUTCHours() * 60 + dt.getUTCMinutes(),\n isoWeekday: dow === 0 ? 7 : dow,\n date: dt.toISOString().slice(0, 10),\n };\n }\n}\n\nfunction pad(n: number): string {\n return String(n).padStart(2, '0');\n}\n\nfunction msg(err: unknown): string {\n return (err as Error)?.message ?? String(err);\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport type {\n Delivery,\n ErrorClass,\n MessagingChannel,\n MessagingChannelContext,\n SendResult,\n} from './channel.js';\n\n/** The object the inbox channel writes rows to. */\nexport const INBOX_OBJECT = 'sys_inbox_message';\n\n/** The receipt object the inbox channel writes a `delivered` row to (ADR-0030). */\nexport const RECEIPT_OBJECT = 'sys_notification_receipt';\n\nexport interface InboxChannelOptions {\n /**\n * Resolve the runtime data engine. Returns `undefined` when no data layer\n * is registered (e.g. a minimal test stack) — the channel then warns and\n * reports a no-op success rather than throwing, matching the platform's\n * built-in CRUD-node degradation.\n */\n getData(): IDataEngine | undefined;\n /** Object name override (default {@link INBOX_OBJECT}). */\n objectName?: string;\n /** Receipt object name override (default {@link RECEIPT_OBJECT}). */\n receiptObject?: string;\n /** Clock injection for deterministic tests. Defaults to `new Date()`. */\n now?(): string;\n}\n\n/**\n * The always-on `inbox` channel (ADR-0012 §4).\n *\n * Unlike email/webhook/push, inbox is direction-reversed: there is no outbound\n * call — we write a `sys_inbox_message` row in our own DB and the user's client\n * pulls it. So it needs no connector/transport. One delivery → one row keyed by\n * the recipient user id.\n *\n * Recipients arrive already resolved to user ids by the `RecipientResolver`\n * (ADR-0030 P1) — the email→id fallback that used to live here moved up to the\n * single resolution home, so the channel just keys the row by `recipient`.\n */\nexport function createInboxChannel(opts: InboxChannelOptions): MessagingChannel {\n const objectName = opts.objectName ?? INBOX_OBJECT;\n const receiptObject = opts.receiptObject ?? RECEIPT_OBJECT;\n const now = opts.now ?? (() => new Date().toISOString());\n\n /**\n * Write the `delivered` receipt for an inbox materialization. Best-effort:\n * receipts are the read-state spine but a failure here must never turn a\n * delivered message into a failed one — we log and move on. Skipped when the\n * event id is absent (a synthetic/minimal stack with nothing to key on).\n */\n async function writeDeliveredReceipt(\n ctx: MessagingChannelContext,\n data: IDataEngine,\n r: { notificationId?: string; userId: string; organizationId?: string; at: string },\n ): Promise<void> {\n if (!r.notificationId) return;\n try {\n await data.insert(receiptObject, {\n notification_id: r.notificationId,\n delivery_id: null,\n user_id: r.userId,\n channel: 'inbox',\n state: 'delivered',\n at: r.at,\n organization_id: r.organizationId ?? null,\n created_at: r.at,\n });\n } catch (err) {\n ctx.logger.warn(\n `[inbox] delivered receipt write failed for '${r.userId}' (${(err as Error).message}); inbox row stands`,\n );\n }\n }\n\n return {\n id: 'inbox',\n\n async send(ctx: MessagingChannelContext, delivery: Delivery): Promise<SendResult> {\n const data = opts.getData();\n const n = delivery.notification;\n\n if (!data) {\n ctx.logger.warn(\n `[inbox] no data engine registered; inbox row for '${delivery.recipient}' not persisted`,\n );\n return { ok: true };\n }\n\n const userId = delivery.recipient;\n const at = now();\n\n const row: Record<string, unknown> = {\n user_id: userId,\n notification_id: n.notificationId ?? null,\n topic: n.topic,\n title: n.title,\n body_md: n.body,\n severity: n.severity ?? 'info',\n action_url: n.actionUrl,\n organization_id: n.organizationId ?? null,\n created_at: at,\n };\n\n let inboxId: string | undefined;\n try {\n const created = await data.insert(objectName, row);\n const id = Array.isArray(created) ? created[0]?.id : created?.id ?? created;\n inboxId = id != null ? String(id) : undefined;\n } catch (err) {\n return { ok: false, error: `inbox insert failed: ${(err as Error).message}` };\n }\n\n // Read-state lives in the receipt (ADR-0030), not on the inbox row.\n // Best-effort: a missing receipt must not fail a delivered message.\n await writeDeliveredReceipt(ctx, data, {\n notificationId: n.notificationId,\n userId,\n organizationId: n.organizationId,\n at,\n });\n\n return { ok: true, externalId: inboxId };\n },\n\n classifyError(_err: unknown): ErrorClass {\n // A failed local insert is almost always transient (lock/timeout).\n return 'retryable';\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport type {\n MessagingChannel,\n MessagingChannelContext,\n Notification,\n} from './channel.js';\nimport { RecipientResolver } from './recipient-resolver.js';\nimport { PreferenceResolver, type PreferenceTarget } from './preference-resolver.js';\nimport type { INotificationOutbox } from './outbox.js';\nimport type { EnqueueHttpInput, HttpDelivery, HttpDeliveryStatus, IHttpOutbox } from './http-outbox.js';\nimport { INBOX_OBJECT, RECEIPT_OBJECT } from './inbox-channel.js';\n\n/** The L2 event object every `emit()` writes one row to (ADR-0030). */\nexport const NOTIFICATION_EVENT_OBJECT = 'sys_notification';\n\n/** Receipt states that count as \"read\" for the inbox unread badge (ADR-0030). */\nconst READ_RECEIPT_STATES = new Set(['read', 'clicked', 'dismissed']);\n\n/**\n * Whether a driver error is a unique/primary-key constraint violation. Spans the\n * SQL drivers we ship: SQLite (`UNIQUE constraint failed`), Postgres (`23505` /\n * `duplicate key`), and MySQL (`ER_DUP_ENTRY` / `Duplicate entry`). Used to turn\n * a lost check-then-act race on a unique index into a fallback update.\n */\nfunction isUniqueViolation(err: unknown): boolean {\n const e = err as { code?: string | number; message?: string } | undefined;\n if (!e) return false;\n if (e.code === '23505' || e.code === 'ER_DUP_ENTRY' || e.code === 'SQLITE_CONSTRAINT_UNIQUE') return true;\n const msg = String(e.message ?? '').toLowerCase();\n return (\n msg.includes('unique constraint failed') ||\n msg.includes('duplicate key') ||\n msg.includes('duplicate entry')\n );\n}\n\n/**\n * One row of the inbox list REST response — the `Notification` shape in the API\n * spec (`NotificationSchema`). `id` is the notification's event id\n * (`notification_id`), which keys its read-state receipt, falling back to the\n * inbox row id for synthetic/legacy rows that carry no event id.\n */\nexport interface InboxNotificationView {\n id: string;\n type: string;\n title: string;\n body: string;\n read: boolean;\n actionUrl?: string;\n createdAt: string;\n}\n\n/**\n * Audience selector for {@link EmitInput}. P0 resolves explicit user ids and\n * email-shaped recipients inline (email→id is finished at the inbox channel);\n * `role:` / `team:` / `owner_of:` selectors are forwarded but only fully\n * expanded by the `RecipientResolver` in P1.\n */\nexport type AudienceSpec =\n | string // user id | email | 'role:x' | 'team:x'\n | { ownerOf: { object: string; id: string } };\n\nexport type Audience = AudienceSpec | readonly AudienceSpec[];\n\n/**\n * The single notification ingress (ADR-0030 §3). Every producer — the flow\n * `notify` node, collaboration `@mention`, record assignment, system alerts —\n * calls `emit()` with this shape. No producer writes a per-user inbox row\n * directly; the in-app inbox is a *materialization of delivery*.\n */\nexport interface EmitInput {\n /** Topic id, e.g. `task.assigned`, `collab.mention`. */\n readonly topic: string;\n /** Who should receive it. See {@link Audience}. */\n readonly audience: Audience;\n /** Template inputs carried to channels (title/body/url/actor/source/…). */\n readonly payload?: Record<string, unknown>;\n /** Severity hint for rendering / filtering. */\n readonly severity?: 'info' | 'warning' | 'critical';\n /** Idempotency key within a topic window; a repeat `emit` is a no-op. */\n readonly dedupKey?: string;\n /** The record that triggered the event. */\n readonly source?: { object: string; id: string };\n /** User who caused the event (mentioner, assigner). */\n readonly actorId?: string;\n /** Tenant stamp for sudo/background paths so RLS matches recipients. */\n readonly organizationId?: string;\n /** Channels to fan out to. Defaults to `['inbox']` (always on). */\n readonly channels?: string[];\n}\n\n/** Per-delivery outcome record returned from {@link MessagingService.emit}. */\nexport interface DeliveryOutcome {\n readonly channel: string;\n readonly recipient: string;\n readonly ok: boolean;\n readonly externalId?: string;\n readonly error?: string;\n}\n\n/** Aggregate result of fanning one notification out to its channels. */\nexport interface EmitResult {\n /** Id of the L2 `sys_notification` event written (or matched, on dedup). */\n readonly notificationId: string;\n /** True when `dedupKey` matched an existing event and fan-out was skipped. */\n readonly deduped: boolean;\n readonly deliveries: DeliveryOutcome[];\n readonly delivered: number;\n readonly failed: number;\n}\n\n/** Context the service needs: a logger, plus data access for the L2 event. */\nexport interface MessagingServiceContext extends MessagingChannelContext {\n /**\n * Resolve the runtime data engine used to persist the L2 event. Returns\n * `undefined` on a minimal/test stack with no data layer — `emit()` then\n * uses a synthetic id and warns rather than throwing, matching the\n * platform's CRUD-node degradation.\n */\n getData?(): IDataEngine | undefined;\n /** Clock injection for deterministic tests. Defaults to `new Date()`. */\n now?(): string;\n /** Override the recipient resolver (tests). Defaults to a data-backed one. */\n recipientResolver?: RecipientResolver;\n /** Override the preference resolver (tests). Defaults to a data-backed one. */\n preferenceResolver?: PreferenceResolver;\n /**\n * Topics that bypass the per-user preference matrix (ADR-0030 P2) — e.g.\n * security/system alerts users must not be able to mute. Exact match, or a\n * `prefix.` entry for prefix match.\n */\n mandatoryTopics?: readonly string[];\n /**\n * Durable delivery outbox (ADR-0030 P1). When present, `emit()` enqueues a\n * `pending` delivery row per `(recipient × channel)` and the\n * `NotificationDispatcher` performs the send + retries. When absent, `emit()`\n * fans out inline (best-effort, no retry) — the P0 behavior.\n */\n outbox?: INotificationOutbox;\n}\n\n/**\n * MessagingService — the notification dispatcher (ADR-0012 / ADR-0030).\n *\n * Holds the `MessagingChannel` registry and implements the single ingress\n * `emit(EmitInput)`:\n * 1. write the L2 `sys_notification` event (idempotent on `dedupKey`);\n * 2. resolve the audience to recipient ids (inline in P0; `RecipientResolver`\n * owns role/team/owner_of expansion in P1);\n * 3. fan `(channel × recipient)` deliveries and call each channel's `send()`,\n * which materializes the artifact (inbox row + receipt, email, …).\n *\n * Deliberately *not* in this phase (ADR-0030 P1+): the durable outbox, retry\n * schedule, cluster-lock, dead-letter, the per-user preference matrix,\n * renderers/templates, and digest middleware. `emit()` is best-effort fan-out;\n * failures are reported in the result. The seams are shaped so those land\n * without breaking callers.\n */\nexport class MessagingService {\n private readonly channels = new Map<string, MessagingChannel>();\n private readonly now: () => string;\n private readonly resolver: RecipientResolver;\n private readonly preferences: PreferenceResolver;\n private outbox?: INotificationOutbox;\n private httpOutbox?: IHttpOutbox;\n\n constructor(private readonly ctx: MessagingServiceContext) {\n this.now = ctx.now ?? (() => new Date().toISOString());\n this.resolver =\n ctx.recipientResolver ??\n new RecipientResolver({ getData: () => ctx.getData?.(), logger: ctx.logger });\n this.preferences =\n ctx.preferenceResolver ??\n new PreferenceResolver({\n getData: () => ctx.getData?.(),\n logger: ctx.logger,\n mandatoryTopics: ctx.mandatoryTopics,\n });\n this.outbox = ctx.outbox;\n }\n\n /**\n * Attach the durable delivery outbox after construction. The plugin wires\n * this once the data engine is resolvable (kernel:ready), switching `emit()`\n * from inline fan-out to the reliable enqueue → dispatcher path.\n */\n setOutbox(outbox: INotificationOutbox): void {\n this.outbox = outbox;\n }\n\n /**\n * Attach the generic outbound-HTTP delivery outbox (ADR-0018 M3). Wired by\n * the plugin at `kernel:ready` once the data engine is resolvable. Once set,\n * {@link enqueueHttp} persists durable rows the {@link HttpDispatcher}\n * drains with retry / dead-letter; the Flow `http` node enqueues through it.\n */\n setHttpOutbox(outbox: IHttpOutbox): void {\n this.httpOutbox = outbox;\n }\n\n /**\n * Whether durable HTTP delivery is available. Callers (e.g. the `http` node)\n * fall back to a direct, non-durable send when this is `false`.\n */\n isHttpDeliveryReady(): boolean {\n return this.httpOutbox !== undefined;\n }\n\n /**\n * Enqueue a durable outbound-HTTP delivery (ADR-0018 M3). Returns the row id.\n * Throws if no HTTP outbox is wired — guard with {@link isHttpDeliveryReady}.\n */\n async enqueueHttp(input: EnqueueHttpInput): Promise<string> {\n if (!this.httpOutbox) {\n throw new Error('messaging: HTTP delivery outbox not configured (no data engine / reliableDelivery off)');\n }\n return this.httpOutbox.enqueue(input);\n }\n\n /**\n * Reset a terminal HTTP delivery row back to `pending` so the dispatcher\n * re-sends it (ADR-0018 M3). Backs the webhook redeliver admin endpoint.\n * Throws if no HTTP outbox is wired, or `HttpRedeliverError` for a missing /\n * non-terminal row.\n */\n async redeliverHttp(id: string): Promise<HttpDelivery> {\n if (!this.httpOutbox) {\n throw new Error('messaging: HTTP delivery outbox not configured');\n }\n return this.httpOutbox.redeliver(id);\n }\n\n /** List HTTP delivery rows (admin/tests). Empty when no outbox is wired. */\n async listHttp(filter?: { status?: HttpDeliveryStatus; source?: string }): Promise<HttpDelivery[]> {\n if (!this.httpOutbox) return [];\n return this.httpOutbox.list(filter);\n }\n\n /** Register a channel implementation. A duplicate id warns and replaces. */\n registerChannel(channel: MessagingChannel): void {\n if (this.channels.has(channel.id)) {\n this.ctx.logger.warn(`[messaging] channel '${channel.id}' already registered; replacing`);\n }\n this.channels.set(channel.id, channel);\n this.ctx.logger.info(`[messaging] channel registered: ${channel.id}`);\n }\n\n /** Remove a channel. No-op when absent. */\n unregisterChannel(id: string): void {\n this.channels.delete(id);\n }\n\n /** Look up a channel by id. */\n getChannel(id: string): MessagingChannel | undefined {\n return this.channels.get(id);\n }\n\n /** All registered channel ids. */\n getRegisteredChannels(): string[] {\n return [...this.channels.keys()];\n }\n\n /* ------------------------------------------------------------------ */\n /* Inbox read API (ADR-0030) — backs /api/v1/notifications */\n /* */\n /* The REST notification surface reads the L5 `sys_inbox_message` */\n /* materialization joined with the `sys_notification_receipt` */\n /* read-state spine — NOT the re-modeled `sys_notification` L2 event */\n /* (which carries no recipient/read columns). Mark-read upserts the */\n /* receipt, keyed `(notification_id, user_id, channel:'inbox')`. */\n /* ------------------------------------------------------------------ */\n\n /**\n * List the signed-in user's in-app inbox, joined with read-state.\n *\n * A message is unread until its event has a `read`/`clicked`/`dismissed`\n * receipt; the `read` filter (when given) is applied in-memory after the\n * join. `unreadCount` is computed over the fetched window (bounded by\n * `limit`, like the Console bell's poll). Returns the REST contract shape\n * (`ListNotificationsResponseSchema`): `{ notifications, unreadCount }`.\n */\n async listInbox(\n userId: string,\n opts: { read?: boolean; type?: string; limit?: number } = {},\n ): Promise<{ notifications: InboxNotificationView[]; unreadCount: number }> {\n const data = this.ctx.getData?.();\n if (!data || !userId) return { notifications: [], unreadCount: 0 };\n\n const limit = Math.min(Math.max(opts.limit ?? 50, 1), 200);\n const where: Record<string, unknown> = { user_id: userId };\n if (opts.type) where.topic = opts.type;\n\n const [rows, receipts] = await Promise.all([\n data.find(INBOX_OBJECT, { where, orderBy: [{ field: 'created_at', order: 'desc' }], limit }) as Promise<Array<Record<string, unknown>>>,\n // Read-state spine. Best-effort: if receipts are unavailable the\n // inbox still lists (everything reads as unread) rather than erroring.\n (data.find(RECEIPT_OBJECT, { where: { user_id: userId, channel: 'inbox' } }) as Promise<Array<Record<string, unknown>>>)\n .catch(() => [] as Array<Record<string, unknown>>),\n ]);\n\n // notification_id → most-advanced receipt state (read/clicked/dismissed\n // wins over a plain delivered one).\n const stateByNotif = new Map<string, string>();\n for (const r of receipts) {\n const nid = r?.notification_id != null ? String(r.notification_id) : '';\n if (!nid) continue;\n const state = String(r.state ?? 'delivered');\n const prev = stateByNotif.get(nid);\n if (!prev || (!READ_RECEIPT_STATES.has(prev) && READ_RECEIPT_STATES.has(state))) {\n stateByNotif.set(nid, state);\n }\n }\n\n let unreadCount = 0;\n const all: InboxNotificationView[] = rows.map((m) => {\n const nid = m?.notification_id != null ? String(m.notification_id) : null;\n const state = nid ? stateByNotif.get(nid) : undefined;\n const read = state ? READ_RECEIPT_STATES.has(state) : false;\n if (!read) unreadCount += 1;\n return {\n id: nid ?? String(m.id),\n type: (m.topic as string) ?? 'notification',\n title: (m.title as string) ?? '',\n body: (m.body_md as string) ?? '',\n read,\n actionUrl: (m.action_url as string) ?? undefined,\n createdAt: (m.created_at as string) ?? this.now(),\n };\n });\n\n const notifications = opts.read === undefined ? all : all.filter((n) => n.read === opts.read);\n return { notifications, unreadCount };\n }\n\n /**\n * Mark specific notifications read by upserting their inbox receipts to\n * `read`. Updates the existing `delivered` receipt in place (keyed\n * `(notification_id, user_id, channel:'inbox')`); inserts one only when\n * absent. `ids` are notification (event) ids. Returns the REST contract\n * shape (`MarkNotificationsReadResponseSchema`): `{ success, readCount }`.\n */\n async markRead(userId: string, ids: readonly string[]): Promise<{ success: boolean; readCount: number }> {\n const data = this.ctx.getData?.();\n if (!data || !userId || !ids?.length) return { success: true, readCount: 0 };\n const at = this.now();\n let readCount = 0;\n for (const id of ids) {\n const nid = String(id ?? '');\n if (!nid) continue;\n try {\n readCount += await this.upsertReadReceipt(data, userId, nid, at);\n } catch (err) {\n this.ctx.logger.warn(`[messaging] markRead failed for '${nid}': ${(err as Error).message}`);\n }\n }\n return { success: true, readCount };\n }\n\n /**\n * Mark every currently-unread inbox message for the user as read. Returns\n * `{ success, readCount }` (`MarkAllNotificationsReadResponseSchema`).\n */\n async markAllRead(userId: string): Promise<{ success: boolean; readCount: number }> {\n const data = this.ctx.getData?.();\n if (!data || !userId) return { success: true, readCount: 0 };\n const { notifications } = await this.listInbox(userId, { read: false, limit: 200 });\n return this.markRead(userId, notifications.map((n) => n.id));\n }\n\n /** Upsert a `read` receipt for one notification; returns 1 when it persisted. */\n private async upsertReadReceipt(\n data: IDataEngine,\n userId: string,\n notificationId: string,\n at: string,\n ): Promise<number> {\n const where = { notification_id: notificationId, user_id: userId, channel: 'inbox' };\n const flipToRead = async (): Promise<boolean> => {\n const existing = await data.findOne(RECEIPT_OBJECT, { where, fields: ['id'] });\n if (!existing?.id) return false;\n await data.update(RECEIPT_OBJECT, { state: 'read', at }, { where: { id: existing.id } } as never);\n return true;\n };\n\n if (await flipToRead()) return 1;\n\n // No receipt yet — insert one. findOne→insert is check-then-act, so a\n // concurrent mark-read (or the best-effort `delivered` write still in\n // flight) can win the (notification_id, user_id, channel) unique index\n // between our read and write. Treat that collision as \"someone else\n // created it\" and flip the now-present row to `read` instead of failing.\n try {\n await data.insert(RECEIPT_OBJECT, {\n notification_id: notificationId,\n delivery_id: null,\n user_id: userId,\n channel: 'inbox',\n state: 'read',\n at,\n created_at: at,\n });\n return 1;\n } catch (err) {\n if (isUniqueViolation(err) && (await flipToRead())) return 1;\n throw err;\n }\n }\n\n /**\n * The single notification ingress. Writes the L2 event, resolves the\n * audience, and fans the result out to its channels. An unregistered\n * channel, or a channel that throws, is reported as a failed delivery — it\n * never aborts the rest of the fan-out. A `dedupKey` that matches an\n * existing event short-circuits: the event id is returned and no new\n * deliveries are produced.\n */\n async emit(input: EmitInput): Promise<EmitResult> {\n const data = this.ctx.getData?.();\n\n // 1) Idempotency — a prior event with the same dedup_key is a no-op.\n if (input.dedupKey && data) {\n const existing = await this.findEventByDedupKey(data, input.dedupKey);\n if (existing) {\n this.ctx.logger.info(\n `[messaging] emit: dedupKey '${input.dedupKey}' already emitted (${existing}); skipping`,\n );\n return { notificationId: existing, deduped: true, deliveries: [], delivered: 0, failed: 0 };\n }\n }\n\n // 2) Write the L2 event (or synthesize an id when there is no data layer).\n // The check at (1) is a fast-path. Where the driver materializes the\n // UNIQUE(dedup_key) index, it is the real guard: a concurrent emit\n // that raced past (1) and inserted first makes our insert hit the\n // unique violation — we catch it and converge to the winner's event\n // (treated as a dedup hit), so a record-change storm can't produce\n // duplicate notifications. Mirrors the delivery outbox's enqueue\n // convergence. (Drivers that don't enforce the index fall back to the\n // best-effort fast-path — the catch is then simply never taken.)\n let notificationId: string;\n try {\n notificationId = await this.writeEvent(data, input);\n } catch (err) {\n if (input.dedupKey && data) {\n const winner = await this.findEventByDedupKey(data, input.dedupKey);\n if (winner) {\n this.ctx.logger.info(\n `[messaging] emit: dedupKey '${input.dedupKey}' raced; converged to ${winner}`,\n );\n return { notificationId: winner, deduped: true, deliveries: [], delivered: 0, failed: 0 };\n }\n }\n throw err;\n }\n\n // 3) Resolve the audience to recipient user ids (RecipientResolver owns\n // role:/team:/owner_of:/email→id expansion).\n const recipients = await this.resolver.resolve(input.audience, {\n organizationId: input.organizationId,\n });\n if (recipients.length === 0) {\n this.ctx.logger.warn(`[messaging] emit: topic '${input.topic}' resolved to 0 recipients`);\n return { notificationId, deduped: false, deliveries: [], delivered: 0, failed: 0 };\n }\n\n // 3b) Preference filter (ADR-0030 P2): drop the (recipient × channel)\n // pairs the user muted. Mandatory topics bypass; fail-open on error.\n const payload = input.payload ?? {};\n const channels = input.channels?.length ? input.channels : ['inbox'];\n const targets = await this.preferences.filter(recipients, channels, {\n topic: input.topic,\n organizationId: input.organizationId,\n severity: input.severity,\n });\n if (targets.length === 0) {\n this.ctx.logger.info(`[messaging] emit: topic '${input.topic}' suppressed for all recipients by preference`);\n return { notificationId, deduped: false, deliveries: [], delivered: 0, failed: 0 };\n }\n\n // 4) Either enqueue durable deliveries (P1 outbox) or fan out inline (P0).\n if (this.outbox) {\n const deliveries = await this.enqueueDeliveries(this.outbox, notificationId, targets, input, payload);\n const delivered = deliveries.filter((d) => d.ok).length;\n return { notificationId, deduped: false, deliveries, delivered, failed: deliveries.length - delivered };\n }\n\n const notification: Notification = {\n notificationId,\n organizationId: input.organizationId,\n topic: input.topic,\n title: str(payload.title) ?? input.topic,\n body: str(payload.body) ?? '',\n severity: input.severity ?? 'info',\n recipients,\n channels: input.channels,\n actionUrl: actionUrlFor(input, payload),\n payload: input.payload,\n };\n\n const { deliveries, delivered, failed } = await this.fanOut(notification, targets);\n return { notificationId, deduped: false, deliveries, delivered, failed };\n }\n\n /**\n * Enqueue one `pending` delivery row per `(channel × recipient)`. The\n * dispatcher does the actual send + retry; here `ok` means \"accepted for\n * delivery\" (enqueued), not yet delivered — progress is observable on the\n * `sys_notification_delivery` row.\n */\n private async enqueueDeliveries(\n outbox: INotificationOutbox,\n notificationId: string,\n targets: PreferenceTarget[],\n input: EmitInput,\n payload: Record<string, unknown>,\n ): Promise<DeliveryOutcome[]> {\n // Snapshot the rendered content onto each delivery so a later event edit\n // can't rewrite an in-flight send.\n const deliveryPayload = {\n ...payload,\n title: str(payload.title) ?? input.topic,\n body: str(payload.body) ?? '',\n severity: input.severity ?? 'info',\n actionUrl: actionUrlFor(input, payload),\n };\n const deliveries: DeliveryOutcome[] = [];\n for (const { recipient, channels, notBefore, digest } of targets) {\n for (const channel of channels) {\n try {\n const id = await outbox.enqueue({\n notificationId,\n recipientId: recipient,\n channel,\n topic: input.topic,\n payload: deliveryPayload,\n organizationId: input.organizationId,\n // Quiet-hours / digest deferral (P3b): the dispatcher won't\n // claim this row until `notBefore`. Absent ⇒ immediate.\n notBefore,\n // P3b-2: tag batched deliveries so the dispatcher collapses\n // a `(recipient, channel, window)` group into one message.\n digestKey: digest ? `${recipient}|${channel}|${digest.window}` : undefined,\n });\n deliveries.push({ channel, recipient, ok: true, externalId: id });\n } catch (err) {\n deliveries.push({ channel, recipient, ok: false, error: (err as Error)?.message ?? String(err) });\n }\n }\n }\n return deliveries;\n }\n\n /** Find an existing event id by its dedup key, tolerating lookup failure. */\n private async findEventByDedupKey(data: IDataEngine, dedupKey: string): Promise<string | undefined> {\n try {\n const row = await data.findOne(NOTIFICATION_EVENT_OBJECT, {\n where: { dedup_key: dedupKey },\n fields: ['id'],\n });\n const id = row?.id;\n return id != null && String(id).length > 0 ? String(id) : undefined;\n } catch (err) {\n this.ctx.logger.warn(`[messaging] dedup lookup failed (${(err as Error).message}); proceeding`);\n return undefined;\n }\n }\n\n /**\n * Persist the L2 event and return its id. With no data layer (minimal/test\n * stacks) we warn and synthesize an id so fan-out can still be exercised.\n */\n private async writeEvent(data: IDataEngine | undefined, input: EmitInput): Promise<string> {\n if (!data) {\n this.ctx.logger.warn('[messaging] no data engine registered; event not persisted');\n return `evt_${Math.random().toString(36).slice(2)}`;\n }\n const row: Record<string, unknown> = {\n topic: input.topic,\n payload: input.payload ?? null,\n severity: input.severity ?? 'info',\n dedup_key: input.dedupKey ?? null,\n // Normalize empty strings to null so the (source_object, source_id)\n // index keys on real ids, never '' (producers may pass a bare object\n // with no id — e.g. a comment thread_id with no record part).\n source_object: str(input.source?.object) ?? null,\n source_id: str(input.source?.id) ?? null,\n actor_id: input.actorId ?? null,\n organization_id: input.organizationId ?? null,\n created_at: this.now(),\n };\n const created = await data.insert(NOTIFICATION_EVENT_OBJECT, row);\n const id = Array.isArray(created) ? created[0]?.id : created?.id ?? created;\n return id != null ? String(id) : `evt_${Math.random().toString(36).slice(2)}`;\n }\n\n /**\n * Fan a notification out to each recipient's accepted channels. Each\n * `(recipient, channel)` pair becomes one `send()` call. An unregistered\n * channel, or a channel that throws, is reported as a failed delivery — it\n * never aborts the rest of the fan-out.\n */\n private async fanOut(\n notification: Notification,\n targets: PreferenceTarget[],\n ): Promise<{ deliveries: DeliveryOutcome[]; delivered: number; failed: number }> {\n const deliveries: DeliveryOutcome[] = [];\n\n for (const { recipient, channels } of targets) {\n for (const channelId of channels) {\n const channel = this.channels.get(channelId);\n if (!channel) {\n deliveries.push({\n channel: channelId,\n recipient,\n ok: false,\n error: `channel '${channelId}' not registered`,\n });\n this.ctx.logger.warn(`[messaging] emit: channel '${channelId}' not registered`);\n continue;\n }\n try {\n const result = await channel.send(this.ctx, { notification, channel: channelId, recipient });\n deliveries.push({\n channel: channelId,\n recipient,\n ok: result.ok,\n externalId: result.externalId,\n error: result.error,\n });\n } catch (err) {\n deliveries.push({\n channel: channelId,\n recipient,\n ok: false,\n error: (err as Error)?.message ?? String(err),\n });\n }\n }\n }\n\n const delivered = deliveries.filter((d) => d.ok).length;\n return { deliveries, delivered, failed: deliveries.length - delivered };\n }\n}\n\n/** Coerce a payload value to a non-empty string, else `undefined`. */\nfunction str(v: unknown): string | undefined {\n if (v == null) return undefined;\n const s = String(v);\n return s.length > 0 ? s : undefined;\n}\n\n/**\n * The deep-link the in-app materialization should carry. An explicit\n * `payload.url`/`payload.actionUrl` wins; otherwise, when the emit names a\n * `source` record, synthesize an app-relative `/{object}/{id}` link so the\n * materialization is self-sufficient for navigation (the bell no longer has the\n * L2 event's `source_object/source_id` to fall back on — ADR-0030 L5). Returns\n * `undefined` when there is nothing to link to.\n */\nfunction actionUrlFor(input: EmitInput, payload: Record<string, unknown>): string | undefined {\n const explicit = str(payload.url) ?? str(payload.actionUrl);\n if (explicit) return explicit;\n const obj = str(input.source?.object);\n const id = str(input.source?.id);\n return obj && id ? `/${obj}/${id}` : undefined;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { randomUUID } from 'node:crypto';\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport type {\n AckResult,\n ClaimOptions,\n DeliveryStatus,\n EnqueueDeliveryInput,\n INotificationOutbox,\n NotificationDeliveryRecord,\n} from './outbox.js';\nimport { hashPartition } from './backoff.js';\nimport { toEpochMs } from './audit-timestamp.js';\n\nexport const DELIVERY_OBJECT = 'sys_notification_delivery';\n\nexport interface SqlNotificationOutboxOptions {\n /** Total partitions — MUST match the dispatcher's `partitionCount`. */\n partitionCount: number;\n /** Object name override (default {@link DELIVERY_OBJECT}). */\n objectName?: string;\n}\n\ninterface DeliveryRow {\n id: string;\n notification_id: string;\n recipient_id: string;\n channel: string;\n topic?: string | null;\n payload?: unknown; // json column — engine returns object or string per driver\n organization_id?: string | null;\n partition_key: number;\n status: DeliveryStatus;\n attempts: number;\n claimed_by?: string | null;\n claimed_at?: number | null;\n next_attempt_at?: number | null;\n last_attempted_at?: number | null;\n error?: string | null;\n digest_key?: string | null;\n // Builtin audit columns (native TIMESTAMP on Postgres/MySQL): WRITTEN as\n // `Date`s. Read-back form is dialect-dependent (epoch-ms on SQLite, a\n // `Date`/ISO string on Postgres); `toRecord` normalises via `toEpochMs`.\n created_at: number | string | Date;\n updated_at: number | string | Date;\n}\n\n/**\n * Durable {@link INotificationOutbox} over ObjectQL — the production store.\n * Driver-agnostic (no `FOR UPDATE SKIP LOCKED`): safety comes from the\n * dispatcher's per-partition cluster lock plus the atomic\n * `UPDATE … WHERE status='pending'` claim. `partition_key` is precomputed on\n * enqueue (ObjectQL has no portable `hash()` in WHERE). Mirrors\n * `SqlWebhookOutbox`.\n */\nexport class SqlNotificationOutbox implements INotificationOutbox {\n private readonly objectName: string;\n private readonly partitionCount: number;\n\n constructor(private readonly engine: IDataEngine, opts: SqlNotificationOutboxOptions) {\n if (opts.partitionCount <= 0) throw new Error('SqlNotificationOutbox: partitionCount must be > 0');\n this.objectName = opts.objectName ?? DELIVERY_OBJECT;\n this.partitionCount = opts.partitionCount;\n }\n\n async enqueue(input: EnqueueDeliveryInput): Promise<string> {\n const dedup = {\n notification_id: input.notificationId,\n recipient_id: input.recipientId,\n channel: input.channel,\n };\n const existing = await this.engine.findOne(this.objectName, { where: dedup, fields: ['id'] });\n if (existing?.id) return String(existing.id);\n\n const id = randomUUID();\n // `Date`, not epoch-ms: `created_at` / `updated_at` are native TIMESTAMP\n // columns and a real timestamp column rejects a bare number on Postgres.\n const now = new Date();\n const row: DeliveryRow = {\n id,\n notification_id: input.notificationId,\n recipient_id: input.recipientId,\n channel: input.channel,\n topic: input.topic ?? null,\n payload: input.payload ?? {},\n organization_id: input.organizationId ?? null,\n // Digest rows partition by their group key so a window's rows land in\n // one partition and a single node collapses them under its lock.\n partition_key: hashPartition(input.digestKey ?? input.notificationId, this.partitionCount),\n status: 'pending',\n attempts: 0,\n // Deferred dispatch (quiet-hours / digest, P3): claim() skips pending\n // rows whose next_attempt_at is in the future.\n next_attempt_at: input.notBefore ?? null,\n digest_key: input.digestKey ?? null,\n created_at: now,\n updated_at: now,\n };\n try {\n await this.engine.insert(this.objectName, row);\n return id;\n } catch (err) {\n // Unique-index collision (dedup race) → return the winner.\n const winner = await this.engine.findOne(this.objectName, { where: dedup, fields: ['id'] });\n if (winner?.id) return String(winner.id);\n throw err;\n }\n }\n\n async claim(opts: ClaimOptions): Promise<NotificationDeliveryRecord[]> {\n const now = opts.now ?? Date.now();\n\n // 1. Reap stale in_flight rows (visibility-timeout recovery).\n await this.engine.update(\n this.objectName,\n { status: 'pending', claimed_by: null, claimed_at: null, updated_at: now },\n { where: { status: 'in_flight', claimed_at: { $lt: now - opts.claimTtlMs } }, multi: true } as any,\n );\n\n // 2. Candidate ids: ready pending rows in our partition. Batched (digest)\n // rows are excluded — they drain via claimDigest so they collapse.\n const partitionFilter = opts.partition ? { partition_key: opts.partition.index } : {};\n const candidates = await this.engine.find(this.objectName, {\n where: {\n status: 'pending',\n digest_key: null,\n ...partitionFilter,\n $or: [{ next_attempt_at: null }, { next_attempt_at: { $lte: now } }],\n },\n fields: ['id'],\n limit: opts.limit,\n });\n if (!candidates.length) return [];\n const ids = (candidates as Array<{ id: string }>).map((c) => c.id);\n\n // 3. Atomic claim — WHERE status='pending' rejects rows another worker took.\n await this.engine.update(\n this.objectName,\n { status: 'in_flight', claimed_by: opts.nodeId, claimed_at: now, updated_at: now },\n { where: { id: { $in: ids }, status: 'pending' }, multi: true } as any,\n );\n\n // 4. Read back only the rows we own.\n const claimed = (await this.engine.find(this.objectName, {\n where: { id: { $in: ids }, claimed_by: opts.nodeId, claimed_at: now, status: 'in_flight' },\n })) as DeliveryRow[];\n return claimed.map((r) => this.toRecord(r));\n }\n\n async claimDigest(opts: ClaimOptions): Promise<NotificationDeliveryRecord[]> {\n const now = opts.now ?? Date.now();\n\n // 1. Reap stale in_flight (same as claim).\n await this.engine.update(\n this.objectName,\n { status: 'pending', claimed_by: null, claimed_at: null, updated_at: now },\n { where: { status: 'in_flight', claimed_at: { $lt: now - opts.claimTtlMs } }, multi: true } as any,\n );\n\n // 2. All DUE batched rows in our partition — a window is claimed whole, so\n // we don't apply `limit` (a generous cap guards a pathological backlog).\n const partitionFilter = opts.partition ? { partition_key: opts.partition.index } : {};\n const candidates = await this.engine.find(this.objectName, {\n where: {\n status: 'pending',\n digest_key: { $ne: null },\n ...partitionFilter,\n $or: [{ next_attempt_at: null }, { next_attempt_at: { $lte: now } }],\n },\n fields: ['id'],\n limit: 10000,\n });\n if (!candidates.length) return [];\n const ids = (candidates as Array<{ id: string }>).map((c) => c.id);\n\n // 3. Atomic claim.\n await this.engine.update(\n this.objectName,\n { status: 'in_flight', claimed_by: opts.nodeId, claimed_at: now, updated_at: now },\n { where: { id: { $in: ids }, status: 'pending' }, multi: true } as any,\n );\n\n // 4. Read back the rows we own.\n const claimed = (await this.engine.find(this.objectName, {\n where: { id: { $in: ids }, claimed_by: opts.nodeId, claimed_at: now, status: 'in_flight' },\n })) as DeliveryRow[];\n return claimed.map((r) => this.toRecord(r));\n }\n\n async ack(id: string, result: AckResult): Promise<void> {\n const current = (await this.engine.findOne(this.objectName, {\n where: { id },\n fields: ['attempts'],\n })) as { attempts?: number } | null;\n if (!current) return;\n\n const now = Date.now();\n let status: DeliveryStatus;\n let nextAttemptAt: number | null = null;\n let error: string | null = null;\n\n if (result.success) {\n status = 'success';\n } else if (result.suppressed) {\n status = 'suppressed';\n error = result.error ?? null;\n } else if (result.dead) {\n status = 'dead';\n error = result.error ?? null;\n } else {\n status = 'pending';\n nextAttemptAt = result.nextAttemptAt ?? null;\n error = result.error ?? null;\n }\n\n await this.engine.update(\n this.objectName,\n {\n status,\n attempts: (current.attempts ?? 0) + 1,\n last_attempted_at: now,\n claimed_by: null,\n claimed_at: null,\n next_attempt_at: nextAttemptAt,\n error,\n updated_at: now,\n },\n { where: { id }, multi: false } as any,\n );\n }\n\n async list(filter?: { status?: DeliveryStatus; notificationId?: string }): Promise<NotificationDeliveryRecord[]> {\n const where: Record<string, unknown> = {};\n if (filter?.status) where.status = filter.status;\n if (filter?.notificationId) where.notification_id = filter.notificationId;\n const rows = (await this.engine.find(this.objectName, { where })) as DeliveryRow[];\n return rows.map((r) => this.toRecord(r));\n }\n\n private toRecord(r: DeliveryRow): NotificationDeliveryRecord {\n let payload = r.payload ?? {};\n if (typeof payload === 'string') {\n try { payload = JSON.parse(payload); } catch { payload = {}; }\n }\n return {\n id: r.id,\n notificationId: r.notification_id,\n recipientId: r.recipient_id,\n channel: r.channel,\n topic: r.topic ?? undefined,\n payload: payload as NotificationDeliveryRecord['payload'],\n organizationId: r.organization_id ?? undefined,\n partitionKey: r.partition_key,\n status: r.status,\n attempts: r.attempts,\n claimedBy: r.claimed_by ?? undefined,\n claimedAt: r.claimed_at ?? undefined,\n nextAttemptAt: r.next_attempt_at ?? undefined,\n lastAttemptedAt: r.last_attempted_at ?? undefined,\n error: r.error ?? undefined,\n digestKey: r.digest_key ?? undefined,\n createdAt: toEpochMs(r.created_at),\n updatedAt: toEpochMs(r.updated_at),\n };\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { AckResult } from './outbox.js';\nimport type { SendResult, ErrorClass } from './channel.js';\n\n/**\n * Stable, framework-free partition hash (32-bit FNV-1a). Both the dispatcher\n * and the outbox `claim()` filter on it, so it must be a single shared helper.\n * Same implementation as `plugin-webhooks`.\n */\nexport function hashPartition(key: string, count: number): number {\n if (count <= 0) throw new Error('partition count must be > 0');\n let h = 0x811c9dc5;\n for (let i = 0; i < key.length; i++) {\n h ^= key.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return Math.abs(h | 0) % count;\n}\n\n/**\n * Exponential retry schedule with jitter. Returns the delay (ms) before the\n * next attempt given how many attempts have already happened, or `null` once\n * the budget is exhausted (→ dead).\n *\n * attempt 1 fails → ~1s · 2 → ~10s · 3 → ~1m · 4 → ~10m · 5 → ~1h · 6+ → dead\n */\nexport function nextRetryDelayMs(attemptsSoFar: number, rng: () => number = Math.random): number | null {\n const SCHEDULE = [1_000, 10_000, 60_000, 600_000, 3_600_000];\n if (attemptsSoFar < 1 || attemptsSoFar > SCHEDULE.length) return null;\n const base = SCHEDULE[attemptsSoFar - 1];\n const jitter = 0.8 + rng() * 0.4; // ∈ [0.8, 1.2)\n return Math.floor(base * jitter);\n}\n\n/**\n * Turn a channel `send()` outcome into an {@link AckResult}, applying the retry\n * schedule on retriable failures.\n *\n * - `ok` → success.\n * - `errorClass` of `permanent` → dead immediately (no point retrying).\n * - `errorClass` of `invalid_recipient` → suppressed (not our transport's fault).\n * - otherwise (retryable / unknown) → schedule a retry, or dead once the budget\n * is exhausted.\n */\nexport function classifyDeliveryAttempt(\n result: SendResult,\n errorClass: ErrorClass | undefined,\n attemptsSoFar: number,\n now: number = Date.now(),\n rng?: () => number,\n): AckResult {\n if (result.ok) return { success: true };\n\n if (errorClass === 'invalid_recipient') {\n return { success: false, error: result.error, suppressed: true };\n }\n if (errorClass === 'permanent') {\n return { success: false, error: result.error, dead: true };\n }\n\n const delay = nextRetryDelayMs(attemptsSoFar + 1, rng);\n if (delay === null) {\n return { success: false, error: result.error, dead: true };\n }\n return { success: false, error: result.error, nextAttemptAt: now + delay };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Normalise a builtin `created_at` / `updated_at` value to epoch milliseconds.\n *\n * Those two columns are provisioned as native `TIMESTAMP` columns by the SQL\n * driver (Postgres/MySQL), so the outboxes WRITE them as `Date`s — never a bare\n * epoch-ms number, which a real timestamp column rejects (the bug that broke the\n * `sys_notification_delivery` retention sweep on Postgres). The read-back form is\n * therefore dialect-dependent: epoch-ms on SQLite, a `Date` (or ISO string) on\n * Postgres. This collapses all of those back to epoch-ms so the outbox record\n * contract (`createdAt` / `updatedAt: number`) stays driver-independent.\n */\nexport function toEpochMs(value: unknown): number {\n if (typeof value === 'number') return value;\n if (value instanceof Date) return value.getTime();\n if (typeof value === 'string') {\n const parsed = Date.parse(value);\n if (Number.isFinite(parsed)) return parsed;\n const numeric = Number(value);\n if (Number.isFinite(numeric)) return numeric;\n }\n return 0;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { randomUUID } from 'node:crypto';\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport { hashPartition } from './backoff.js';\nimport { toEpochMs } from './audit-timestamp.js';\nimport {\n HttpRedeliverError,\n type EnqueueHttpInput,\n type HttpAckResult,\n type HttpClaimOptions,\n type HttpDelivery,\n type HttpDeliveryStatus,\n type IHttpOutbox,\n} from './http-outbox.js';\nimport { SYS_HTTP_DELIVERY } from './objects/http-delivery.object.js';\n\nexport interface SqlHttpOutboxOptions {\n /**\n * Total partition count — MUST match the dispatcher's `partitionCount`.\n * Used at enqueue time to precompute `partition_key`.\n */\n partitionCount: number;\n /** Object name to read/write. Defaults to `sys_http_delivery`. */\n objectName?: string;\n}\n\ninterface DeliveryRow {\n id: string;\n source: string;\n ref_id: string;\n dedup_key: string;\n label?: string | null;\n url: string;\n method?: string | null;\n headers_json?: string | null;\n signing_secret?: string | null;\n timeout_ms?: number | null;\n payload_json: string;\n partition_key: number;\n status: HttpDeliveryStatus;\n attempts: number;\n claimed_by?: string | null;\n claimed_at?: number | null;\n next_retry_at?: number | null;\n last_attempted_at?: number | null;\n response_code?: number | null;\n response_body?: string | null;\n error?: string | null;\n // Builtin audit columns (native TIMESTAMP on Postgres/MySQL): WRITTEN as\n // `Date`s. Read-back form is dialect-dependent; `toRecord` normalises via\n // `toEpochMs`.\n created_at: number | string | Date;\n updated_at: number | string | Date;\n}\n\n/**\n * Durable {@link IHttpOutbox} backed by ObjectQL — the production storage impl\n * for the generic outbound-HTTP outbox (ADR-0018 M3). Works against any\n * registered driver through the driver-agnostic `IDataEngine` API.\n *\n * Mirrors `SqlWebhookOutbox` exactly (cluster-lock + atomic\n * `UPDATE WHERE status='pending'` for the exactly-once claim; precomputed\n * `partition_key`; SELECT-then-INSERT dedup converging on the unique index).\n * Dedup uniqueness is `(source, dedup_key)`; partition affinity is on `ref_id`.\n */\nexport class SqlHttpOutbox implements IHttpOutbox {\n private readonly objectName: string;\n private readonly partitionCount: number;\n\n constructor(\n private readonly engine: IDataEngine,\n opts: SqlHttpOutboxOptions,\n ) {\n if (opts.partitionCount <= 0) {\n throw new Error('SqlHttpOutbox: partitionCount must be > 0');\n }\n this.objectName = opts.objectName ?? SYS_HTTP_DELIVERY;\n this.partitionCount = opts.partitionCount;\n }\n\n async enqueue(input: EnqueueHttpInput): Promise<string> {\n const existing = await this.engine.findOne(this.objectName, {\n where: { source: input.source, dedup_key: input.dedupKey },\n fields: ['id'],\n });\n if (existing?.id) return existing.id as string;\n\n const id = randomUUID();\n // `Date`, not epoch-ms: `created_at` / `updated_at` are native TIMESTAMP\n // columns and a real timestamp column rejects a bare number on Postgres.\n const now = new Date();\n const row: DeliveryRow = {\n id,\n source: input.source,\n ref_id: input.refId,\n dedup_key: input.dedupKey,\n label: input.label,\n url: input.url,\n method: input.method ?? 'POST',\n headers_json: input.headers ? JSON.stringify(input.headers) : undefined,\n signing_secret: input.signingSecret,\n timeout_ms: input.timeoutMs,\n payload_json: JSON.stringify(input.payload ?? null),\n partition_key: hashPartition(input.refId, this.partitionCount),\n status: 'pending',\n attempts: 0,\n created_at: now,\n updated_at: now,\n };\n try {\n await this.engine.insert(this.objectName, row);\n return id;\n } catch (err) {\n const winner = await this.engine.findOne(this.objectName, {\n where: { source: input.source, dedup_key: input.dedupKey },\n fields: ['id'],\n });\n if (winner?.id) return winner.id as string;\n throw err;\n }\n }\n\n async claim(opts: HttpClaimOptions): Promise<HttpDelivery[]> {\n const now = opts.now ?? Date.now();\n\n // 1. Reap stale in_flight rows — visibility-timeout recovery.\n await this.engine.update(\n this.objectName,\n { status: 'pending', claimed_by: null, claimed_at: null, updated_at: now },\n {\n where: {\n status: 'in_flight',\n claimed_at: { $lt: now - opts.claimTtlMs },\n },\n multi: true,\n },\n );\n\n // 2. Pick candidate ids.\n const partitionFilter = opts.partition ? { partition_key: opts.partition.index } : {};\n const candidates = await this.engine.find(this.objectName, {\n where: {\n status: 'pending',\n ...partitionFilter,\n $or: [{ next_retry_at: null }, { next_retry_at: { $lte: now } }],\n },\n fields: ['id'],\n limit: opts.limit,\n });\n if (candidates.length === 0) return [];\n\n const ids = (candidates as Array<{ id: string }>).map((c) => c.id);\n\n // 3. Atomic claim. WHERE status='pending' rejects rows another worker took.\n await this.engine.update(\n this.objectName,\n { status: 'in_flight', claimed_by: opts.nodeId, claimed_at: now, updated_at: now },\n { where: { id: { $in: ids }, status: 'pending' }, multi: true },\n );\n\n // 4. Read back the rows we actually own.\n const claimed = (await this.engine.find(this.objectName, {\n where: { id: { $in: ids }, claimed_by: opts.nodeId, claimed_at: now, status: 'in_flight' },\n })) as DeliveryRow[];\n\n return claimed.map((r) => this.toDelivery(r));\n }\n\n async ack(id: string, result: HttpAckResult): Promise<void> {\n const current = (await this.engine.findOne(this.objectName, {\n where: { id },\n fields: ['attempts'],\n })) as { attempts?: number } | null;\n if (!current) return;\n\n const now = Date.now();\n let status: HttpDeliveryStatus;\n let nextRetryAt: number | null;\n let error: string | null;\n\n if (result.success) {\n status = 'success';\n nextRetryAt = null;\n error = null;\n } else if (result.dead) {\n status = 'dead';\n nextRetryAt = null;\n error = result.error ?? null;\n } else {\n status = 'pending';\n nextRetryAt = result.nextRetryAt ?? null;\n error = result.error ?? null;\n }\n\n await this.engine.update(\n this.objectName,\n {\n status,\n attempts: (current.attempts ?? 0) + 1,\n last_attempted_at: now,\n claimed_by: null,\n claimed_at: null,\n response_code: result.httpStatus ?? null,\n response_body: result.responseBody ?? null,\n next_retry_at: nextRetryAt,\n error,\n updated_at: now,\n },\n { where: { id }, multi: false },\n );\n }\n\n async list(filter?: { status?: HttpDeliveryStatus; source?: string }): Promise<HttpDelivery[]> {\n const where: Record<string, unknown> = {};\n if (filter?.status) where.status = filter.status;\n if (filter?.source) where.source = filter.source;\n const rows = (await this.engine.find(this.objectName, { where })) as DeliveryRow[];\n return rows.map((r) => this.toDelivery(r));\n }\n\n async redeliver(id: string): Promise<HttpDelivery> {\n const current = (await this.engine.findOne(this.objectName, { where: { id } })) as DeliveryRow | null;\n if (!current) {\n throw new HttpRedeliverError(`Delivery row '${id}' not found`, 'not_found');\n }\n if (current.status !== 'success' && current.status !== 'failed' && current.status !== 'dead') {\n throw new HttpRedeliverError(\n `Delivery row '${id}' is '${current.status}', expected one of: success, failed, dead`,\n 'not_eligible',\n );\n }\n const now = Date.now();\n await this.engine.update(\n this.objectName,\n {\n status: 'pending',\n attempts: 0,\n claimed_by: null,\n claimed_at: null,\n next_retry_at: null,\n last_attempted_at: null,\n response_code: null,\n response_body: null,\n error: null,\n updated_at: now,\n },\n { where: { id, status: { $in: ['success', 'failed', 'dead'] } }, multi: false },\n );\n const after = (await this.engine.findOne(this.objectName, { where: { id } })) as DeliveryRow | null;\n if (!after || after.status !== 'pending') {\n throw new HttpRedeliverError(`Delivery row '${id}' state changed during redeliver`, 'not_eligible');\n }\n return this.toDelivery(after);\n }\n\n private toDelivery(r: DeliveryRow): HttpDelivery {\n return {\n id: r.id,\n source: r.source,\n refId: r.ref_id,\n dedupKey: r.dedup_key,\n label: r.label ?? undefined,\n url: r.url,\n method: r.method ?? undefined,\n headers: r.headers_json ? JSON.parse(r.headers_json) : undefined,\n signingSecret: r.signing_secret ?? undefined,\n timeoutMs: r.timeout_ms ?? undefined,\n payload: JSON.parse(r.payload_json),\n status: r.status,\n attempts: r.attempts,\n claimedBy: r.claimed_by ?? undefined,\n claimedAt: r.claimed_at ?? undefined,\n nextRetryAt: r.next_retry_at ?? undefined,\n lastAttemptedAt: r.last_attempted_at ?? undefined,\n responseCode: r.response_code ?? undefined,\n responseBody: r.response_body ?? undefined,\n error: r.error ?? undefined,\n createdAt: toEpochMs(r.created_at),\n updatedAt: toEpochMs(r.updated_at),\n };\n }\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Generic outbound-HTTP delivery outbox (ADR-0018 M3).\n *\n * This is the *raw HTTP callout* counterpart to the notification outbox\n * (`outbox.ts`, which is recipient/channel-centric). It stores rows that must\n * be POSTed (or any method) exactly once — modulo at-least-once + receiver-side\n * idempotency — with retry / backoff / dead-letter handled by the shared\n * {@link HttpDispatcher}.\n *\n * It generalises the original `plugin-webhooks` outbox so two callers share one\n * reliable substrate:\n * - the Flow `http` node executor (`@objectstack/service-automation`), and\n * - webhook fan-out (`@objectstack/plugin-webhooks`),\n *\n * which is exactly the \"build the reliability machinery once, reuse it\n * everywhere\" decision in ADR-0018 §4. Webhook-specific concepts collapse onto\n * generic fields: `webhookId`→`refId`, `eventId`→`dedupKey`, `eventType`→`label`,\n * `secret`→`signingSecret`.\n */\n\nexport type HttpDeliveryStatus =\n | 'pending'\n | 'in_flight'\n | 'success'\n | 'failed'\n | 'dead';\n\nexport interface HttpDelivery {\n /** UUID — also doubles as the receiver-side idempotency key (`X-Objectstack-Delivery`). */\n id: string;\n /**\n * Provenance domain — e.g. `'webhook'` | `'flow'`. Combined with `dedupKey`\n * for the uniqueness constraint, and used (with `refId`) for partition\n * affinity so rows from one source/anchor stay in-order.\n */\n source: string;\n /**\n * Partition / ordering anchor within `source` — the webhook id, the flow id,\n * etc. `hash(refId) mod partitionCount` picks the partition.\n */\n refId: string;\n /** UNIQUE(source, dedup_key) prevents double-enqueue. */\n dedupKey: string;\n /**\n * Human/diagnostic label, e.g. an event type (`data.record.created`) or a\n * `flow:node` id. Surfaced on the `X-Objectstack-Event` header when present.\n */\n label?: string;\n /** Destination URL (snapshotted on enqueue — config edits don't rewrite live rows). */\n url: string;\n /** HTTP method — defaults to POST. */\n method?: string;\n /** Custom headers. */\n headers?: Record<string, string>;\n /** HMAC-SHA256 secret. If present, an `X-Objectstack-Signature` header is added. */\n signingSecret?: string;\n /** Per-request timeout in ms. */\n timeoutMs?: number;\n /** JSON-serialisable body. */\n payload: unknown;\n\n /** Lifecycle state. */\n status: HttpDeliveryStatus;\n /** Number of attempts made so far (0 before first attempt). */\n attempts: number;\n /** Node id currently working on this row, when `status = in_flight`. */\n claimedBy?: string;\n /** Wall-clock ms when the row was claimed. */\n claimedAt?: number;\n /** Earliest ms at which this row becomes eligible for the next attempt. */\n nextRetryAt?: number;\n /** Wall-clock ms of the last attempt (success or fail). */\n lastAttemptedAt?: number;\n /** HTTP status code from the most recent attempt. */\n responseCode?: number;\n /** Truncated response body for diagnostics. */\n responseBody?: string;\n /** Last transport / timeout error message. */\n error?: string;\n\n createdAt: number;\n updatedAt: number;\n}\n\nexport interface EnqueueHttpInput {\n source: string;\n refId: string;\n dedupKey: string;\n label?: string;\n url: string;\n method?: string;\n headers?: Record<string, string>;\n signingSecret?: string;\n timeoutMs?: number;\n payload: unknown;\n}\n\nexport interface HttpClaimOptions {\n /** Identifier of the node doing the claim (for `claimedBy`). */\n nodeId: string;\n /** Max rows to claim per call. */\n limit: number;\n /**\n * Partition assignment for this worker. Only rows whose\n * `hash(refId) mod count === index` are claimed. Omit to claim across all\n * partitions (single-node mode).\n */\n partition?: { index: number; count: number };\n /** Visibility timeout — claimed rows revert to pending after this many ms. */\n claimTtlMs: number;\n /** \"Now\" reference, ms since epoch. Defaults to Date.now(). */\n now?: number;\n}\n\nexport interface HttpAckSuccess {\n success: true;\n httpStatus: number;\n responseBody?: string;\n durationMs: number;\n}\n\nexport interface HttpAckFailure {\n success: false;\n httpStatus?: number;\n responseBody?: string;\n error?: string;\n durationMs: number;\n /** Computed by the dispatcher per the retry schedule, or undefined for dead. */\n nextRetryAt?: number;\n /** Marks the row terminal — no more attempts. */\n dead?: boolean;\n}\n\nexport type HttpAckResult = HttpAckSuccess | HttpAckFailure;\n\n/**\n * Error raised by `IHttpOutbox.redeliver` when the requested row is either\n * missing or in a non-terminal state.\n */\nexport class HttpRedeliverError extends Error {\n constructor(\n message: string,\n readonly code: 'not_found' | 'not_eligible',\n ) {\n super(message);\n this.name = 'HttpRedeliverError';\n }\n}\n\n/**\n * Pluggable storage backend for outbound-HTTP delivery rows. Implementations\n * MUST make `claim()` atomic across concurrent callers — that property is the\n * exactly-once guarantee.\n */\nexport interface IHttpOutbox {\n /**\n * Insert a new delivery row. Implementations MUST treat `(source, dedupKey)`\n * as unique and silently converge duplicates. Returns the row id (existing\n * or new).\n */\n enqueue(input: EnqueueHttpInput): Promise<string>;\n\n /**\n * Atomically claim up to `limit` rows whose `nextRetryAt <= now` (or null)\n * and matching the partition predicate. Claimed rows MUST be marked\n * `in_flight` so concurrent claimers don't see them.\n */\n claim(opts: HttpClaimOptions): Promise<HttpDelivery[]>;\n\n /** Record the outcome of an attempt. */\n ack(id: string, result: HttpAckResult): Promise<void>;\n\n /** Snapshot accessor for tests / admin tooling. */\n list(filter?: { status?: HttpDeliveryStatus; source?: string }): Promise<HttpDelivery[]>;\n\n /**\n * Reset a terminal row (`success` / `failed` / `dead`) back to `pending` so\n * the dispatcher re-sends it. Resets `attempts=0`; URL / payload / secret are\n * NOT touched (byte-for-byte replay). Throws {@link HttpRedeliverError}.\n */\n redeliver(id: string): Promise<HttpDelivery>;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Field, ObjectSchema } from '@objectstack/spec/data';\n\n/**\n * `sys_http_delivery` — durable outbox row for one outbound-HTTP attempt\n * (ADR-0018 M3).\n *\n * The raw-callout counterpart to `sys_notification_delivery` (recipient/channel\n * deliveries). Shared by the Flow `http` node executor and webhook fan-out so\n * both inherit retry / idempotency / dead-letter from one substrate. Generalises\n * `sys_webhook_delivery`: `webhook_id`→`ref_id`, `event_id`→`dedup_key`,\n * `event_type`→`label`, `secret`→`signing_secret`.\n *\n * Designed for the SqlHttpOutbox claim algorithm:\n * 1. Producers INSERT pending rows (dedup'd by `(source, dedup_key)`).\n * 2. The per-partition lock-holder runs:\n * SELECT id WHERE status='pending' AND partition_key=? AND (next_retry_at <= now OR null)\n * UPDATE SET status='in_flight' WHERE id IN (...) AND status='pending' ← atomic claim\n * POST to target URL\n * UPDATE SET status=success/pending/dead, attempts=attempts+1, ...\n *\n * `partition_key` is precomputed on enqueue (`hash(ref_id) mod N`) so the\n * dispatcher filters cheaply without DB-side hash functions.\n *\n * @namespace sys\n */\nexport const HttpDelivery = ObjectSchema.create({\n name: 'sys_http_delivery',\n label: 'HTTP Delivery',\n pluralLabel: 'HTTP Deliveries',\n icon: 'globe',\n isSystem: true,\n managedBy: 'system',\n userActions: { create: false, edit: false, delete: false, import: false },\n description:\n 'Durable outbox row for one outbound-HTTP attempt (ADR-0018). Managed by @objectstack/service-messaging; do not write directly.',\n displayNameField: 'id',\n nameField: 'id', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)\n titleFormat: '{label} → {url}',\n compactLayout: ['source', 'url', 'status', 'attempts', 'next_retry_at'],\n\n listViews: {\n recent: {\n type: 'grid',\n name: 'recent',\n label: 'Recent',\n data: { provider: 'object', object: 'sys_http_delivery' },\n columns: ['source', 'label', 'url', 'status', 'attempts', 'response_code', 'updated_at'],\n sort: [{ field: 'updated_at', order: 'desc' }],\n pagination: { pageSize: 50 },\n },\n failures: {\n type: 'grid',\n name: 'failures',\n label: 'Failures',\n data: { provider: 'object', object: 'sys_http_delivery' },\n columns: ['source', 'url', 'status', 'attempts', 'response_code', 'error', 'updated_at'],\n filter: [{ field: 'status', operator: 'in', value: ['failed', 'dead'] }],\n sort: [{ field: 'updated_at', order: 'desc' }],\n pagination: { pageSize: 50 },\n },\n pending: {\n type: 'grid',\n name: 'pending',\n label: 'Pending',\n data: { provider: 'object', object: 'sys_http_delivery' },\n columns: ['source', 'url', 'attempts', 'next_retry_at', 'updated_at'],\n filter: [{ field: 'status', operator: 'equals', value: 'pending' }],\n sort: [{ field: 'next_retry_at', order: 'asc' }],\n pagination: { pageSize: 50 },\n },\n },\n\n fields: {\n id: Field.text({\n label: 'Delivery ID',\n required: true,\n maxLength: 64,\n description: 'UUID — also doubles as the receiver-side idempotency key',\n }),\n\n source: Field.text({\n label: 'Source',\n required: true,\n maxLength: 32,\n description: \"Provenance domain, e.g. 'webhook' | 'flow'. UNIQUE(source, dedup_key).\",\n }),\n\n ref_id: Field.text({\n label: 'Ref ID',\n required: true,\n maxLength: 128,\n description: 'Partition/ordering anchor within source (webhook id, flow id, …)',\n }),\n\n dedup_key: Field.text({\n label: 'Dedup Key',\n required: true,\n maxLength: 191,\n description: 'UNIQUE(source, dedup_key) for at-most-once enqueue',\n }),\n\n label: Field.text({\n label: 'Label',\n required: false,\n maxLength: 191,\n description: 'Diagnostic label / event type — surfaced on X-Objectstack-Event',\n }),\n\n url: Field.text({\n label: 'Target URL',\n required: true,\n maxLength: 2048,\n description: 'Snapshotted at enqueue so config edits do not rewrite live rows',\n }),\n\n method: Field.text({ label: 'Method', required: false, maxLength: 10 }),\n headers_json: Field.textarea({ label: 'Headers JSON', required: false }),\n signing_secret: Field.text({ label: 'HMAC Secret', required: false, maxLength: 256 }),\n timeout_ms: Field.number({ label: 'Timeout (ms)', required: false }),\n payload_json: Field.textarea({ label: 'Payload JSON', required: true }),\n\n partition_key: Field.number({\n label: 'Partition',\n required: true,\n description: 'hash(ref_id) mod partitionCount — precomputed for cheap WHERE',\n }),\n\n status: Field.text({\n label: 'Status',\n required: true,\n defaultValue: 'pending',\n maxLength: 16,\n description: 'pending | in_flight | success | failed | dead',\n }),\n\n attempts: Field.number({\n label: 'Attempts',\n required: true,\n defaultValue: 0,\n description: 'Number of attempts made so far',\n }),\n\n claimed_by: Field.text({ label: 'Claimed By', required: false, maxLength: 128 }),\n claimed_at: Field.number({ label: 'Claimed At (ms)', required: false }),\n next_retry_at: Field.number({ label: 'Next Retry At (ms)', required: false }),\n last_attempted_at: Field.number({ label: 'Last Attempted At (ms)', required: false }),\n response_code: Field.number({ label: 'HTTP Status', required: false }),\n response_body: Field.textarea({ label: 'Response Body (capped)', required: false }),\n error: Field.textarea({ label: 'Error', required: false }),\n\n // Builtin audit columns are native TIMESTAMP columns (Postgres/MySQL),\n // so declare them `datetime` and write `Date`s (not epoch-ms numbers,\n // which a real timestamp column rejects). See SqlHttpOutbox.\n created_at: Field.datetime({ label: 'Created At', required: true }),\n updated_at: Field.datetime({ label: 'Updated At', required: true }),\n },\n\n indexes: [\n { fields: ['source', 'dedup_key'], unique: true },\n // Hot path: claim query\n { fields: ['status', 'partition_key', 'next_retry_at'] },\n // Reaper: scan stale in_flight rows by claimed_at\n { fields: ['status', 'claimed_at'] },\n { fields: ['source', 'ref_id'] },\n ],\n});\n\n/** Canonical object name — exported so SqlHttpOutbox callers can override. */\nexport const SYS_HTTP_DELIVERY = 'sys_http_delivery' as const;\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { NotificationDeliveryRecord } from './outbox.js';\n\n/**\n * One collapsed digest message assembled from a window's batched deliveries\n * (ADR-0030 P3b-2). `items` preserves the individual notifications so a\n * structured channel (inbox) can render a list, while `title`/`body` give a\n * flat rendering for plain channels (email text).\n */\nexport interface DigestRenderResult {\n title: string;\n body: string;\n severity: 'info';\n count: number;\n items: Array<{\n notificationId: string;\n title: string;\n body?: string;\n topic?: string;\n actionUrl?: string;\n }>;\n}\n\n/**\n * Collapse same-`(recipient, channel, window)` deliveries into a single message.\n * The caller guarantees `rows` is non-empty and shares a recipient + channel\n * (the digest group). Only non-`critical` notifications are ever batched, so the\n * digest severity is always `info`.\n */\nexport function renderDigest(rows: NotificationDeliveryRecord[]): DigestRenderResult {\n const items = rows.map((r) => {\n const p = r.payload ?? {};\n return {\n notificationId: r.notificationId,\n title: typeof p.title === 'string' && p.title ? p.title : (r.topic ?? 'Notification'),\n body: typeof p.body === 'string' && p.body ? p.body : undefined,\n topic: r.topic,\n actionUrl: typeof p.actionUrl === 'string' && p.actionUrl ? p.actionUrl : undefined,\n };\n });\n const count = items.length;\n const title = count === 1 ? items[0].title : `You have ${count} notifications`;\n const body = items.map((it) => `• ${it.title}`).join('\\n');\n return { title, body, severity: 'info', count, items };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { MessagingChannel, MessagingChannelContext, Notification, SendResult } from './channel.js';\nimport type { INotificationOutbox, NotificationDeliveryRecord } from './outbox.js';\nimport { classifyDeliveryAttempt } from './backoff.js';\nimport { renderDigest } from './digest-render.js';\n\n/** Minimal channel-registry surface the dispatcher needs (MessagingService satisfies it). */\nexport interface ChannelRegistry {\n getChannel(id: string): MessagingChannel | undefined;\n}\n\n/** A held lock; `release()` frees it, `isHeld()`/`renew()` mirror the cluster API. */\nexport interface DispatchLockHandle {\n release(): Promise<void> | void;\n isHeld?(): boolean;\n renew?(ttlMs: number): Promise<void> | void;\n}\n\n/** Just the slice of `IClusterService` the dispatcher uses. */\nexport interface DispatchCluster {\n lock: {\n acquire(key: string, opts: { ttlMs: number; waitMs: number }): Promise<DispatchLockHandle | null>;\n };\n}\n\n/**\n * Single-node fallback lock — always grants. Used when no cluster service is\n * registered, so the dispatcher runs correctly (just without cross-node\n * coordination). The per-partition serialization a single process needs is\n * already provided by the `inflightTick` guard + the outbox's atomic claim.\n */\nconst SINGLE_NODE_CLUSTER: DispatchCluster = {\n lock: {\n async acquire() {\n return { release() {}, isHeld: () => true, renew() {} };\n },\n },\n};\n\nexport interface NotificationDispatcherLogger {\n warn: (msg: string, meta?: any) => void;\n info?: (msg: string, meta?: any) => void;\n}\n\nexport interface NotificationDispatcherOptions {\n nodeId: string;\n outbox: INotificationOutbox;\n channels: ChannelRegistry;\n /** Context handed to each channel's `send()` (logger). */\n channelContext: MessagingChannelContext;\n /** Cross-node coordination. Defaults to a single-node always-grant lock. */\n cluster?: DispatchCluster;\n partitionCount?: number;\n batchSize?: number;\n intervalMs?: number;\n lockTtlMs?: number;\n claimTtlMs?: number;\n rng?: () => number;\n /** Injectable clock (ms) for deterministic tests. Defaults to Date.now. */\n now?: () => number;\n logger?: NotificationDispatcherLogger;\n /** Observability hook fired after every attempt. */\n onAttempt?: (delivery: NotificationDeliveryRecord, success: boolean) => void;\n}\n\n/**\n * NotificationDispatcher (ADR-0030 P1) — drains the `sys_notification_delivery`\n * outbox and sends each row through its channel, retrying with backoff and\n * dead-lettering once the budget is exhausted. Structurally mirrors\n * `WebhookDispatcher`: an interval loop walks `partitionCount` partitions, each\n * guarded by a per-partition cluster lock; within a held partition it claims a\n * batch (`pending → in_flight`), sends, and acks.\n *\n * At-least-once: if a channel send succeeds but the ack write fails, the row\n * reverts to pending after the claim TTL and is re-sent — the inbox channel's\n * receipt write is idempotent-friendly, and downstream channels should be too.\n */\nexport class NotificationDispatcher {\n private readonly opts: Required<\n Omit<NotificationDispatcherOptions, 'rng' | 'logger' | 'onAttempt' | 'cluster' | 'now'>\n > &\n Pick<NotificationDispatcherOptions, 'rng' | 'logger' | 'onAttempt' | 'now'> & { cluster: DispatchCluster };\n private timer: ReturnType<typeof setInterval> | undefined;\n private running = false;\n private inflightTick: Promise<void> | undefined;\n\n constructor(options: NotificationDispatcherOptions) {\n const intervalMs = options.intervalMs ?? 500;\n const lockTtlMs = options.lockTtlMs ?? intervalMs * 5;\n this.opts = {\n nodeId: options.nodeId,\n outbox: options.outbox,\n channels: options.channels,\n channelContext: options.channelContext,\n cluster: options.cluster ?? SINGLE_NODE_CLUSTER,\n partitionCount: options.partitionCount ?? 8,\n batchSize: options.batchSize ?? 32,\n intervalMs,\n lockTtlMs,\n claimTtlMs: options.claimTtlMs ?? lockTtlMs * 2,\n rng: options.rng,\n now: options.now,\n logger: options.logger,\n onAttempt: options.onAttempt,\n };\n }\n\n /** Begin the periodic loop. Idempotent. */\n start(): void {\n if (this.running) return;\n this.running = true;\n this.scheduleTick();\n this.timer = setInterval(() => this.scheduleTick(), this.opts.intervalMs);\n // Don't keep the event loop alive solely for the dispatcher.\n (this.timer as { unref?: () => void })?.unref?.();\n }\n\n /** Stop the loop and drain the in-flight tick. */\n async stop(): Promise<void> {\n if (!this.running) return;\n this.running = false;\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = undefined;\n }\n if (this.inflightTick) {\n try { await this.inflightTick; } catch { /* already logged */ }\n }\n }\n\n /** Run one full tick (all partitions). Exposed for deterministic tests. */\n async tick(): Promise<void> {\n await this.runTick();\n }\n\n private scheduleTick(): void {\n if (this.inflightTick) return;\n this.inflightTick = this.runTick()\n .catch((err) => {\n this.opts.logger?.warn?.('notification-dispatcher: tick failed', {\n nodeId: this.opts.nodeId,\n error: (err as Error)?.message ?? String(err),\n });\n })\n .finally(() => { this.inflightTick = undefined; });\n }\n\n private async runTick(): Promise<void> {\n const count = this.opts.partitionCount;\n const offset = stableNodeOffset(this.opts.nodeId, count);\n for (let step = 0; step < count; step++) {\n await this.runPartition((offset + step) % count);\n }\n }\n\n private async runPartition(index: number): Promise<void> {\n const handle = await this.opts.cluster.lock.acquire(`notify.dispatcher.partition.${index}`, {\n ttlMs: this.opts.lockTtlMs,\n waitMs: 0,\n });\n if (!handle) return;\n try {\n const claimed = await this.opts.outbox.claim({\n nodeId: this.opts.nodeId,\n limit: this.opts.batchSize,\n partition: { index, count: this.opts.partitionCount },\n claimTtlMs: this.opts.claimTtlMs,\n });\n if (claimed.length > 0) {\n await handle.renew?.(this.opts.lockTtlMs);\n for (const row of claimed) {\n if (handle.isHeld && !handle.isHeld()) break;\n await this.processRow(row);\n }\n }\n\n // P3b-2 digest pass: collapse due batched rows by group. Runs under\n // the same partition lock — a window's rows share a partition (keyed\n // on digest_key), so exactly one node assembles each digest.\n const digestRows = await this.opts.outbox.claimDigest({\n nodeId: this.opts.nodeId,\n limit: this.opts.batchSize,\n partition: { index, count: this.opts.partitionCount },\n claimTtlMs: this.opts.claimTtlMs,\n });\n if (digestRows.length > 0) {\n await handle.renew?.(this.opts.lockTtlMs);\n for (const group of groupByDigestKey(digestRows)) {\n if (handle.isHeld && !handle.isHeld()) break;\n await this.processDigestGroup(group);\n }\n }\n } finally {\n await handle.release();\n }\n }\n\n /**\n * Send one collapsed message for a `(recipient, channel, window)` group and\n * ack every row in it with that one outcome. On failure the whole group\n * re-defers together (each row keeps its own backoff via its `attempts`).\n */\n private async processDigestGroup(rows: NotificationDeliveryRecord[]): Promise<void> {\n const channelName = rows[0].channel;\n const recipient = rows[0].recipientId;\n const channel = this.opts.channels.getChannel(channelName);\n if (!channel) {\n for (const row of rows) {\n await this.opts.outbox.ack(row.id, { success: false, error: `channel '${channelName}' not registered`, dead: true });\n this.opts.onAttempt?.(row, false);\n }\n return;\n }\n\n const digest = renderDigest(rows);\n const notification: Notification = {\n notificationId: rows[0].notificationId, // representative event id\n organizationId: rows[0].organizationId,\n topic: rows[0].topic,\n title: digest.title,\n body: digest.body,\n severity: 'info',\n recipients: [recipient],\n channels: [channelName],\n payload: { digest: true, count: digest.count, items: digest.items },\n };\n\n let result: SendResult;\n try {\n result = await channel.send(this.opts.channelContext, { notification, channel: channelName, recipient });\n } catch (err) {\n result = { ok: false, error: (err as Error)?.message ?? String(err) };\n }\n\n const errorClass = !result.ok && channel.classifyError ? channel.classifyError(result.error) : undefined;\n const now = this.opts.now?.() ?? Date.now();\n for (const row of rows) {\n const ack = classifyDeliveryAttempt(result, errorClass, row.attempts, now, this.opts.rng);\n await this.opts.outbox.ack(row.id, ack);\n this.opts.onAttempt?.(row, result.ok);\n }\n }\n\n private async processRow(row: NotificationDeliveryRecord): Promise<void> {\n const channel = this.opts.channels.getChannel(row.channel);\n if (!channel) {\n // No transport for this channel → terminal, observable on the row.\n await this.opts.outbox.ack(row.id, {\n success: false,\n error: `channel '${row.channel}' not registered`,\n dead: true,\n });\n this.opts.onAttempt?.(row, false);\n return;\n }\n\n const p = row.payload ?? {};\n const notification: Notification = {\n notificationId: row.notificationId,\n organizationId: row.organizationId,\n topic: row.topic,\n title: typeof p.title === 'string' ? p.title : row.topic ?? '',\n body: typeof p.body === 'string' ? p.body : '',\n severity: (p.severity as Notification['severity']) ?? 'info',\n recipients: [row.recipientId],\n channels: [row.channel],\n actionUrl: typeof p.actionUrl === 'string' ? p.actionUrl : undefined,\n payload: p,\n };\n\n let result: SendResult;\n try {\n result = await channel.send(this.opts.channelContext, {\n notification,\n channel: row.channel,\n recipient: row.recipientId,\n });\n } catch (err) {\n result = { ok: false, error: (err as Error)?.message ?? String(err) };\n }\n\n const errorClass = !result.ok && channel.classifyError ? channel.classifyError(result.error) : undefined;\n const now = this.opts.now?.() ?? Date.now();\n const ack = classifyDeliveryAttempt(result, errorClass, row.attempts, now, this.opts.rng);\n await this.opts.outbox.ack(row.id, ack);\n this.opts.onAttempt?.(row, result.ok);\n }\n}\n\n/** Group claimed digest rows by their `digestKey` (insertion order preserved). */\nfunction groupByDigestKey(rows: NotificationDeliveryRecord[]): NotificationDeliveryRecord[][] {\n const groups = new Map<string, NotificationDeliveryRecord[]>();\n for (const r of rows) {\n const key = r.digestKey ?? r.id; // defensive — claimDigest only returns keyed rows\n let g = groups.get(key);\n if (!g) { g = []; groups.set(key, g); }\n g.push(r);\n }\n return [...groups.values()];\n}\n\n/** Spread the starting partition per node so contention rotates fairly. */\nfunction stableNodeOffset(nodeId: string, partitionCount: number): number {\n let h = 0;\n for (let i = 0; i < nodeId.length; i++) h = (h * 31 + nodeId.charCodeAt(i)) | 0;\n return Math.abs(h) % partitionCount;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { createHmac, randomUUID } from 'node:crypto';\nimport type { HttpAckResult, HttpDelivery } from './http-outbox.js';\n\n/**\n * Pure HTTP transport for the generic outbound-delivery outbox (ADR-0018 M3).\n *\n * Lifted and generalised from `plugin-webhooks/src/http-sender.ts`: a single\n * stateless attempt (`sendOnce`) plus the retry-schedule classifier\n * (`classifyAttempt`). The dispatcher owns claim/ack; this module owns the wire.\n */\n\n/** Default per-request timeout. */\nexport const DEFAULT_HTTP_TIMEOUT_MS = 15_000;\n\n/** Truncate response bodies to keep storage cost predictable. */\nconst RESPONSE_BODY_CAP = 16 * 1024;\n\nexport type FetchImpl = (\n input: string,\n init: {\n method: string;\n headers: Record<string, string>;\n body: string;\n signal: AbortSignal;\n },\n) => Promise<{\n ok: boolean;\n status: number;\n text(): Promise<string>;\n}>;\n\n/** Single HTTP attempt classified to an ack shape (without nextRetryAt). */\nexport type HttpAttemptOutcome =\n | { success: true; httpStatus: number; responseBody?: string; durationMs: number }\n | {\n success: false;\n retriable: boolean;\n httpStatus?: number;\n responseBody?: string;\n error?: string;\n durationMs: number;\n };\n\n/**\n * Send one HTTP attempt for the delivery. Pure (no DB writes) so the dispatcher\n * owns retry-schedule + ack logic.\n *\n * - 2xx → success\n * - 4xx (except 408/429) → permanent failure (retriable = false → dead)\n * - 408, 429, 5xx, transport → retriable\n */\nexport async function sendOnce(\n delivery: HttpDelivery,\n fetchImpl: FetchImpl,\n): Promise<HttpAttemptOutcome> {\n const body =\n typeof delivery.payload === 'string'\n ? delivery.payload\n : JSON.stringify(delivery.payload ?? null);\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'User-Agent': 'ObjectStack-Http/1.0',\n 'X-Objectstack-Delivery': delivery.id,\n 'X-Objectstack-Attempt': String(delivery.attempts + 1),\n ...(delivery.label ? { 'X-Objectstack-Event': delivery.label } : {}),\n ...(delivery.headers ?? {}),\n };\n if (delivery.signingSecret) {\n const sig = createHmac('sha256', delivery.signingSecret).update(body).digest('hex');\n headers['X-Objectstack-Signature'] = `sha256=${sig}`;\n }\n\n const timeoutMs = delivery.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const start = Date.now();\n try {\n const res = await fetchImpl(delivery.url, {\n method: delivery.method ?? 'POST',\n headers,\n body,\n signal: controller.signal,\n });\n clearTimeout(timer);\n const responseText = await safeReadBody(res);\n const durationMs = Date.now() - start;\n if (res.ok) {\n return { success: true, httpStatus: res.status, responseBody: responseText, durationMs };\n }\n const retriable = res.status === 408 || res.status === 429 || res.status >= 500;\n return {\n success: false,\n retriable,\n httpStatus: res.status,\n responseBody: responseText,\n error: `HTTP ${res.status}`,\n durationMs,\n };\n } catch (err: unknown) {\n clearTimeout(timer);\n const durationMs = Date.now() - start;\n const e = err as { name?: string; message?: string };\n const error = e?.name === 'AbortError' ? `timeout after ${timeoutMs}ms` : e?.message ?? String(err);\n return { success: false, retriable: true, error, durationMs };\n }\n}\n\nasync function safeReadBody(res: { text(): Promise<string> }): Promise<string | undefined> {\n try {\n const text = await res.text();\n return text.length > RESPONSE_BODY_CAP ? text.slice(0, RESPONSE_BODY_CAP) : text;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Stripe-style retry schedule. Returns the next delay (ms) given how many\n * attempts have already happened, or `null` once the budget is exhausted.\n *\n * 1→~1s · 2→~10s · 3→~1m · 4→~10m · 5→~1h · 6→~6h · 7→~24h · 8+→dead\n *\n * Each delay is multiplied by jitter ∈ [0.8, 1.2).\n */\nexport function nextHttpRetryDelayMs(\n attemptsSoFar: number,\n rng: () => number = Math.random,\n): number | null {\n const SCHEDULE = [1_000, 10_000, 60_000, 600_000, 3_600_000, 21_600_000, 86_400_000];\n if (attemptsSoFar < 1 || attemptsSoFar > SCHEDULE.length) return null;\n const base = SCHEDULE[attemptsSoFar - 1];\n const jitter = 0.8 + rng() * 0.4;\n return Math.floor(base * jitter);\n}\n\n/**\n * Compose an {@link HttpAckResult} from an outcome, applying the retry schedule\n * on retriable failures.\n */\nexport function classifyAttempt(\n outcome: HttpAttemptOutcome,\n attemptsSoFar: number,\n now: number = Date.now(),\n rng?: () => number,\n): HttpAckResult {\n if (outcome.success) return outcome;\n if (!outcome.retriable) {\n return {\n success: false,\n httpStatus: outcome.httpStatus,\n responseBody: outcome.responseBody,\n error: outcome.error,\n durationMs: outcome.durationMs,\n dead: true,\n };\n }\n const delay = nextHttpRetryDelayMs(attemptsSoFar + 1, rng);\n if (delay === null) {\n return {\n success: false,\n httpStatus: outcome.httpStatus,\n responseBody: outcome.responseBody,\n error: outcome.error,\n durationMs: outcome.durationMs,\n dead: true,\n };\n }\n return {\n success: false,\n httpStatus: outcome.httpStatus,\n responseBody: outcome.responseBody,\n error: outcome.error,\n durationMs: outcome.durationMs,\n nextRetryAt: now + delay,\n };\n}\n\n/** Generate a fresh delivery id (UUID v4). Exposed for tests. */\nexport function newDeliveryId(): string {\n return randomUUID();\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { DispatchCluster, DispatchLockHandle } from './dispatcher.js';\nimport { classifyAttempt, sendOnce, type FetchImpl } from './http-sender.js';\nimport type { HttpDelivery, IHttpOutbox } from './http-outbox.js';\n\n/**\n * HttpDispatcher (ADR-0018 M3) — drains the generic outbound-HTTP outbox\n * (`sys_http_delivery`) and POSTs each row, retrying with backoff and\n * dead-lettering once the budget is exhausted.\n *\n * Structurally identical to `NotificationDispatcher` / `WebhookDispatcher`: an\n * interval loop walks `partitionCount` partitions, each guarded by a\n * per-partition cluster lock; within a held partition it claims a batch\n * (`pending → in_flight`), sends, and acks. Partition affinity is on the\n * delivery's `refId`, preserving in-order delivery per source anchor.\n *\n * At-least-once: if the POST succeeds but the ack write fails, the row reverts\n * to pending after the claim TTL and is re-posted. Receivers MUST be idempotent\n * on the `X-Objectstack-Delivery` (== row id) header.\n */\n\nconst SINGLE_NODE_CLUSTER: DispatchCluster = {\n lock: {\n async acquire() {\n return { release() {}, isHeld: () => true, renew() {} };\n },\n },\n};\n\nexport interface HttpDispatcherLogger {\n warn: (msg: string, meta?: any) => void;\n info?: (msg: string, meta?: any) => void;\n}\n\nexport interface HttpDispatcherOptions {\n /** Stable id identifying this dispatcher node. */\n nodeId: string;\n /** Outbox backend. */\n outbox: IHttpOutbox;\n /** Cross-node coordination. Defaults to a single-node always-grant lock. */\n cluster?: DispatchCluster;\n /** Partitions to split work across (must match the outbox's). Default 8. */\n partitionCount?: number;\n /** Max rows to claim from each partition per tick. Default 32. */\n batchSize?: number;\n /** Tick interval in ms. Default 500. */\n intervalMs?: number;\n /** Per-partition lock TTL. Default = 5 × intervalMs. */\n lockTtlMs?: number;\n /** Visibility timeout for claimed rows. Default = 2 × lockTtlMs. */\n claimTtlMs?: number;\n /** Override `globalThis.fetch` (tests). */\n fetchImpl?: FetchImpl;\n /** RNG override for the retry-jitter schedule (tests). */\n rng?: () => number;\n /** Injectable clock (ms) for deterministic tests. Defaults to Date.now. */\n now?: () => number;\n /** Logger callback (optional). */\n logger?: HttpDispatcherLogger;\n /** Hook fired after every attempt — observability hook. */\n onAttempt?: (delivery: HttpDelivery, success: boolean) => void;\n}\n\nexport class HttpDispatcher {\n private readonly opts: Required<\n Omit<HttpDispatcherOptions, 'fetchImpl' | 'rng' | 'logger' | 'onAttempt' | 'cluster' | 'now'>\n > &\n Pick<HttpDispatcherOptions, 'fetchImpl' | 'rng' | 'logger' | 'onAttempt' | 'now'> & {\n cluster: DispatchCluster;\n };\n private timer: ReturnType<typeof setInterval> | undefined;\n private running = false;\n private inflightTick: Promise<void> | undefined;\n\n constructor(options: HttpDispatcherOptions) {\n const intervalMs = options.intervalMs ?? 500;\n const lockTtlMs = options.lockTtlMs ?? intervalMs * 5;\n this.opts = {\n nodeId: options.nodeId,\n outbox: options.outbox,\n cluster: options.cluster ?? SINGLE_NODE_CLUSTER,\n partitionCount: options.partitionCount ?? 8,\n batchSize: options.batchSize ?? 32,\n intervalMs,\n lockTtlMs,\n claimTtlMs: options.claimTtlMs ?? lockTtlMs * 2,\n fetchImpl: options.fetchImpl,\n rng: options.rng,\n now: options.now,\n logger: options.logger,\n onAttempt: options.onAttempt,\n };\n }\n\n /** Begin the periodic loop. Safe to call once; subsequent calls are no-ops. */\n start(): void {\n if (this.running) return;\n this.running = true;\n this.scheduleTick();\n this.timer = setInterval(() => this.scheduleTick(), this.opts.intervalMs);\n this.timer.unref?.();\n }\n\n /** Stop the loop and wait for the in-flight tick to drain. */\n async stop(): Promise<void> {\n if (!this.running) return;\n this.running = false;\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = undefined;\n }\n if (this.inflightTick) {\n try {\n await this.inflightTick;\n } catch {\n /* swallow — already logged */\n }\n }\n }\n\n /** Run one full tick (all partitions). Exposed for deterministic tests. */\n async tick(): Promise<void> {\n await this.runTick();\n }\n\n private scheduleTick(): void {\n if (this.inflightTick) return;\n this.inflightTick = this.runTick()\n .catch((err) => {\n this.opts.logger?.warn?.('http-dispatcher: tick failed', {\n nodeId: this.opts.nodeId,\n error: (err as Error)?.message ?? String(err),\n });\n })\n .finally(() => {\n this.inflightTick = undefined;\n });\n }\n\n private async runTick(): Promise<void> {\n const partitionCount = this.opts.partitionCount;\n const offset = stableNodeOffset(this.opts.nodeId, partitionCount);\n for (let step = 0; step < partitionCount; step++) {\n const i = (offset + step) % partitionCount;\n await this.runPartition(i);\n }\n }\n\n private async runPartition(index: number): Promise<void> {\n const key = `http.dispatcher.partition.${index}`;\n const handle: DispatchLockHandle | null = await this.opts.cluster.lock.acquire(key, {\n ttlMs: this.opts.lockTtlMs,\n waitMs: 0,\n });\n if (!handle) return;\n\n try {\n const claimed = await this.opts.outbox.claim({\n nodeId: this.opts.nodeId,\n limit: this.opts.batchSize,\n partition: { index, count: this.opts.partitionCount },\n claimTtlMs: this.opts.claimTtlMs,\n now: this.opts.now?.(),\n });\n if (claimed.length === 0) return;\n await handle.renew?.(this.opts.lockTtlMs);\n for (const row of claimed) {\n if (handle.isHeld && !handle.isHeld()) break;\n await this.processRow(row);\n }\n } finally {\n await handle.release();\n }\n }\n\n private async processRow(row: HttpDelivery): Promise<void> {\n const fetchImpl = (this.opts.fetchImpl ?? (globalThis.fetch as unknown as FetchImpl)) as\n | FetchImpl\n | undefined;\n if (!fetchImpl) {\n this.opts.logger?.warn?.('http-dispatcher: no fetch impl available', { rowId: row.id });\n await this.opts.outbox.ack(row.id, {\n success: false,\n error: 'no fetch implementation',\n durationMs: 0,\n dead: true,\n });\n return;\n }\n const outcome = await sendOnce(row, fetchImpl);\n const result = classifyAttempt(outcome, row.attempts, this.opts.now?.() ?? Date.now(), this.opts.rng);\n await this.opts.outbox.ack(row.id, result);\n this.opts.onAttempt?.(row, result.success);\n }\n}\n\n/** Spread starting partition per node so nodes don't serialise on partition 0. */\nfunction stableNodeOffset(nodeId: string, partitionCount: number): number {\n let h = 0;\n for (let i = 0; i < nodeId.length; i++) {\n h = (h * 31 + nodeId.charCodeAt(i)) | 0;\n }\n return Math.abs(h) % partitionCount;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport { NOTIFICATION_EVENT_OBJECT } from './messaging-service.js';\nimport { DELIVERY_OBJECT } from './sql-outbox.js';\nimport { INBOX_OBJECT, RECEIPT_OBJECT } from './inbox-channel.js';\n\n/**\n * Default retention window for the notification pipeline, in days. Default-on as\n * of GA (launch-readiness.md P1-2): 90 days keeps a quarter of in-app history\n * for the bell / audit while bounding `sys_notification` (+ delivery / inbox /\n * receipt) growth. Operators override via `MessagingServicePlugin` options;\n * `0` disables pruning entirely.\n */\nexport const DEFAULT_NOTIFICATION_RETENTION_DAYS = 90;\n\n/** Minimal logger surface (matches the channel/service context). */\ninterface RetentionLogger {\n info(msg: string): void;\n warn(msg: string): void;\n}\n\n/**\n * One object the sweeper prunes, plus the field that carries its age. Every\n * target's `created_at` is a builtin audit column — a native `TIMESTAMP` on\n * Postgres/MySQL — so the cutoff is always an ISO-8601 string. (An earlier\n * version passed an epoch-ms number for the delivery outbox; that compared a\n * bigint to a timestamp column and Postgres rejected it with \"date/time field\n * value out of range\". SQLite's lenient column affinity hid the bug.) The\n * driver coerces the ISO comparand to the column's storage form per dialect.\n */\nexport interface RetentionTarget {\n readonly object: string;\n readonly tsField: string;\n}\n\n/**\n * Default sweep set, ordered leaf-first (materializations/receipts/deliveries\n * before the event) so the log reads top-down even though there are no enforced\n * FKs. A notification ages out **wholesale** — event + delivery + materialization\n * + receipt past the cutoff are all removed — keeping the model consistent (no\n * dangling `notification_id`) and the bell (which only shows recent rows)\n * unaffected.\n */\nexport const DEFAULT_RETENTION_TARGETS: readonly RetentionTarget[] = [\n { object: RECEIPT_OBJECT, tsField: 'created_at' },\n { object: INBOX_OBJECT, tsField: 'created_at' },\n { object: DELIVERY_OBJECT, tsField: 'created_at' },\n { object: NOTIFICATION_EVENT_OBJECT, tsField: 'created_at' },\n];\n\nexport interface NotificationRetentionOptions {\n /** Resolve the data engine; `undefined` ⇒ prune is a no-op. */\n getData(): IDataEngine | undefined;\n logger: RetentionLogger;\n /** Override the swept objects (tests / custom deployments). */\n targets?: readonly RetentionTarget[];\n /** Clock injection for deterministic tests. Defaults to `Date.now()`. */\n now?(): number;\n}\n\n/** Per-object prune outcome. `deleted` is `undefined` when the driver doesn't report a count. */\nexport interface PruneOutcome {\n object: string;\n deleted?: number;\n error?: string;\n}\n\n/**\n * Retention sweeper for the notification pipeline (ADR-0030 hardening).\n *\n * Every `emit()` writes a `sys_notification` event row (plus delivery /\n * materialization / receipt rows), so a high-frequency periodic flow grows the\n * tables unbounded. {@link prune} deletes everything older than a cutoff across\n * all {@link RetentionTarget}s in one bulk `delete` per object, under a system\n * context (cross-tenant: retention is an operator policy). Each target is\n * isolated — one object's failure is logged and the sweep continues.\n *\n * Retention is **opt-in**: the plugin runs this only when `retentionDays` is\n * configured. The mechanism lives here so it's testable in isolation.\n */\nexport class NotificationRetention {\n private readonly now: () => number;\n private readonly targets: readonly RetentionTarget[];\n\n constructor(private readonly opts: NotificationRetentionOptions) {\n this.now = opts.now ?? (() => Date.now());\n this.targets = opts.targets ?? DEFAULT_RETENTION_TARGETS;\n }\n\n /**\n * Delete pipeline rows older than `retentionDays`. Returns one outcome per\n * swept object. No-op (empty result) when no data engine is available or\n * `retentionDays` is not a positive number.\n */\n async prune(retentionDays: number): Promise<PruneOutcome[]> {\n const data = this.opts.getData();\n if (!data) {\n this.opts.logger.warn('[messaging] retention: no data engine; prune skipped');\n return [];\n }\n if (!(retentionDays > 0)) {\n this.opts.logger.warn(`[messaging] retention: invalid retentionDays=${retentionDays}; prune skipped`);\n return [];\n }\n\n const cutoffIso = new Date(this.now() - retentionDays * 86_400_000).toISOString();\n const outcomes: PruneOutcome[] = [];\n\n for (const t of this.targets) {\n try {\n const res = await data.delete(t.object, {\n // ISO-8601 cutoff for every target: `created_at` is a native\n // timestamp column, which rejects a bare epoch-ms number on\n // Postgres. The driver coerces this per dialect on the way down.\n where: { [t.tsField]: { $lt: cutoffIso } },\n multi: true,\n // System context: retention is an operator policy that spans\n // tenants, so it must not be scoped by the caller's RLS.\n context: { isSystem: true },\n } as any);\n const deleted = countDeleted(res);\n outcomes.push({ object: t.object, deleted });\n if (deleted === undefined || deleted > 0) {\n this.opts.logger.info(\n `[messaging] retention: pruned ${deleted ?? '?'} ${t.object} rows older than ${cutoffIso}`,\n );\n }\n } catch (err) {\n const msg = (err as Error)?.message ?? String(err);\n this.opts.logger.warn(`[messaging] retention: prune of ${t.object} failed (${msg}); continuing`);\n outcomes.push({ object: t.object, error: msg });\n }\n }\n return outcomes;\n }\n}\n\n/** Best-effort row-count extraction from a driver's delete result. */\nfunction countDeleted(res: unknown): number | undefined {\n if (typeof res === 'number') return res;\n if (Array.isArray(res)) return res.length;\n if (res && typeof res === 'object') {\n const r = res as Record<string, unknown>;\n for (const k of ['deletedCount', 'deleted', 'count', 'affected', 'affectedRows']) {\n if (typeof r[k] === 'number') return r[k] as number;\n }\n }\n return undefined;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\n\n/** The object notification templates live in. */\nexport const TEMPLATE_OBJECT = 'sys_notification_template';\n\n/** Default locale used when a delivery carries none. */\nexport const DEFAULT_LOCALE = 'en';\n\n/** A loaded template row (the columns the renderer reads). */\nexport interface NotificationTemplateRow {\n subject?: string | null;\n body?: string | null;\n format?: 'markdown' | 'html' | 'text' | 'mjml' | null;\n}\n\n/** The rendered artifact a channel turns into its transport payload. */\nexport interface RenderedNotification {\n subject: string;\n /** Set when the template/format is HTML-ish (html/mjml). */\n html?: string;\n /** Set otherwise (markdown/text, or the no-template fallback). */\n text?: string;\n}\n\n/**\n * Render context: the event payload plus a few top-level conveniences so a\n * template can write `{{ title }}` as well as `{{ payload.title }}`.\n */\nexport interface RenderInput {\n topic: string;\n payload: Record<string, unknown>;\n /** Resolved title/body from the notification (fallback when no template). */\n title?: string;\n body?: string;\n}\n\nconst TOKEN = /\\{\\{\\s*([\\w.$]+)\\s*\\}\\}/g;\n\n/**\n * Declarative `{{ path.to.value }}` interpolation over a context object. No\n * logic, no conditionals — templates stay auditable metadata (a deliberate\n * low-code constraint). An unknown path renders to an empty string. This is the\n * single, linear-time substitution pass; it never evaluates template code.\n */\nexport function interpolate(template: string, context: Record<string, unknown>): string {\n if (!template) return '';\n return template.replace(TOKEN, (_m, path: string) => {\n const v = lookup(context, path);\n return v == null ? '' : String(v);\n });\n}\n\nfunction lookup(ctx: Record<string, unknown>, path: string): unknown {\n let cur: unknown = ctx;\n for (const key of path.split('.')) {\n if (cur == null || typeof cur !== 'object') return undefined;\n cur = (cur as Record<string, unknown>)[key];\n }\n return cur;\n}\n\n/**\n * Render a notification for a channel. With a template, `subject`/`body` are\n * interpolated and `body` is routed to `html` (html/mjml) or `text`\n * (markdown/text). With no template, fall back to the notification's\n * `title`/`body` (the P0/P1 behavior) as `subject`/`text`.\n */\nexport function renderNotification(\n template: NotificationTemplateRow | null | undefined,\n input: RenderInput,\n): RenderedNotification {\n const ctx: Record<string, unknown> = {\n ...input.payload,\n payload: input.payload,\n topic: input.topic,\n title: input.title ?? input.payload.title,\n body: input.body ?? input.payload.body,\n };\n\n if (template && (template.subject || template.body)) {\n const subject = interpolate(String(template.subject ?? ''), ctx) || String(ctx.title ?? input.topic);\n const renderedBody = interpolate(String(template.body ?? ''), ctx);\n const isHtml = template.format === 'html' || template.format === 'mjml';\n return isHtml ? { subject, html: renderedBody } : { subject, text: renderedBody };\n }\n\n // Generic fallback — no template for this (topic, channel, locale).\n return {\n subject: String(ctx.title ?? input.topic),\n text: String(ctx.body ?? ''),\n };\n}\n\nexport interface NotificationTemplateStoreOptions {\n getData(): IDataEngine | undefined;\n objectName?: string;\n}\n\n/**\n * Loads `sys_notification_template` rows by `(topic, channel, locale)`, with a\n * locale fallback (`en-US` → `en` → {@link DEFAULT_LOCALE}). Best-effort: no\n * data engine or a lookup error yields `null` (→ the renderer's generic\n * fallback), never a throw — a template outage must not block delivery.\n */\nexport class NotificationTemplateStore {\n private readonly objectName: string;\n constructor(private readonly opts: NotificationTemplateStoreOptions) {\n this.objectName = opts.objectName ?? TEMPLATE_OBJECT;\n }\n\n async load(topic: string, channel: string, locale?: string): Promise<NotificationTemplateRow | null> {\n const data = this.opts.getData();\n if (!data) return null;\n const candidates = localeCandidates(locale);\n for (const loc of candidates) {\n try {\n const row = await data.findOne(this.objectName, {\n where: { topic, channel, locale: loc, is_active: true },\n fields: ['subject', 'body', 'format'],\n });\n if (row) return row as NotificationTemplateRow;\n } catch {\n return null; // best-effort — fall back to generic rendering\n }\n }\n return null;\n }\n}\n\n/** `en-US` → ['en-US','en', DEFAULT_LOCALE]; dedups, keeps order. */\nfunction localeCandidates(locale?: string): string[] {\n const out: string[] = [];\n const push = (l?: string) => { if (l && !out.includes(l)) out.push(l); };\n push(locale);\n if (locale && locale.includes('-')) push(locale.split('-')[0]);\n push(DEFAULT_LOCALE);\n return out;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport type {\n Delivery,\n ErrorClass,\n MessagingChannel,\n MessagingChannelContext,\n SendResult,\n} from './channel.js';\nimport {\n NotificationTemplateStore,\n renderNotification,\n DEFAULT_LOCALE,\n} from './template-renderer.js';\n\n/** The user identity object a recipient id is resolved to an address against. */\nexport const USER_OBJECT = 'sys_user';\n\n/**\n * Structural view of the email service (`@objectstack/plugin-email`'s\n * `EmailService`), declared locally so service-messaging takes no runtime\n * dependency on it — the channel resolves whatever is registered under the\n * `email` service and sends through this shape (mirrors the `notify` node's\n * `MessagingServiceSurface` pattern).\n */\nexport interface EmailSenderSurface {\n send(input: {\n to: string | string[];\n subject: string;\n html?: string;\n text?: string;\n }): Promise<{ id?: string } | unknown>;\n}\n\nexport interface EmailChannelOptions {\n /** Resolve the email service; `undefined` ⇒ the channel no-ops (not installed). */\n getEmail(): EmailSenderSurface | undefined;\n /** Resolve the data engine (recipient address lookup). */\n getData(): IDataEngine | undefined;\n /** Template store for `(topic, 'email', locale)` rendering. */\n store: NotificationTemplateStore;\n /** User identity object override (default {@link USER_OBJECT}). */\n userObject?: string;\n /** Locale used when the delivery carries none (default {@link DEFAULT_LOCALE}). */\n defaultLocale?: string;\n}\n\nconst EMAIL_SHAPE = (s: string): boolean => {\n // Linear, non-backtracking \"looks like an email\" — same shape as the\n // recipient resolver's check (avoids the ReDoS-prone regex).\n if (!s || /\\s/.test(s)) return false;\n const at = s.indexOf('@');\n if (at <= 0 || at !== s.lastIndexOf('@') || at === s.length - 1) return false;\n const dot = s.slice(at + 1).indexOf('.');\n return dot > 0 && dot < s.length - at - 2;\n};\n\n/**\n * The `email` channel (ADR-0030 P3) — delivers a notification by email.\n *\n * It adds only the messaging semantics on top of the existing email transport\n * (ADR-0022 \"channel delegates transport to a sub-system\"): resolve the\n * recipient's address, render `(topic, 'email', locale)` from\n * `sys_notification_template` (fallback to `payload.title`/`body`), and hand the\n * subject/body to the `email` service. Retry/backoff/dead-letter come for free\n * from the P1 outbox dispatcher.\n *\n * Degrades like the inbox channel: no email service ⇒ logged no-op success\n * (capability not installed); a recipient with no resolvable address ⇒ a\n * reported failure (so the delivery row shows why).\n */\nexport function createEmailChannel(opts: EmailChannelOptions): MessagingChannel {\n const userObject = opts.userObject ?? USER_OBJECT;\n const defaultLocale = opts.defaultLocale ?? DEFAULT_LOCALE;\n\n async function resolveAddress(\n ctx: MessagingChannelContext,\n data: IDataEngine | undefined,\n recipient: string,\n ): Promise<string | undefined> {\n if (EMAIL_SHAPE(recipient)) return recipient; // already an address\n if (!data) return undefined;\n try {\n const user = await data.findOne(userObject, { where: { id: recipient }, fields: ['email'] });\n const email = user?.email;\n return typeof email === 'string' && EMAIL_SHAPE(email) ? email : undefined;\n } catch (err) {\n ctx.logger.warn(`[email] address lookup for '${recipient}' failed (${(err as Error).message})`);\n return undefined;\n }\n }\n\n return {\n id: 'email',\n\n async send(ctx: MessagingChannelContext, delivery: Delivery): Promise<SendResult> {\n const email = opts.getEmail();\n if (!email) {\n ctx.logger.warn(`[email] no email service registered; '${delivery.recipient}' not emailed`);\n return { ok: true }; // capability not installed — no-op, like inbox w/o data\n }\n\n const n = delivery.notification;\n const address = await resolveAddress(ctx, opts.getData(), delivery.recipient);\n if (!address) {\n return { ok: false, error: `no email address for recipient '${delivery.recipient}'` };\n }\n\n const payload = (n.payload ?? {}) as Record<string, unknown>;\n const locale = typeof payload.locale === 'string' ? payload.locale : defaultLocale;\n const template = await opts.store.load(n.topic ?? '', 'email', locale);\n const rendered = renderNotification(template, {\n topic: n.topic ?? '',\n payload,\n title: n.title,\n body: n.body,\n });\n\n try {\n const result: any = await email.send({\n to: address,\n subject: rendered.subject,\n ...(rendered.html !== undefined ? { html: rendered.html } : {}),\n ...(rendered.text !== undefined ? { text: rendered.text } : {}),\n });\n const id = result?.id;\n return { ok: true, externalId: id != null ? String(id) : undefined };\n } catch (err) {\n return { ok: false, error: `email send failed: ${(err as Error).message}` };\n }\n },\n\n classifyError(_err: unknown): ErrorClass {\n return 'retryable';\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_inbox_message` — user-facing in-app notification rows (ADR-0012 §4, §11;\n * ADR-0030 Layer 5).\n *\n * Written by the always-on `inbox` messaging channel, one row per\n * `(notification, recipient)`, as the **materialization** of an L2\n * `sys_notification` event delivered to the in-app channel. The Console bell\n * pulls these (ADR-0030 re-points the bell here from `sys_notification`);\n * `service-realtime` decides when to ping an online user. Belongs to\n * `service-messaging` per the \"protocol + service ownership\" pattern.\n *\n * Read-state is **not** stored here — it lives in `sys_notification_receipt`\n * (ADR-0030), so cross-channel read semantics stay reachable. `notification_id`\n * links back to the event; `delivery_id` links to the outbox row (P1).\n */\nexport const InboxMessage = ObjectSchema.create({\n name: 'sys_inbox_message',\n label: 'Inbox Message',\n pluralLabel: 'Inbox Messages',\n icon: 'inbox',\n description: 'User-facing in-app notification rows materialized by the inbox messaging channel.',\n nameField: 'title', // [ADR-0079] canonical primary-title pointer (single-field titleFormat)\n titleFormat: '{title}',\n compactLayout: ['title', 'user_id', 'severity', 'created_at'],\n\n listViews: {\n mine: {\n type: 'grid',\n name: 'mine',\n label: 'Notifications',\n data: { provider: 'object', object: 'sys_inbox_message' },\n columns: ['title', 'topic', 'severity', 'created_at'],\n filter: [{ field: 'user_id', operator: 'equals', value: '{current_user_id}' }],\n sort: [{ field: 'created_at', order: 'desc' }],\n pagination: { pageSize: 50 },\n emptyState: { title: 'Inbox zero', message: 'No notifications.' },\n },\n },\n\n fields: {\n id: Field.text({\n label: 'Inbox Message ID',\n required: true,\n readonly: true,\n }),\n\n user_id: Field.text({\n label: 'Recipient User',\n required: true,\n searchable: true,\n }),\n\n notification_id: Field.text({\n label: 'Notification Event',\n searchable: true,\n description: 'FK → sys_notification (the L2 event this row materializes)',\n }),\n\n delivery_id: Field.text({\n label: 'Delivery',\n description: 'FK → sys_notification_delivery (outbox row); null until P1',\n }),\n\n topic: Field.text({\n label: 'Topic',\n searchable: true,\n }),\n\n title: Field.text({\n label: 'Title',\n required: true,\n }),\n\n body_md: Field.markdown({\n label: 'Body',\n }),\n\n severity: Field.select({\n label: 'Severity',\n options: [\n { label: 'Info', value: 'info' },\n { label: 'Warning', value: 'warning' },\n { label: 'Critical', value: 'critical' },\n ],\n }),\n\n action_url: Field.text({\n label: 'Action URL',\n }),\n\n created_at: Field.datetime({\n label: 'Created At',\n readonly: true,\n }),\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_notification_receipt` — per-recipient × channel delivery receipt\n * (ADR-0030 Layer 5).\n *\n * The single source of truth for **read-state**. When a channel materializes a\n * delivery it writes a `delivered` receipt; the UI flips it to `read` /\n * `clicked` / `dismissed`. Keeping read-state here (rather than on\n * `sys_inbox_message`) makes cross-channel semantics reachable later — e.g.\n * \"clicked the email → mark the inbox row read\" is a receipt update, not a\n * second source of truth.\n *\n * Keyed by `(notification_id, user_id, channel)`. `delivery_id` links to the\n * `sys_notification_delivery` outbox row once that lands (P1); it is nullable\n * in P0 where the inbox channel dispatches inline.\n *\n * Belongs to `service-messaging` (the owner of the materialization channels).\n */\nexport const NotificationReceipt = ObjectSchema.create({\n name: 'sys_notification_receipt',\n label: 'Notification Receipt',\n pluralLabel: 'Notification Receipts',\n icon: 'check-check',\n isSystem: true,\n managedBy: 'system',\n description: 'Per-recipient × channel receipt; the source of truth for notification read-state.',\n titleFormat: '{state}',\n compactLayout: ['notification_id', 'user_id', 'channel', 'state', 'at'],\n\n fields: {\n id: Field.text({\n label: 'Receipt ID',\n required: true,\n readonly: true,\n }),\n\n notification_id: Field.text({\n label: 'Notification Event',\n required: true,\n searchable: true,\n description: 'FK → sys_notification (L2 event)',\n }),\n\n delivery_id: Field.text({\n label: 'Delivery',\n required: false,\n description: 'FK → sys_notification_delivery (outbox row); null until P1',\n }),\n\n user_id: Field.text({\n label: 'Recipient User',\n required: true,\n searchable: true,\n }),\n\n channel: Field.text({\n label: 'Channel',\n required: true,\n description: 'Channel id this receipt is for (inbox / email / push / …)',\n }),\n\n state: Field.select(['delivered', 'read', 'clicked', 'dismissed'], {\n label: 'State',\n required: true,\n defaultValue: 'delivered',\n }),\n\n at: Field.datetime({\n label: 'At',\n required: false,\n description: 'When the receipt reached its current state',\n }),\n\n created_at: Field.datetime({\n label: 'Created At',\n readonly: true,\n }),\n },\n\n indexes: [\n { fields: ['notification_id', 'user_id', 'channel'], unique: true },\n { fields: ['user_id', 'state'] },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_notification_delivery` — the durable outbox (ADR-0030 Layer 4).\n *\n * One row per `(event × recipient × channel)`. The spine of reliable delivery:\n * `emit()` writes rows in `pending`; the `NotificationDispatcher` claims them\n * (`pending → in_flight`), sends via the channel, and acks the outcome\n * (`success` / back to `pending` with a `next_attempt_at` for retry / `dead`\n * once the retry budget is exhausted). Mirrors `sys_webhook_delivery`.\n *\n * `payload` snapshots the rendered notification content at enqueue time so a\n * later edit of the L2 event can't rewrite an in-flight delivery (and so the\n * dispatcher needs no second read to send). Scheduling fields (`claimed_at`,\n * `next_attempt_at`, `last_attempted_at`) are epoch ms; the builtin\n * `created_at` / `updated_at` audit columns are native timestamps.\n */\nexport const NotificationDelivery = ObjectSchema.create({\n name: 'sys_notification_delivery',\n label: 'Notification Delivery',\n pluralLabel: 'Notification Deliveries',\n icon: 'send',\n isSystem: true,\n managedBy: 'system',\n description: 'Durable per-recipient × channel delivery outbox (ADR-0030 Layer 4).',\n titleFormat: '{channel} → {recipient_id}',\n compactLayout: ['notification_id', 'recipient_id', 'channel', 'status', 'attempts'],\n\n fields: {\n id: Field.text({ label: 'Delivery ID', required: true, readonly: true }),\n\n notification_id: Field.text({\n label: 'Notification Event',\n required: true,\n searchable: true,\n description: 'FK → sys_notification (L2 event)',\n }),\n recipient_id: Field.text({ label: 'Recipient User', required: true, searchable: true }),\n channel: Field.text({ label: 'Channel', required: true }),\n topic: Field.text({ label: 'Topic', searchable: true }),\n\n // P3b-2 digest: when the recipient's preference batches this channel\n // (`digest: daily|weekly`), the row enqueues deferred to the next window\n // and carries `${recipient}|${channel}|${window}` here. The dispatcher's\n // digest pass collapses all same-key rows into ONE rendered message at\n // window time. Null ⇒ an ordinary (immediate / quiet-hours) delivery.\n digest_key: Field.text({ label: 'Digest Key', searchable: true,\n description: 'recipient|channel|window grouping key for batched (digest) deliveries; null for normal sends.' }),\n\n payload: Field.json({\n label: 'Payload',\n description: 'Snapshot of the rendered notification content for dispatch.',\n }),\n\n status: Field.select(['pending', 'in_flight', 'success', 'failed', 'dead', 'suppressed'], {\n label: 'Status',\n required: true,\n defaultValue: 'pending',\n }),\n\n attempts: Field.number({ label: 'Attempts', defaultValue: 0 }),\n partition_key: Field.number({ label: 'Partition Key', defaultValue: 0 }),\n\n claimed_by: Field.text({ label: 'Claimed By', description: 'Node id while in_flight' }),\n claimed_at: Field.number({ label: 'Claimed At (ms)' }),\n next_attempt_at: Field.number({ label: 'Next Attempt At (ms)' }),\n last_attempted_at: Field.number({ label: 'Last Attempted At (ms)' }),\n error: Field.textarea({ label: 'Error' }),\n\n // Builtin audit columns: the SQL driver provisions `created_at` /\n // `updated_at` as native TIMESTAMP columns (Postgres/MySQL), so they are\n // declared `datetime` and written as `Date`s — a bare epoch-ms number is\n // rejected by a real timestamp column. See SqlNotificationOutbox.\n created_at: Field.datetime({ label: 'Created At', readonly: true }),\n updated_at: Field.datetime({ label: 'Updated At' }),\n },\n\n indexes: [\n // Dedup: one delivery per (event, recipient, channel).\n { fields: ['notification_id', 'recipient_id', 'channel'], unique: true },\n // The hot claim query.\n { fields: ['status', 'partition_key', 'next_attempt_at'] },\n // Stale-in_flight reaper.\n { fields: ['status', 'claimed_at'] },\n { fields: ['notification_id'] },\n // P3b-2: the digest collapse pass — claim due batched rows by group.\n { fields: ['digest_key', 'status', 'next_attempt_at'] },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_notification_preference` — per-user × topic × channel delivery toggle\n * (ADR-0030 Layer 3).\n *\n * The mute/allow matrix the preference filter consults before fan-out. A row\n * declares whether `user_id` wants `topic` on `channel`. Resolution is\n * most-specific-wins with wildcards:\n *\n * (user, topic, channel) → (user, topic, *) → (user, *, channel) →\n * (user, *, *) → ('*', topic, channel) → … → ('*', '*', '*') → default ON\n *\n * `user_id = '*'` rows are the **admin global default**; a real-user row\n * **overrides** it. `topic = '*'` / `channel = '*'` are wildcards. Mandatory\n * topics (configured on the service) bypass this object entirely.\n *\n * Belongs to `service-messaging` (owner of the delivery pipeline).\n */\nexport const NotificationPreference = ObjectSchema.create({\n name: 'sys_notification_preference',\n label: 'Notification Preference',\n pluralLabel: 'Notification Preferences',\n icon: 'bell-ring',\n isSystem: true,\n managedBy: 'system',\n description: 'Per-user × topic × channel notification toggle (mute/allow), with admin-global defaults.',\n titleFormat: '{user_id} · {topic} · {channel}',\n compactLayout: ['user_id', 'topic', 'channel', 'enabled', 'digest'],\n\n fields: {\n id: Field.text({ label: 'Preference ID', required: true, readonly: true }),\n\n user_id: Field.text({\n label: 'User',\n required: true,\n searchable: true,\n description: \"Recipient user id, or '*' for the admin-global default.\",\n }),\n\n topic: Field.text({\n label: 'Topic',\n required: true,\n searchable: true,\n defaultValue: '*',\n description: \"Notification topic, or '*' for all topics.\",\n }),\n\n channel: Field.text({\n label: 'Channel',\n required: true,\n defaultValue: '*',\n description: \"Channel id (inbox/email/push/…), or '*' for all channels.\",\n }),\n\n enabled: Field.boolean({\n label: 'Enabled',\n defaultValue: true,\n description: 'When false, this (user, topic, channel) is muted.',\n }),\n\n digest: Field.select(['none', 'daily', 'weekly'], {\n label: 'Digest',\n required: false,\n defaultValue: 'none',\n description: 'Batch cadence (P3 digest middleware).',\n }),\n\n quiet_hours: Field.json({\n label: 'Quiet Hours',\n required: false,\n description: 'Optional { tz, start, end } window (P3 quiet-hours middleware).',\n }),\n\n created_at: Field.datetime({ label: 'Created At', readonly: true }),\n updated_at: Field.datetime({ label: 'Updated At', required: false }),\n },\n\n indexes: [\n { fields: ['user_id', 'topic', 'channel'], unique: true },\n { fields: ['topic'] },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_notification_subscription` — who is subscribed to a topic (ADR-0030\n * Layer 3).\n *\n * Declares standing interest in a `topic` by a `principal` (`role:x`, `team:x`,\n * `user:id`, or a bare user id). Where a producer emits with `audience:\n * 'subscribers'` (or no explicit audience), the resolver expands the topic's\n * subscriptions into recipients — the opt-in counterpart to the explicit\n * audience most producers pass today.\n *\n * Distinct from `sys_notification_preference`: a subscription says \"include me\n * for this topic\"; a preference says \"but mute it on this channel\".\n *\n * Belongs to `service-messaging`.\n */\nexport const NotificationSubscription = ObjectSchema.create({\n name: 'sys_notification_subscription',\n label: 'Notification Subscription',\n pluralLabel: 'Notification Subscriptions',\n icon: 'rss',\n isSystem: true,\n managedBy: 'system',\n description: 'Standing subscription of a principal (role/team/user) to a notification topic.',\n titleFormat: '{principal} · {topic}',\n compactLayout: ['topic', 'principal', 'enabled', 'created_at'],\n\n fields: {\n id: Field.text({ label: 'Subscription ID', required: true, readonly: true }),\n\n topic: Field.text({\n label: 'Topic',\n required: true,\n searchable: true,\n description: 'Notification topic this principal subscribes to.',\n }),\n\n principal: Field.text({\n label: 'Principal',\n required: true,\n searchable: true,\n description: \"Subscriber selector: 'role:x' | 'team:x' | 'user:id' | bare user id.\",\n }),\n\n enabled: Field.boolean({\n label: 'Enabled',\n defaultValue: true,\n description: 'When false, the subscription is inactive.',\n }),\n\n created_at: Field.datetime({ label: 'Created At', readonly: true }),\n },\n\n indexes: [\n { fields: ['topic', 'principal'], unique: true },\n { fields: ['topic'] },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_notification_template` — channel-agnostic render template (ADR-0030\n * cross-cutting / P3).\n *\n * One row per `(topic, channel, locale)` carrying the `subject`/`body` a channel\n * renders from the event `payload` (declarative `{{ payload.x }}` interpolation\n * — see `template-renderer.ts`). `format` tells the channel how to treat `body`\n * (markdown/html/text). When no template matches, channels fall back to\n * `payload.title` / `payload.body` (the P0/P1 behavior), so templates are purely\n * additive.\n *\n * Studio-configurable (contributed to the Setup → Configuration nav). Belongs to\n * `service-messaging`.\n */\nexport const NotificationTemplate = ObjectSchema.create({\n name: 'sys_notification_template',\n label: 'Notification Template',\n pluralLabel: 'Notification Templates',\n icon: 'file-text',\n isSystem: true,\n managedBy: 'system',\n description: 'Per (topic × channel × locale) render template for notifications.',\n titleFormat: '{topic} · {channel} · {locale}',\n compactLayout: ['topic', 'channel', 'locale', 'is_active'],\n\n fields: {\n id: Field.text({ label: 'Template ID', required: true, readonly: true }),\n\n topic: Field.text({ label: 'Topic', required: true, searchable: true }),\n\n channel: Field.text({\n label: 'Channel',\n required: true,\n defaultValue: 'email',\n description: 'Channel id this template renders for (email/inbox/push/…).',\n }),\n\n locale: Field.text({\n label: 'Locale',\n required: true,\n defaultValue: 'en',\n description: \"BCP-47 locale, e.g. 'en' / 'en-US' / 'zh-CN'.\",\n }),\n\n version: Field.number({\n label: 'Version',\n required: false,\n defaultValue: 1,\n }),\n\n subject: Field.text({\n label: 'Subject / Title',\n required: false,\n description: 'Rendered into the email subject / inbox title. Supports {{ payload.x }}.',\n }),\n\n body: Field.markdown({\n label: 'Body',\n required: false,\n description: 'Template body. Supports {{ payload.x }}. Interpreted per `format`.',\n }),\n\n format: Field.select(['markdown', 'html', 'text', 'mjml'], {\n label: 'Body Format',\n required: false,\n defaultValue: 'markdown',\n }),\n\n is_active: Field.boolean({\n label: 'Active',\n defaultValue: true,\n description: 'Only active templates are selected at render time.',\n }),\n\n created_at: Field.datetime({ label: 'Created At', readonly: true }),\n updated_at: Field.datetime({ label: 'Updated At', required: false }),\n },\n\n indexes: [\n { fields: ['topic', 'channel', 'locale'] },\n { fields: ['topic'] },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { randomUUID } from 'node:crypto';\nimport type {\n AckResult,\n ClaimOptions,\n DeliveryStatus,\n EnqueueDeliveryInput,\n INotificationOutbox,\n NotificationDeliveryRecord,\n} from './outbox.js';\nimport { hashPartition } from './backoff.js';\n\n/**\n * In-memory {@link INotificationOutbox} — the test/minimal-stack backend.\n * Single-process, so `claim()` is trivially atomic (no DB round-trips). Same\n * semantics as {@link SqlNotificationOutbox} for dedup, reaping, and acks.\n */\nexport class MemoryNotificationOutbox implements INotificationOutbox {\n private readonly rows = new Map<string, NotificationDeliveryRecord>();\n\n constructor(\n private readonly partitionCount = 8,\n /** Injectable clock (ms) for deterministic tests. Defaults to Date.now. */\n private readonly clock: () => number = () => Date.now(),\n ) {}\n\n async enqueue(input: EnqueueDeliveryInput): Promise<string> {\n for (const r of this.rows.values()) {\n if (\n r.notificationId === input.notificationId &&\n r.recipientId === input.recipientId &&\n r.channel === input.channel\n ) {\n return r.id; // dedup\n }\n }\n const id = randomUUID();\n const now = this.clock();\n this.rows.set(id, {\n id,\n notificationId: input.notificationId,\n recipientId: input.recipientId,\n channel: input.channel,\n topic: input.topic,\n payload: input.payload ?? {},\n organizationId: input.organizationId,\n // Digest rows partition by their group key so a window's rows land in\n // one partition and a single node collapses them under its lock.\n partitionKey: hashPartition(input.digestKey ?? input.notificationId, this.partitionCount),\n status: 'pending',\n attempts: 0,\n // Deferred dispatch (quiet-hours / digest, P3): claim() skips pending\n // rows whose nextAttemptAt is still in the future.\n nextAttemptAt: input.notBefore,\n digestKey: input.digestKey,\n createdAt: now,\n updatedAt: now,\n });\n return id;\n }\n\n async claim(opts: ClaimOptions): Promise<NotificationDeliveryRecord[]> {\n const now = opts.now ?? this.clock();\n // Reap stale in_flight.\n for (const r of this.rows.values()) {\n if (r.status === 'in_flight' && (r.claimedAt ?? 0) < now - opts.claimTtlMs) {\n r.status = 'pending';\n r.claimedBy = undefined;\n r.claimedAt = undefined;\n r.updatedAt = now;\n }\n }\n const out: NotificationDeliveryRecord[] = [];\n for (const r of this.rows.values()) {\n if (out.length >= opts.limit) break;\n if (r.status !== 'pending') continue;\n if (r.digestKey != null) continue; // batched rows drain via claimDigest\n if (opts.partition && r.partitionKey !== opts.partition.index) continue;\n if (r.nextAttemptAt != null && r.nextAttemptAt > now) continue;\n r.status = 'in_flight';\n r.claimedBy = opts.nodeId;\n r.claimedAt = now;\n r.updatedAt = now;\n out.push({ ...r });\n }\n return out;\n }\n\n async claimDigest(opts: ClaimOptions): Promise<NotificationDeliveryRecord[]> {\n const now = opts.now ?? this.clock();\n // Reap stale in_flight (same as claim).\n for (const r of this.rows.values()) {\n if (r.status === 'in_flight' && (r.claimedAt ?? 0) < now - opts.claimTtlMs) {\n r.status = 'pending';\n r.claimedBy = undefined;\n r.claimedAt = undefined;\n r.updatedAt = now;\n }\n }\n // Claim every DUE batched row in the partition — a window must be taken\n // whole, so `limit` does not truncate a group here.\n const out: NotificationDeliveryRecord[] = [];\n for (const r of this.rows.values()) {\n if (r.status !== 'pending') continue;\n if (r.digestKey == null) continue;\n if (opts.partition && r.partitionKey !== opts.partition.index) continue;\n if (r.nextAttemptAt != null && r.nextAttemptAt > now) continue;\n r.status = 'in_flight';\n r.claimedBy = opts.nodeId;\n r.claimedAt = now;\n r.updatedAt = now;\n out.push({ ...r });\n }\n return out;\n }\n\n async ack(id: string, result: AckResult): Promise<void> {\n const r = this.rows.get(id);\n if (!r) return;\n const now = this.clock();\n r.attempts += 1;\n r.lastAttemptedAt = now;\n r.claimedBy = undefined;\n r.claimedAt = undefined;\n r.updatedAt = now;\n if (result.success) {\n r.status = 'success';\n r.nextAttemptAt = undefined;\n r.error = undefined;\n } else if (result.suppressed) {\n r.status = 'suppressed';\n r.error = result.error;\n } else if (result.dead) {\n r.status = 'dead';\n r.error = result.error;\n } else {\n r.status = 'pending';\n r.nextAttemptAt = result.nextAttemptAt;\n r.error = result.error;\n }\n }\n\n async list(filter?: { status?: DeliveryStatus; notificationId?: string }): Promise<NotificationDeliveryRecord[]> {\n let rows = [...this.rows.values()];\n if (filter?.status) rows = rows.filter((r) => r.status === filter.status);\n if (filter?.notificationId) rows = rows.filter((r) => r.notificationId === filter.notificationId);\n return rows.map((r) => ({ ...r }));\n }\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { randomUUID } from 'node:crypto';\nimport { hashPartition } from './backoff.js';\nimport {\n HttpRedeliverError,\n type EnqueueHttpInput,\n type HttpAckResult,\n type HttpClaimOptions,\n type HttpDelivery,\n type HttpDeliveryStatus,\n type IHttpOutbox,\n} from './http-outbox.js';\n\n/**\n * In-memory {@link IHttpOutbox} for tests and single-process development.\n * Mirrors `MemoryWebhookOutbox`: atomic-claim semantics come for free from the\n * single-threaded event loop operating on one `Map`. Two instances do NOT share\n * state — pass the same instance to both dispatchers to simulate one DB.\n */\nexport class MemoryHttpOutbox implements IHttpOutbox {\n private readonly rows = new Map<string, HttpDelivery>();\n /** Dedup index keyed by `${source}::${dedupKey}` -> row id. */\n private readonly dedup = new Map<string, string>();\n\n async enqueue(input: EnqueueHttpInput): Promise<string> {\n const dedupKey = `${input.source}::${input.dedupKey}`;\n const existing = this.dedup.get(dedupKey);\n if (existing) return existing;\n\n const id = randomUUID();\n const now = Date.now();\n const row: HttpDelivery = {\n id,\n source: input.source,\n refId: input.refId,\n dedupKey: input.dedupKey,\n label: input.label,\n url: input.url,\n method: input.method ?? 'POST',\n headers: input.headers,\n signingSecret: input.signingSecret,\n timeoutMs: input.timeoutMs,\n payload: input.payload,\n status: 'pending',\n attempts: 0,\n createdAt: now,\n updatedAt: now,\n };\n this.rows.set(id, row);\n this.dedup.set(dedupKey, id);\n return id;\n }\n\n async claim(opts: HttpClaimOptions): Promise<HttpDelivery[]> {\n const now = opts.now ?? Date.now();\n const claimed: HttpDelivery[] = [];\n\n for (const row of this.rows.values()) {\n if (\n row.status === 'in_flight' &&\n row.claimedAt !== undefined &&\n now - row.claimedAt > opts.claimTtlMs\n ) {\n row.status = 'pending';\n row.claimedBy = undefined;\n row.claimedAt = undefined;\n row.updatedAt = now;\n }\n }\n\n for (const row of this.rows.values()) {\n if (claimed.length >= opts.limit) break;\n if (row.status !== 'pending') continue;\n if (row.nextRetryAt !== undefined && row.nextRetryAt > now) continue;\n if (opts.partition) {\n const p = hashPartition(row.refId, opts.partition.count);\n if (p !== opts.partition.index) continue;\n }\n row.status = 'in_flight';\n row.claimedBy = opts.nodeId;\n row.claimedAt = now;\n row.updatedAt = now;\n claimed.push({ ...row });\n }\n return claimed;\n }\n\n async ack(id: string, result: HttpAckResult): Promise<void> {\n const row = this.rows.get(id);\n if (!row) return;\n const now = Date.now();\n row.attempts += 1;\n row.lastAttemptedAt = now;\n row.updatedAt = now;\n row.claimedBy = undefined;\n row.claimedAt = undefined;\n row.responseCode = result.httpStatus;\n row.responseBody = result.responseBody;\n\n let status: HttpDeliveryStatus;\n if (result.success) {\n status = 'success';\n row.nextRetryAt = undefined;\n row.error = undefined;\n } else if (result.dead) {\n status = 'dead';\n row.error = result.error;\n row.nextRetryAt = undefined;\n } else {\n status = 'pending';\n row.error = result.error;\n row.nextRetryAt = result.nextRetryAt;\n }\n row.status = status;\n }\n\n async list(filter?: { status?: HttpDeliveryStatus; source?: string }): Promise<HttpDelivery[]> {\n let all = Array.from(this.rows.values()).map((r) => ({ ...r }));\n if (filter?.status) all = all.filter((r) => r.status === filter.status);\n if (filter?.source) all = all.filter((r) => r.source === filter.source);\n return all;\n }\n\n async redeliver(id: string): Promise<HttpDelivery> {\n const row = this.rows.get(id);\n if (!row) {\n throw new HttpRedeliverError(`Delivery row '${id}' not found`, 'not_found');\n }\n if (row.status !== 'success' && row.status !== 'failed' && row.status !== 'dead') {\n throw new HttpRedeliverError(\n `Delivery row '${id}' is '${row.status}', expected one of: success, failed, dead`,\n 'not_eligible',\n );\n }\n const now = Date.now();\n row.status = 'pending';\n row.attempts = 0;\n row.claimedBy = undefined;\n row.claimedAt = undefined;\n row.nextRetryAt = undefined;\n row.error = undefined;\n row.responseCode = undefined;\n row.responseBody = undefined;\n row.updatedAt = now;\n return { ...row };\n }\n}\n"],"mappings":";AAEA,SAAS,cAAAA,mBAAkB;;;ACS3B,SAAS,eAAe,GAAoB;AACxC,MAAI,CAAC,KAAK,KAAK,KAAK,CAAC,EAAG,QAAO;AAC/B,QAAM,KAAK,EAAE,QAAQ,GAAG;AACxB,MAAI,MAAM,KAAK,OAAO,EAAE,YAAY,GAAG,KAAK,OAAO,EAAE,SAAS,EAAG,QAAO;AACxE,QAAM,SAAS,EAAE,MAAM,KAAK,CAAC;AAC7B,QAAM,MAAM,OAAO,QAAQ,GAAG;AAE9B,SAAO,MAAM,KAAK,MAAM,OAAO,SAAS;AAC5C;AAGO,IAAM,cAAc;AAEpB,IAAM,gBAAgB;AAEtB,IAAM,qBAAqB;AAMlC,IAAM,uBAAuB,CAAC,YAAY,eAAe,eAAe,SAAS,UAAU;AAkDpF,IAAM,oBAAN,MAAwB;AAAA,EAM3B,YAA6B,MAAgC;AAAhC;AACzB,SAAK,aAAa,KAAK,cAAc;AACrC,SAAK,eAAe,KAAK,gBAAgB;AACzC,SAAK,mBAAmB,KAAK,oBAAoB;AACjD,SAAK,cAAc,KAAK,eAAe;AAAA,EAC3C;AAAA;AAAA,EAGA,MAAM,QAAQ,UAAoB,MAAsB,CAAC,GAAsB;AAC3E,UAAM,QAAQ,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,QAAwB;AAC5E,UAAM,OAAO,KAAK,KAAK,QAAQ;AAC/B,UAAM,MAAgB,CAAC;AAEvB,eAAW,QAAQ,OAAO;AACtB,iBAAW,MAAM,MAAM,KAAK,WAAW,MAAM,MAAM,GAAG,GAAG;AACrD,YAAI,GAAI,KAAI,KAAK,EAAE;AAAA,MACvB;AAAA,IACJ;AAEA,WAAO,CAAC,GAAG,IAAI,IAAI,GAAG,CAAC;AAAA,EAC3B;AAAA,EAEA,MAAc,WACV,MACA,MACA,KACiB;AACjB,QAAI,OAAO,SAAS,UAAU;AAE1B,UAAI,QAAQ,OAAO,SAAS,YAAY,aAAa,MAAM;AACvD,eAAO,KAAK,eAAe,KAAK,QAAQ,QAAQ,KAAK,QAAQ,IAAI,IAAI;AAAA,MACzE;AACA,WAAK,KAAK,OAAO,KAAK,2CAA2C,KAAK,UAAU,IAAI,CAAC,WAAW;AAChG,aAAO,CAAC;AAAA,IACZ;AAEA,UAAM,QAAQ,KAAK,KAAK;AACxB,QAAI,CAAC,MAAO,QAAO,CAAC;AAEpB,QAAI,MAAM,WAAW,OAAO,EAAG,QAAO,CAAC,MAAM,MAAM,CAAC,CAAC,EAAE,OAAO,OAAO;AACrE,QAAI,MAAM,WAAW,OAAO,EAAG,QAAO,KAAK,YAAY,MAAM,MAAM,CAAC,GAAG,MAAM,GAAG;AAChF,QAAI,MAAM,WAAW,OAAO,EAAG,QAAO,KAAK,YAAY,MAAM,MAAM,CAAC,GAAG,IAAI;AAC3E,QAAI,MAAM,WAAW,WAAW,GAAG;AAG/B,YAAM,OAAO,MAAM,MAAM,YAAY,MAAM;AAC3C,YAAM,MAAM,KAAK,QAAQ,GAAG;AAC5B,UAAI,MAAM,EAAG,QAAO,KAAK,eAAe,KAAK,MAAM,GAAG,GAAG,GAAG,KAAK,MAAM,MAAM,CAAC,GAAG,IAAI;AACrF,WAAK,KAAK,OAAO,KAAK,yCAAyC,KAAK,YAAY;AAChF,aAAO,CAAC;AAAA,IACZ;AACA,QAAI,eAAe,KAAK,EAAG,QAAO,CAAC,MAAM,KAAK,aAAa,OAAO,IAAI,CAAC;AAEvE,WAAO,CAAC,KAAK;AAAA,EACjB;AAAA;AAAA,EAGA,MAAc,YAAY,MAAc,MAA+B,KAAwC;AAC3G,QAAI,CAAC,QAAQ,CAAC,KAAM,QAAO,CAAC;AAC5B,UAAM,QAAiC,EAAE,KAAK;AAC9C,QAAI,IAAI,eAAgB,OAAM,kBAAkB,IAAI;AACpD,QAAI;AACA,YAAM,OAAO,MAAM,KAAK,KAAK,KAAK,cAAc,EAAE,OAAO,QAAQ,CAAC,SAAS,GAAG,OAAO,IAAM,CAAC;AAC5F,aAAO,QAAQ,IAAI;AAAA,IACvB,SAAS,KAAK;AACV,WAAK,KAAK,OAAO,KAAK,sBAAsB,IAAI,oBAAoB,IAAI,GAAG,CAAC,iBAAiB;AAC7F,aAAO,CAAC;AAAA,IACZ;AAAA,EACJ;AAAA;AAAA,EAGA,MAAc,YAAY,QAAgB,MAAkD;AACxF,QAAI,CAAC,UAAU,CAAC,KAAM,QAAO,CAAC;AAC9B,QAAI;AACA,YAAM,OAAO,MAAM,KAAK,KAAK,KAAK,kBAAkB;AAAA,QAChD,OAAO,EAAE,SAAS,OAAO;AAAA,QACzB,QAAQ,CAAC,SAAS;AAAA,QAClB,OAAO;AAAA,MACX,CAAC;AACD,aAAO,QAAQ,IAAI;AAAA,IACvB,SAAS,KAAK;AACV,WAAK,KAAK,OAAO,KAAK,sBAAsB,MAAM,oBAAoB,IAAI,GAAG,CAAC,iBAAiB;AAC/F,aAAO,CAAC;AAAA,IACZ;AAAA,EACJ;AAAA;AAAA,EAGA,MAAc,eAAe,QAAgB,IAAY,MAAkD;AACvG,QAAI,CAAC,UAAU,CAAC,MAAM,CAAC,KAAM,QAAO,CAAC;AACrC,QAAI;AACA,YAAM,MAAM,MAAM,KAAK,QAAQ,QAAQ,EAAE,OAAO,EAAE,GAAG,GAAG,QAAQ,CAAC,MAAM,GAAG,KAAK,WAAW,EAAE,CAAC;AAC7F,UAAI,CAAC,IAAK,QAAO,CAAC;AAClB,iBAAW,KAAK,KAAK,aAAa;AAC9B,cAAM,IAAI,IAAI,CAAC;AACf,YAAI,OAAO,MAAM,YAAY,EAAE,SAAS,EAAG,QAAO,CAAC,CAAC;AAAA,MACxD;AACA,aAAO,CAAC;AAAA,IACZ,SAAS,KAAK;AACV,WAAK,KAAK,OAAO,KAAK,0BAA0B,MAAM,IAAI,EAAE,oBAAoB,IAAI,GAAG,CAAC,iBAAiB;AACzG,aAAO,CAAC;AAAA,IACZ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,aAAa,OAAe,MAAgD;AACtF,QAAI,CAAC,KAAM,QAAO;AAClB,QAAI;AACA,YAAM,OAAO,MAAM,KAAK,QAAQ,KAAK,YAAY,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;AACrF,YAAM,KAAK,MAAM;AACjB,UAAI,MAAM,QAAQ,OAAO,EAAE,EAAE,SAAS,EAAG,QAAO,OAAO,EAAE;AACzD,WAAK,KAAK,OAAO,KAAK,oBAAoB,KAAK,UAAU,oBAAoB,KAAK,qBAAqB;AACvG,aAAO;AAAA,IACX,SAAS,KAAK;AACV,WAAK,KAAK,OAAO,KAAK,uBAAuB,KAAK,oBAAoB,IAAI,GAAG,CAAC,qBAAqB;AACnG,aAAO;AAAA,IACX;AAAA,EACJ;AACJ;AAGA,SAAS,QAAQ,MAAyB;AACtC,MAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,QAAO,CAAC;AAClC,SAAO,CAAC,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,MAAW,OAAO,GAAG,WAAW,EAAE,CAAC,EAAE,OAAO,OAAO,CAAC,CAAC;AACtF;AAEA,SAAS,IAAI,KAAsB;AAC/B,SAAQ,KAAe,WAAW,OAAO,GAAG;AAChD;;;ACtNO,IAAM,oBAAoB;AA8DjC,IAAM,WAAW;AAgBV,IAAM,qBAAN,MAAyB;AAAA,EAI5B,YAA6B,MAAiC;AAAjC;AACzB,SAAK,aAAa,KAAK,cAAc;AACrC,SAAK,YAAY,KAAK,mBAAmB,CAAC;AAAA,EAC9C;AAAA;AAAA,EAGA,YAAY,OAAwB;AAChC,WAAO,KAAK,UAAU;AAAA,MAAK,CAAC,MACxB,EAAE,SAAS,GAAG,IAAI,MAAM,WAAW,CAAC,IAAI,UAAU;AAAA,IACtD;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OACF,YACA,UACA,KAC2B;AAC3B,UAAM,MAAM,MAA0B,WAAW,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CAAC,GAAG,QAAQ,EAAE,EAAE;AACvG,QAAI,WAAW,WAAW,KAAK,SAAS,WAAW,EAAG,QAAO,CAAC;AAC9D,QAAI,KAAK,YAAY,IAAI,KAAK,EAAG,QAAO,IAAI;AAE5C,UAAM,OAAO,KAAK,KAAK,QAAQ;AAC/B,QAAI,CAAC,KAAM,QAAO,IAAI;AAEtB,QAAI;AACJ,QAAI;AACA,aAAO,MAAM,KAAK,SAAS,MAAM,GAAG;AAAA,IACxC,SAAS,KAAK;AACV,WAAK,KAAK,OAAO;AAAA,QACb,mCAAmC,IAAI,KAAK,aAAaC,KAAI,GAAG,CAAC;AAAA,MACrE;AACA,aAAO,IAAI;AAAA,IACf;AAGA,UAAM,eAAe,IAAI,IAAI,UAAU;AACvC,UAAM,QAAQ,oBAAI,IAAyB;AAC3C,eAAW,KAAK,MAAM;AAClB,YAAM,OAAO,OAAO,EAAE,WAAW,EAAE;AACnC,UAAI,SAAS,YAAY,CAAC,aAAa,IAAI,IAAI,EAAG;AAClD,YAAM,QAAQ,OAAO,EAAE,SAAS,QAAQ;AACxC,YAAM,UAAU,OAAO,EAAE,WAAW,QAAQ;AAC5C,YAAM,IAAI,GAAG,IAAI,IAAI,KAAK,IAAI,OAAO,IAAI;AAAA,QACrC,SAAS,OAAO,EAAE,OAAO;AAAA,QACzB,YAAY,gBAAgB,EAAE,WAAW;AAAA,QACzC,QAAQ,YAAY,EAAE,MAAM;AAAA,MAChC,CAAC;AAAA,IACL;AAEA,UAAM,QAAQ,IAAI,OAAO,KAAK,IAAI;AAClC,UAAM,WAAW,IAAI,aAAa;AAClC,UAAM,UAA8B,CAAC;AACrC,eAAW,aAAa,YAAY;AAChC,YAAM,WAAW,SAAS;AAAA,QACtB,CAAC,YAAY,KAAK,WAAW,OAAO,WAAW,IAAI,OAAO,OAAO,GAAG,WAAW;AAAA,MACnF;AACA,UAAI,SAAS,WAAW,EAAG;AAM3B,UAAI;AACJ,UAAI;AACJ,UAAI,CAAC,UAAU;AACX,cAAM,UAAU,KAAK,cAAc,OAAO,WAAW,IAAI,KAAK;AAC9D,YAAI,SAAS;AACT,gBAAM,IAAI,eAAe,SAAS,OAAO,KAAK,kBAAkB,OAAO,WAAW,IAAI,KAAK,GAAG,EAAE;AAChG,sBAAY,EAAE;AACd,mBAAS,EAAE,QAAQ,EAAE,OAAO;AAAA,QAChC,OAAO;AACH,gBAAM,KAAK,KAAK,kBAAkB,OAAO,WAAW,IAAI,KAAK;AAC7D,sBAAY,KAAK,mBAAmB,IAAI,KAAK,IAAI;AAAA,QACrD;AAAA,MACJ;AACA,YAAM,SAA2B,EAAE,WAAW,UAAU,SAAS;AACjE,UAAI,aAAa,KAAM,QAAO,YAAY;AAC1C,UAAI,OAAQ,QAAO,SAAS;AAC5B,cAAQ,KAAK,MAAM;AAAA,IACvB;AACA,WAAO;AAAA,EACX;AAAA;AAAA,EAGA,MAAc,SAAS,MAAmB,KAA4D;AAGlG,UAAM,OAAgC,CAAC;AACvC,QAAI,IAAI,eAAgB,MAAK,kBAAkB,IAAI;AACnD,UAAM,CAAC,UAAU,QAAQ,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC3C,KAAK,KAAK,KAAK,YAAY,EAAE,OAAO,EAAE,GAAG,MAAM,OAAO,IAAI,MAAM,GAAG,OAAO,IAAM,CAAC;AAAA,MACjF,KAAK,KAAK,KAAK,YAAY,EAAE,OAAO,EAAE,GAAG,MAAM,OAAO,SAAS,GAAG,OAAO,IAAM,CAAC;AAAA,IACpF,CAAC;AACD,WAAO,CAAC,GAAI,YAAY,CAAC,GAAI,GAAI,YAAY,CAAC,CAAE;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,WAAW,OAAiC,MAAc,OAAe,SAA0C;AACvH,eAAW,KAAK,CAAC,MAAM,QAAQ,GAAG;AAC9B,iBAAW,KAAK,CAAC,OAAO,QAAQ,GAAG;AAC/B,mBAAW,KAAK,CAAC,SAAS,QAAQ,GAAG;AACjC,gBAAM,MAAM,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;AACtC,cAAI,QAAQ,OAAW,QAAO;AAAA,QAClC;AAAA,MACJ;AAAA,IACJ;AACA,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,kBAAkB,OAAiC,MAAc,OAAuC;AAC5G,eAAW,KAAK,CAAC,MAAM,QAAQ,GAAG;AAC9B,iBAAW,KAAK,CAAC,OAAO,QAAQ,GAAG;AAC/B,cAAM,MAAM,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,QAAQ,EAAE;AAC7C,YAAI,KAAK,WAAY,QAAO,IAAI;AAAA,MACpC;AAAA,IACJ;AACA,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,cAAc,OAAiC,MAAc,OAA0C;AAC3G,eAAW,KAAK,CAAC,MAAM,QAAQ,GAAG;AAC9B,iBAAW,KAAK,CAAC,OAAO,QAAQ,GAAG;AAC/B,cAAM,MAAM,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,QAAQ,EAAE;AAC7C,YAAI,KAAK,OAAQ,QAAO,IAAI;AAAA,MAChC;AAAA,IACJ;AACA,WAAO;AAAA,EACX;AACJ;AAQA,SAAS,OAAO,GAAqB;AACjC,SAAO,MAAM,QAAQ,MAAM,KAAK,MAAM,OAAO,MAAM;AACvD;AAEA,SAAS,gBAAgB,GAAoC;AACzD,MAAI,IAAS;AACb,MAAI,OAAO,MAAM,UAAU;AACvB,QAAI;AAAE,UAAI,KAAK,MAAM,CAAC;AAAA,IAAG,QAAQ;AAAE,aAAO;AAAA,IAAW;AAAA,EACzD;AACA,MAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,MAAI,EAAE,SAAS,QAAQ,EAAE,OAAO,KAAM,QAAO;AAC7C,SAAO,EAAE,IAAI,EAAE,IAAI,OAAO,OAAO,EAAE,KAAK,GAAG,KAAK,OAAO,EAAE,GAAG,EAAE;AAClE;AAGA,SAAS,YAAY,GAAuC;AACxD,SAAO,MAAM,WAAW,MAAM,WAAW,IAAI;AACjD;AAeO,SAAS,eACZ,SACA,OACA,IACqC;AACrC,QAAM,OAAO,MAAM;AACnB,QAAM,EAAE,cAAc,YAAY,KAAK,IAAI,cAAc,OAAO,IAAI;AACpE,MAAI,YAAY,SAAS;AACrB,UAAM,iBAAiB,OAAO;AAC9B,WAAO,EAAE,WAAW,QAAQ,iBAAiB,KAAQ,QAAQ,KAAK;AAAA,EACtE;AAEA,QAAM,oBAAqB,IAAI,cAAc,KAAM;AACnD,QAAM,eAAe,mBAAmB,OAAO;AAC/C,QAAM,WAAW,SAAS,aAAa,KAAK;AAC5C,SAAO,EAAE,WAAW,QAAQ,eAAe,KAAQ,QAAQ,cAAc,UAAU,IAAI,EAAE,KAAK;AAClG;AAQO,SAAS,mBAAmB,YAAwB,OAAmC;AAC1F,QAAM,QAAQ,UAAU,WAAW,KAAK;AACxC,QAAM,MAAM,UAAU,WAAW,GAAG;AACpC,MAAI,SAAS,QAAQ,OAAO,QAAQ,UAAU,IAAK,QAAO;AAE1D,QAAM,MAAM,iBAAiB,OAAO,WAAW,MAAM,KAAK;AAC1D,MAAI;AACJ,MAAI,QAAQ,KAAK;AACb,QAAI,OAAO,SAAS,MAAM,IAAK,YAAW,MAAM;AAAA,EACpD,OAAO;AAEH,QAAI,OAAO,MAAO,YAAW,OAAO,MAAM;AAAA,aACjC,MAAM,IAAK,YAAW,MAAM;AAAA,EACzC;AACA,SAAO,YAAY,OAAO,SAAY,QAAQ,WAAW;AAC7D;AAEA,SAAS,UAAU,GAAgC;AAC/C,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,IAAI,sBAAsB,KAAK,EAAE,KAAK,CAAC;AAC7C,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AACrB,QAAM,MAAM,OAAO,EAAE,CAAC,CAAC;AACvB,MAAI,IAAI,MAAM,MAAM,GAAI,QAAO;AAC/B,SAAO,IAAI,KAAK;AACpB;AAEA,SAAS,iBAAiB,OAAe,IAAoB;AACzD,MAAI;AACA,UAAM,QAAQ,IAAI,KAAK,eAAe,SAAS;AAAA,MAC3C,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,UAAU;AAAA,IACd,CAAC,EAAE,cAAc,IAAI,KAAK,KAAK,CAAC;AAChC,UAAM,OAAO,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,GAAG,SAAS,GAAG,IAAI;AAC1E,UAAM,SAAS,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,GAAG,SAAS,GAAG;AAC1E,WAAO,OAAO,KAAK;AAAA,EACvB,QAAQ;AAEJ,UAAM,IAAI,IAAI,KAAK,KAAK;AACxB,WAAO,EAAE,YAAY,IAAI,KAAK,EAAE,cAAc;AAAA,EAClD;AACJ;AAOA,SAAS,cAAc,OAAe,IAAwE;AAC1G,MAAI;AACA,UAAM,QAAQ,IAAI,KAAK,eAAe,SAAS;AAAA,MAC3C,QAAQ;AAAA,MAAO,MAAM;AAAA,MAAW,OAAO;AAAA,MAAW,KAAK;AAAA,MACvD,MAAM;AAAA,MAAW,QAAQ;AAAA,MAAW,UAAU;AAAA,IAClD,CAAC,EAAE,cAAc,IAAI,KAAK,KAAK,CAAC;AAChC,UAAM,MAAM,CAAC,MAAc,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,SAAS;AACrE,UAAM,IAAI,OAAO,IAAI,MAAM,CAAC;AAC5B,UAAM,KAAK,OAAO,IAAI,OAAO,CAAC;AAC9B,UAAM,IAAI,OAAO,IAAI,KAAK,CAAC;AAC3B,UAAM,OAAO,OAAO,IAAI,MAAM,CAAC,IAAI;AACnC,UAAM,SAAS,OAAO,IAAI,QAAQ,CAAC;AACnC,UAAM,MAAM,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC,EAAE,UAAU;AACvD,WAAO,EAAE,cAAc,OAAO,KAAK,QAAQ,YAAY,QAAQ,IAAI,IAAI,KAAK,MAAM,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG;AAAA,EAClH,QAAQ;AACJ,UAAM,KAAK,IAAI,KAAK,KAAK;AACzB,UAAM,MAAM,GAAG,UAAU;AACzB,WAAO;AAAA,MACH,cAAc,GAAG,YAAY,IAAI,KAAK,GAAG,cAAc;AAAA,MACvD,YAAY,QAAQ,IAAI,IAAI;AAAA,MAC5B,MAAM,GAAG,YAAY,EAAE,MAAM,GAAG,EAAE;AAAA,IACtC;AAAA,EACJ;AACJ;AAEA,SAAS,IAAI,GAAmB;AAC5B,SAAO,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG;AACpC;AAEA,SAASA,KAAI,KAAsB;AAC/B,SAAQ,KAAe,WAAW,OAAO,GAAG;AAChD;;;AC7WO,IAAM,eAAe;AAGrB,IAAM,iBAAiB;AA8BvB,SAAS,mBAAmB,MAA6C;AAC5E,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,gBAAgB,KAAK,iBAAiB;AAC5C,QAAM,MAAM,KAAK,QAAQ,OAAM,oBAAI,KAAK,GAAE,YAAY;AAQtD,iBAAe,sBACX,KACA,MACA,GACa;AACb,QAAI,CAAC,EAAE,eAAgB;AACvB,QAAI;AACA,YAAM,KAAK,OAAO,eAAe;AAAA,QAC7B,iBAAiB,EAAE;AAAA,QACnB,aAAa;AAAA,QACb,SAAS,EAAE;AAAA,QACX,SAAS;AAAA,QACT,OAAO;AAAA,QACP,IAAI,EAAE;AAAA,QACN,iBAAiB,EAAE,kBAAkB;AAAA,QACrC,YAAY,EAAE;AAAA,MAClB,CAAC;AAAA,IACL,SAAS,KAAK;AACV,UAAI,OAAO;AAAA,QACP,+CAA+C,EAAE,MAAM,MAAO,IAAc,OAAO;AAAA,MACvF;AAAA,IACJ;AAAA,EACJ;AAEA,SAAO;AAAA,IACH,IAAI;AAAA,IAEJ,MAAM,KAAK,KAA8B,UAAyC;AAC9E,YAAM,OAAO,KAAK,QAAQ;AAC1B,YAAM,IAAI,SAAS;AAEnB,UAAI,CAAC,MAAM;AACP,YAAI,OAAO;AAAA,UACP,qDAAqD,SAAS,SAAS;AAAA,QAC3E;AACA,eAAO,EAAE,IAAI,KAAK;AAAA,MACtB;AAEA,YAAM,SAAS,SAAS;AACxB,YAAM,KAAK,IAAI;AAEf,YAAM,MAA+B;AAAA,QACjC,SAAS;AAAA,QACT,iBAAiB,EAAE,kBAAkB;AAAA,QACrC,OAAO,EAAE;AAAA,QACT,OAAO,EAAE;AAAA,QACT,SAAS,EAAE;AAAA,QACX,UAAU,EAAE,YAAY;AAAA,QACxB,YAAY,EAAE;AAAA,QACd,iBAAiB,EAAE,kBAAkB;AAAA,QACrC,YAAY;AAAA,MAChB;AAEA,UAAI;AACJ,UAAI;AACA,cAAM,UAAU,MAAM,KAAK,OAAO,YAAY,GAAG;AACjD,cAAM,KAAK,MAAM,QAAQ,OAAO,IAAI,QAAQ,CAAC,GAAG,KAAK,SAAS,MAAM;AACpE,kBAAU,MAAM,OAAO,OAAO,EAAE,IAAI;AAAA,MACxC,SAAS,KAAK;AACV,eAAO,EAAE,IAAI,OAAO,OAAO,wBAAyB,IAAc,OAAO,GAAG;AAAA,MAChF;AAIA,YAAM,sBAAsB,KAAK,MAAM;AAAA,QACnC,gBAAgB,EAAE;AAAA,QAClB;AAAA,QACA,gBAAgB,EAAE;AAAA,QAClB;AAAA,MACJ,CAAC;AAED,aAAO,EAAE,IAAI,MAAM,YAAY,QAAQ;AAAA,IAC3C;AAAA,IAEA,cAAc,MAA2B;AAErC,aAAO;AAAA,IACX;AAAA,EACJ;AACJ;;;ACxHO,IAAM,4BAA4B;AAGzC,IAAM,sBAAsB,oBAAI,IAAI,CAAC,QAAQ,WAAW,WAAW,CAAC;AAQpE,SAAS,kBAAkB,KAAuB;AAC9C,QAAM,IAAI;AACV,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,EAAE,SAAS,WAAW,EAAE,SAAS,kBAAkB,EAAE,SAAS,2BAA4B,QAAO;AACrG,QAAMC,OAAM,OAAO,EAAE,WAAW,EAAE,EAAE,YAAY;AAChD,SACIA,KAAI,SAAS,0BAA0B,KACvCA,KAAI,SAAS,eAAe,KAC5BA,KAAI,SAAS,iBAAiB;AAEtC;AA4HO,IAAM,mBAAN,MAAuB;AAAA,EAQ1B,YAA6B,KAA8B;AAA9B;AAP7B,SAAiB,WAAW,oBAAI,IAA8B;AAQ1D,SAAK,MAAM,IAAI,QAAQ,OAAM,oBAAI,KAAK,GAAE,YAAY;AACpD,SAAK,WACD,IAAI,qBACJ,IAAI,kBAAkB,EAAE,SAAS,MAAM,IAAI,UAAU,GAAG,QAAQ,IAAI,OAAO,CAAC;AAChF,SAAK,cACD,IAAI,sBACJ,IAAI,mBAAmB;AAAA,MACnB,SAAS,MAAM,IAAI,UAAU;AAAA,MAC7B,QAAQ,IAAI;AAAA,MACZ,iBAAiB,IAAI;AAAA,IACzB,CAAC;AACL,SAAK,SAAS,IAAI;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,QAAmC;AACzC,SAAK,SAAS;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,cAAc,QAA2B;AACrC,SAAK,aAAa;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,sBAA+B;AAC3B,WAAO,KAAK,eAAe;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YAAY,OAA0C;AACxD,QAAI,CAAC,KAAK,YAAY;AAClB,YAAM,IAAI,MAAM,wFAAwF;AAAA,IAC5G;AACA,WAAO,KAAK,WAAW,QAAQ,KAAK;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,cAAc,IAAmC;AACnD,QAAI,CAAC,KAAK,YAAY;AAClB,YAAM,IAAI,MAAM,gDAAgD;AAAA,IACpE;AACA,WAAO,KAAK,WAAW,UAAU,EAAE;AAAA,EACvC;AAAA;AAAA,EAGA,MAAM,SAAS,QAAoF;AAC/F,QAAI,CAAC,KAAK,WAAY,QAAO,CAAC;AAC9B,WAAO,KAAK,WAAW,KAAK,MAAM;AAAA,EACtC;AAAA;AAAA,EAGA,gBAAgB,SAAiC;AAC7C,QAAI,KAAK,SAAS,IAAI,QAAQ,EAAE,GAAG;AAC/B,WAAK,IAAI,OAAO,KAAK,wBAAwB,QAAQ,EAAE,iCAAiC;AAAA,IAC5F;AACA,SAAK,SAAS,IAAI,QAAQ,IAAI,OAAO;AACrC,SAAK,IAAI,OAAO,KAAK,mCAAmC,QAAQ,EAAE,EAAE;AAAA,EACxE;AAAA;AAAA,EAGA,kBAAkB,IAAkB;AAChC,SAAK,SAAS,OAAO,EAAE;AAAA,EAC3B;AAAA;AAAA,EAGA,WAAW,IAA0C;AACjD,WAAO,KAAK,SAAS,IAAI,EAAE;AAAA,EAC/B;AAAA;AAAA,EAGA,wBAAkC;AAC9B,WAAO,CAAC,GAAG,KAAK,SAAS,KAAK,CAAC;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,MAAM,UACF,QACA,OAA0D,CAAC,GACa;AACxE,UAAM,OAAO,KAAK,IAAI,UAAU;AAChC,QAAI,CAAC,QAAQ,CAAC,OAAQ,QAAO,EAAE,eAAe,CAAC,GAAG,aAAa,EAAE;AAEjE,UAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,KAAK,SAAS,IAAI,CAAC,GAAG,GAAG;AACzD,UAAM,QAAiC,EAAE,SAAS,OAAO;AACzD,QAAI,KAAK,KAAM,OAAM,QAAQ,KAAK;AAElC,UAAM,CAAC,MAAM,QAAQ,IAAI,MAAM,QAAQ,IAAI;AAAA,MACvC,KAAK,KAAK,cAAc,EAAE,OAAO,SAAS,CAAC,EAAE,OAAO,cAAc,OAAO,OAAO,CAAC,GAAG,MAAM,CAAC;AAAA;AAAA;AAAA,MAG1F,KAAK,KAAK,gBAAgB,EAAE,OAAO,EAAE,SAAS,QAAQ,SAAS,QAAQ,EAAE,CAAC,EACtE,MAAM,MAAM,CAAC,CAAmC;AAAA,IACzD,CAAC;AAID,UAAM,eAAe,oBAAI,IAAoB;AAC7C,eAAW,KAAK,UAAU;AACtB,YAAM,MAAM,GAAG,mBAAmB,OAAO,OAAO,EAAE,eAAe,IAAI;AACrE,UAAI,CAAC,IAAK;AACV,YAAM,QAAQ,OAAO,EAAE,SAAS,WAAW;AAC3C,YAAM,OAAO,aAAa,IAAI,GAAG;AACjC,UAAI,CAAC,QAAS,CAAC,oBAAoB,IAAI,IAAI,KAAK,oBAAoB,IAAI,KAAK,GAAI;AAC7E,qBAAa,IAAI,KAAK,KAAK;AAAA,MAC/B;AAAA,IACJ;AAEA,QAAI,cAAc;AAClB,UAAM,MAA+B,KAAK,IAAI,CAAC,MAAM;AACjD,YAAM,MAAM,GAAG,mBAAmB,OAAO,OAAO,EAAE,eAAe,IAAI;AACrE,YAAM,QAAQ,MAAM,aAAa,IAAI,GAAG,IAAI;AAC5C,YAAM,OAAO,QAAQ,oBAAoB,IAAI,KAAK,IAAI;AACtD,UAAI,CAAC,KAAM,gBAAe;AAC1B,aAAO;AAAA,QACH,IAAI,OAAO,OAAO,EAAE,EAAE;AAAA,QACtB,MAAO,EAAE,SAAoB;AAAA,QAC7B,OAAQ,EAAE,SAAoB;AAAA,QAC9B,MAAO,EAAE,WAAsB;AAAA,QAC/B;AAAA,QACA,WAAY,EAAE,cAAyB;AAAA,QACvC,WAAY,EAAE,cAAyB,KAAK,IAAI;AAAA,MACpD;AAAA,IACJ,CAAC;AAED,UAAM,gBAAgB,KAAK,SAAS,SAAY,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI;AAC5F,WAAO,EAAE,eAAe,YAAY;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,SAAS,QAAgB,KAA0E;AACrG,UAAM,OAAO,KAAK,IAAI,UAAU;AAChC,QAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,OAAQ,QAAO,EAAE,SAAS,MAAM,WAAW,EAAE;AAC3E,UAAM,KAAK,KAAK,IAAI;AACpB,QAAI,YAAY;AAChB,eAAW,MAAM,KAAK;AAClB,YAAM,MAAM,OAAO,MAAM,EAAE;AAC3B,UAAI,CAAC,IAAK;AACV,UAAI;AACA,qBAAa,MAAM,KAAK,kBAAkB,MAAM,QAAQ,KAAK,EAAE;AAAA,MACnE,SAAS,KAAK;AACV,aAAK,IAAI,OAAO,KAAK,oCAAoC,GAAG,MAAO,IAAc,OAAO,EAAE;AAAA,MAC9F;AAAA,IACJ;AACA,WAAO,EAAE,SAAS,MAAM,UAAU;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YAAY,QAAkE;AAChF,UAAM,OAAO,KAAK,IAAI,UAAU;AAChC,QAAI,CAAC,QAAQ,CAAC,OAAQ,QAAO,EAAE,SAAS,MAAM,WAAW,EAAE;AAC3D,UAAM,EAAE,cAAc,IAAI,MAAM,KAAK,UAAU,QAAQ,EAAE,MAAM,OAAO,OAAO,IAAI,CAAC;AAClF,WAAO,KAAK,SAAS,QAAQ,cAAc,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAAA,EAC/D;AAAA;AAAA,EAGA,MAAc,kBACV,MACA,QACA,gBACA,IACe;AACf,UAAM,QAAQ,EAAE,iBAAiB,gBAAgB,SAAS,QAAQ,SAAS,QAAQ;AACnF,UAAM,aAAa,YAA8B;AAC7C,YAAM,WAAW,MAAM,KAAK,QAAQ,gBAAgB,EAAE,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;AAC7E,UAAI,CAAC,UAAU,GAAI,QAAO;AAC1B,YAAM,KAAK,OAAO,gBAAgB,EAAE,OAAO,QAAQ,GAAG,GAAG,EAAE,OAAO,EAAE,IAAI,SAAS,GAAG,EAAE,CAAU;AAChG,aAAO;AAAA,IACX;AAEA,QAAI,MAAM,WAAW,EAAG,QAAO;AAO/B,QAAI;AACA,YAAM,KAAK,OAAO,gBAAgB;AAAA,QAC9B,iBAAiB;AAAA,QACjB,aAAa;AAAA,QACb,SAAS;AAAA,QACT,SAAS;AAAA,QACT,OAAO;AAAA,QACP;AAAA,QACA,YAAY;AAAA,MAChB,CAAC;AACD,aAAO;AAAA,IACX,SAAS,KAAK;AACV,UAAI,kBAAkB,GAAG,KAAM,MAAM,WAAW,EAAI,QAAO;AAC3D,YAAM;AAAA,IACV;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,KAAK,OAAuC;AAC9C,UAAM,OAAO,KAAK,IAAI,UAAU;AAGhC,QAAI,MAAM,YAAY,MAAM;AACxB,YAAM,WAAW,MAAM,KAAK,oBAAoB,MAAM,MAAM,QAAQ;AACpE,UAAI,UAAU;AACV,aAAK,IAAI,OAAO;AAAA,UACZ,+BAA+B,MAAM,QAAQ,sBAAsB,QAAQ;AAAA,QAC/E;AACA,eAAO,EAAE,gBAAgB,UAAU,SAAS,MAAM,YAAY,CAAC,GAAG,WAAW,GAAG,QAAQ,EAAE;AAAA,MAC9F;AAAA,IACJ;AAWA,QAAI;AACJ,QAAI;AACA,uBAAiB,MAAM,KAAK,WAAW,MAAM,KAAK;AAAA,IACtD,SAAS,KAAK;AACV,UAAI,MAAM,YAAY,MAAM;AACxB,cAAM,SAAS,MAAM,KAAK,oBAAoB,MAAM,MAAM,QAAQ;AAClE,YAAI,QAAQ;AACR,eAAK,IAAI,OAAO;AAAA,YACZ,+BAA+B,MAAM,QAAQ,yBAAyB,MAAM;AAAA,UAChF;AACA,iBAAO,EAAE,gBAAgB,QAAQ,SAAS,MAAM,YAAY,CAAC,GAAG,WAAW,GAAG,QAAQ,EAAE;AAAA,QAC5F;AAAA,MACJ;AACA,YAAM;AAAA,IACV;AAIA,UAAM,aAAa,MAAM,KAAK,SAAS,QAAQ,MAAM,UAAU;AAAA,MAC3D,gBAAgB,MAAM;AAAA,IAC1B,CAAC;AACD,QAAI,WAAW,WAAW,GAAG;AACzB,WAAK,IAAI,OAAO,KAAK,4BAA4B,MAAM,KAAK,4BAA4B;AACxF,aAAO,EAAE,gBAAgB,SAAS,OAAO,YAAY,CAAC,GAAG,WAAW,GAAG,QAAQ,EAAE;AAAA,IACrF;AAIA,UAAM,UAAU,MAAM,WAAW,CAAC;AAClC,UAAM,WAAW,MAAM,UAAU,SAAS,MAAM,WAAW,CAAC,OAAO;AACnE,UAAM,UAAU,MAAM,KAAK,YAAY,OAAO,YAAY,UAAU;AAAA,MAChE,OAAO,MAAM;AAAA,MACb,gBAAgB,MAAM;AAAA,MACtB,UAAU,MAAM;AAAA,IACpB,CAAC;AACD,QAAI,QAAQ,WAAW,GAAG;AACtB,WAAK,IAAI,OAAO,KAAK,4BAA4B,MAAM,KAAK,+CAA+C;AAC3G,aAAO,EAAE,gBAAgB,SAAS,OAAO,YAAY,CAAC,GAAG,WAAW,GAAG,QAAQ,EAAE;AAAA,IACrF;AAGA,QAAI,KAAK,QAAQ;AACb,YAAMC,cAAa,MAAM,KAAK,kBAAkB,KAAK,QAAQ,gBAAgB,SAAS,OAAO,OAAO;AACpG,YAAMC,aAAYD,YAAW,OAAO,CAAC,MAAM,EAAE,EAAE,EAAE;AACjD,aAAO,EAAE,gBAAgB,SAAS,OAAO,YAAAA,aAAY,WAAAC,YAAW,QAAQD,YAAW,SAASC,WAAU;AAAA,IAC1G;AAEA,UAAM,eAA6B;AAAA,MAC/B;AAAA,MACA,gBAAgB,MAAM;AAAA,MACtB,OAAO,MAAM;AAAA,MACb,OAAO,IAAI,QAAQ,KAAK,KAAK,MAAM;AAAA,MACnC,MAAM,IAAI,QAAQ,IAAI,KAAK;AAAA,MAC3B,UAAU,MAAM,YAAY;AAAA,MAC5B;AAAA,MACA,UAAU,MAAM;AAAA,MAChB,WAAW,aAAa,OAAO,OAAO;AAAA,MACtC,SAAS,MAAM;AAAA,IACnB;AAEA,UAAM,EAAE,YAAY,WAAW,OAAO,IAAI,MAAM,KAAK,OAAO,cAAc,OAAO;AACjF,WAAO,EAAE,gBAAgB,SAAS,OAAO,YAAY,WAAW,OAAO;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,kBACV,QACA,gBACA,SACA,OACA,SAC0B;AAG1B,UAAM,kBAAkB;AAAA,MACpB,GAAG;AAAA,MACH,OAAO,IAAI,QAAQ,KAAK,KAAK,MAAM;AAAA,MACnC,MAAM,IAAI,QAAQ,IAAI,KAAK;AAAA,MAC3B,UAAU,MAAM,YAAY;AAAA,MAC5B,WAAW,aAAa,OAAO,OAAO;AAAA,IAC1C;AACA,UAAM,aAAgC,CAAC;AACvC,eAAW,EAAE,WAAW,UAAU,WAAW,OAAO,KAAK,SAAS;AAC9D,iBAAW,WAAW,UAAU;AAC5B,YAAI;AACA,gBAAM,KAAK,MAAM,OAAO,QAAQ;AAAA,YAC5B;AAAA,YACA,aAAa;AAAA,YACb;AAAA,YACA,OAAO,MAAM;AAAA,YACb,SAAS;AAAA,YACT,gBAAgB,MAAM;AAAA;AAAA;AAAA,YAGtB;AAAA;AAAA;AAAA,YAGA,WAAW,SAAS,GAAG,SAAS,IAAI,OAAO,IAAI,OAAO,MAAM,KAAK;AAAA,UACrE,CAAC;AACD,qBAAW,KAAK,EAAE,SAAS,WAAW,IAAI,MAAM,YAAY,GAAG,CAAC;AAAA,QACpE,SAAS,KAAK;AACV,qBAAW,KAAK,EAAE,SAAS,WAAW,IAAI,OAAO,OAAQ,KAAe,WAAW,OAAO,GAAG,EAAE,CAAC;AAAA,QACpG;AAAA,MACJ;AAAA,IACJ;AACA,WAAO;AAAA,EACX;AAAA;AAAA,EAGA,MAAc,oBAAoB,MAAmB,UAA+C;AAChG,QAAI;AACA,YAAM,MAAM,MAAM,KAAK,QAAQ,2BAA2B;AAAA,QACtD,OAAO,EAAE,WAAW,SAAS;AAAA,QAC7B,QAAQ,CAAC,IAAI;AAAA,MACjB,CAAC;AACD,YAAM,KAAK,KAAK;AAChB,aAAO,MAAM,QAAQ,OAAO,EAAE,EAAE,SAAS,IAAI,OAAO,EAAE,IAAI;AAAA,IAC9D,SAAS,KAAK;AACV,WAAK,IAAI,OAAO,KAAK,oCAAqC,IAAc,OAAO,eAAe;AAC9F,aAAO;AAAA,IACX;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,WAAW,MAA+B,OAAmC;AACvF,QAAI,CAAC,MAAM;AACP,WAAK,IAAI,OAAO,KAAK,4DAA4D;AACjF,aAAO,OAAO,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AAAA,IACrD;AACA,UAAM,MAA+B;AAAA,MACjC,OAAO,MAAM;AAAA,MACb,SAAS,MAAM,WAAW;AAAA,MAC1B,UAAU,MAAM,YAAY;AAAA,MAC5B,WAAW,MAAM,YAAY;AAAA;AAAA;AAAA;AAAA,MAI7B,eAAe,IAAI,MAAM,QAAQ,MAAM,KAAK;AAAA,MAC5C,WAAW,IAAI,MAAM,QAAQ,EAAE,KAAK;AAAA,MACpC,UAAU,MAAM,WAAW;AAAA,MAC3B,iBAAiB,MAAM,kBAAkB;AAAA,MACzC,YAAY,KAAK,IAAI;AAAA,IACzB;AACA,UAAM,UAAU,MAAM,KAAK,OAAO,2BAA2B,GAAG;AAChE,UAAM,KAAK,MAAM,QAAQ,OAAO,IAAI,QAAQ,CAAC,GAAG,KAAK,SAAS,MAAM;AACpE,WAAO,MAAM,OAAO,OAAO,EAAE,IAAI,OAAO,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AAAA,EAC/E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,OACV,cACA,SAC6E;AAC7E,UAAM,aAAgC,CAAC;AAEvC,eAAW,EAAE,WAAW,SAAS,KAAK,SAAS;AAC3C,iBAAW,aAAa,UAAU;AAC9B,cAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,YAAI,CAAC,SAAS;AACV,qBAAW,KAAK;AAAA,YACZ,SAAS;AAAA,YACT;AAAA,YACA,IAAI;AAAA,YACJ,OAAO,YAAY,SAAS;AAAA,UAChC,CAAC;AACD,eAAK,IAAI,OAAO,KAAK,8BAA8B,SAAS,kBAAkB;AAC9E;AAAA,QACJ;AACA,YAAI;AACA,gBAAM,SAAS,MAAM,QAAQ,KAAK,KAAK,KAAK,EAAE,cAAc,SAAS,WAAW,UAAU,CAAC;AAC3F,qBAAW,KAAK;AAAA,YACZ,SAAS;AAAA,YACT;AAAA,YACA,IAAI,OAAO;AAAA,YACX,YAAY,OAAO;AAAA,YACnB,OAAO,OAAO;AAAA,UAClB,CAAC;AAAA,QACL,SAAS,KAAK;AACV,qBAAW,KAAK;AAAA,YACZ,SAAS;AAAA,YACT;AAAA,YACA,IAAI;AAAA,YACJ,OAAQ,KAAe,WAAW,OAAO,GAAG;AAAA,UAChD,CAAC;AAAA,QACL;AAAA,MACJ;AAAA,IACJ;AAEA,UAAM,YAAY,WAAW,OAAO,CAAC,MAAM,EAAE,EAAE,EAAE;AACjD,WAAO,EAAE,YAAY,WAAW,QAAQ,WAAW,SAAS,UAAU;AAAA,EAC1E;AACJ;AAGA,SAAS,IAAI,GAAgC;AACzC,MAAI,KAAK,KAAM,QAAO;AACtB,QAAM,IAAI,OAAO,CAAC;AAClB,SAAO,EAAE,SAAS,IAAI,IAAI;AAC9B;AAUA,SAAS,aAAa,OAAkB,SAAsD;AAC1F,QAAM,WAAW,IAAI,QAAQ,GAAG,KAAK,IAAI,QAAQ,SAAS;AAC1D,MAAI,SAAU,QAAO;AACrB,QAAM,MAAM,IAAI,MAAM,QAAQ,MAAM;AACpC,QAAM,KAAK,IAAI,MAAM,QAAQ,EAAE;AAC/B,SAAO,OAAO,KAAK,IAAI,GAAG,IAAI,EAAE,KAAK;AACzC;;;AC1pBA,SAAS,kBAAkB;;;ACQpB,SAAS,cAAc,KAAa,OAAuB;AAC9D,MAAI,SAAS,EAAG,OAAM,IAAI,MAAM,6BAA6B;AAC7D,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACjC,SAAK,IAAI,WAAW,CAAC;AACrB,QAAI,KAAK,KAAK,GAAG,QAAU;AAAA,EAC/B;AACA,SAAO,KAAK,IAAI,IAAI,CAAC,IAAI;AAC7B;AASO,SAAS,iBAAiB,eAAuB,MAAoB,KAAK,QAAuB;AACpG,QAAM,WAAW,CAAC,KAAO,KAAQ,KAAQ,KAAS,IAAS;AAC3D,MAAI,gBAAgB,KAAK,gBAAgB,SAAS,OAAQ,QAAO;AACjE,QAAM,OAAO,SAAS,gBAAgB,CAAC;AACvC,QAAM,SAAS,MAAM,IAAI,IAAI;AAC7B,SAAO,KAAK,MAAM,OAAO,MAAM;AACnC;AAYO,SAAS,wBACZ,QACA,YACA,eACA,MAAc,KAAK,IAAI,GACvB,KACS;AACT,MAAI,OAAO,GAAI,QAAO,EAAE,SAAS,KAAK;AAEtC,MAAI,eAAe,qBAAqB;AACpC,WAAO,EAAE,SAAS,OAAO,OAAO,OAAO,OAAO,YAAY,KAAK;AAAA,EACnE;AACA,MAAI,eAAe,aAAa;AAC5B,WAAO,EAAE,SAAS,OAAO,OAAO,OAAO,OAAO,MAAM,KAAK;AAAA,EAC7D;AAEA,QAAM,QAAQ,iBAAiB,gBAAgB,GAAG,GAAG;AACrD,MAAI,UAAU,MAAM;AAChB,WAAO,EAAE,SAAS,OAAO,OAAO,OAAO,OAAO,MAAM,KAAK;AAAA,EAC7D;AACA,SAAO,EAAE,SAAS,OAAO,OAAO,OAAO,OAAO,eAAe,MAAM,MAAM;AAC7E;;;ACrDO,SAAS,UAAU,OAAwB;AAC9C,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,iBAAiB,KAAM,QAAO,MAAM,QAAQ;AAChD,MAAI,OAAO,UAAU,UAAU;AAC3B,UAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,QAAI,OAAO,SAAS,MAAM,EAAG,QAAO;AACpC,UAAM,UAAU,OAAO,KAAK;AAC5B,QAAI,OAAO,SAAS,OAAO,EAAG,QAAO;AAAA,EACzC;AACA,SAAO;AACX;;;AFRO,IAAM,kBAAkB;AAyCxB,IAAM,wBAAN,MAA2D;AAAA,EAI9D,YAA6B,QAAqB,MAAoC;AAAzD;AACzB,QAAI,KAAK,kBAAkB,EAAG,OAAM,IAAI,MAAM,mDAAmD;AACjG,SAAK,aAAa,KAAK,cAAc;AACrC,SAAK,iBAAiB,KAAK;AAAA,EAC/B;AAAA,EAEA,MAAM,QAAQ,OAA8C;AACxD,UAAM,QAAQ;AAAA,MACV,iBAAiB,MAAM;AAAA,MACvB,cAAc,MAAM;AAAA,MACpB,SAAS,MAAM;AAAA,IACnB;AACA,UAAM,WAAW,MAAM,KAAK,OAAO,QAAQ,KAAK,YAAY,EAAE,OAAO,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;AAC5F,QAAI,UAAU,GAAI,QAAO,OAAO,SAAS,EAAE;AAE3C,UAAM,KAAK,WAAW;AAGtB,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,MAAmB;AAAA,MACrB;AAAA,MACA,iBAAiB,MAAM;AAAA,MACvB,cAAc,MAAM;AAAA,MACpB,SAAS,MAAM;AAAA,MACf,OAAO,MAAM,SAAS;AAAA,MACtB,SAAS,MAAM,WAAW,CAAC;AAAA,MAC3B,iBAAiB,MAAM,kBAAkB;AAAA;AAAA;AAAA,MAGzC,eAAe,cAAc,MAAM,aAAa,MAAM,gBAAgB,KAAK,cAAc;AAAA,MACzF,QAAQ;AAAA,MACR,UAAU;AAAA;AAAA;AAAA,MAGV,iBAAiB,MAAM,aAAa;AAAA,MACpC,YAAY,MAAM,aAAa;AAAA,MAC/B,YAAY;AAAA,MACZ,YAAY;AAAA,IAChB;AACA,QAAI;AACA,YAAM,KAAK,OAAO,OAAO,KAAK,YAAY,GAAG;AAC7C,aAAO;AAAA,IACX,SAAS,KAAK;AAEV,YAAM,SAAS,MAAM,KAAK,OAAO,QAAQ,KAAK,YAAY,EAAE,OAAO,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;AAC1F,UAAI,QAAQ,GAAI,QAAO,OAAO,OAAO,EAAE;AACvC,YAAM;AAAA,IACV;AAAA,EACJ;AAAA,EAEA,MAAM,MAAM,MAA2D;AACnE,UAAM,MAAM,KAAK,OAAO,KAAK,IAAI;AAGjC,UAAM,KAAK,OAAO;AAAA,MACd,KAAK;AAAA,MACL,EAAE,QAAQ,WAAW,YAAY,MAAM,YAAY,MAAM,YAAY,IAAI;AAAA,MACzE,EAAE,OAAO,EAAE,QAAQ,aAAa,YAAY,EAAE,KAAK,MAAM,KAAK,WAAW,EAAE,GAAG,OAAO,KAAK;AAAA,IAC9F;AAIA,UAAM,kBAAkB,KAAK,YAAY,EAAE,eAAe,KAAK,UAAU,MAAM,IAAI,CAAC;AACpF,UAAM,aAAa,MAAM,KAAK,OAAO,KAAK,KAAK,YAAY;AAAA,MACvD,OAAO;AAAA,QACH,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,GAAG;AAAA,QACH,KAAK,CAAC,EAAE,iBAAiB,KAAK,GAAG,EAAE,iBAAiB,EAAE,MAAM,IAAI,EAAE,CAAC;AAAA,MACvE;AAAA,MACA,QAAQ,CAAC,IAAI;AAAA,MACb,OAAO,KAAK;AAAA,IAChB,CAAC;AACD,QAAI,CAAC,WAAW,OAAQ,QAAO,CAAC;AAChC,UAAM,MAAO,WAAqC,IAAI,CAAC,MAAM,EAAE,EAAE;AAGjE,UAAM,KAAK,OAAO;AAAA,MACd,KAAK;AAAA,MACL,EAAE,QAAQ,aAAa,YAAY,KAAK,QAAQ,YAAY,KAAK,YAAY,IAAI;AAAA,MACjF,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,IAAI,GAAG,QAAQ,UAAU,GAAG,OAAO,KAAK;AAAA,IAClE;AAGA,UAAM,UAAW,MAAM,KAAK,OAAO,KAAK,KAAK,YAAY;AAAA,MACrD,OAAO,EAAE,IAAI,EAAE,KAAK,IAAI,GAAG,YAAY,KAAK,QAAQ,YAAY,KAAK,QAAQ,YAAY;AAAA,IAC7F,CAAC;AACD,WAAO,QAAQ,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC;AAAA,EAC9C;AAAA,EAEA,MAAM,YAAY,MAA2D;AACzE,UAAM,MAAM,KAAK,OAAO,KAAK,IAAI;AAGjC,UAAM,KAAK,OAAO;AAAA,MACd,KAAK;AAAA,MACL,EAAE,QAAQ,WAAW,YAAY,MAAM,YAAY,MAAM,YAAY,IAAI;AAAA,MACzE,EAAE,OAAO,EAAE,QAAQ,aAAa,YAAY,EAAE,KAAK,MAAM,KAAK,WAAW,EAAE,GAAG,OAAO,KAAK;AAAA,IAC9F;AAIA,UAAM,kBAAkB,KAAK,YAAY,EAAE,eAAe,KAAK,UAAU,MAAM,IAAI,CAAC;AACpF,UAAM,aAAa,MAAM,KAAK,OAAO,KAAK,KAAK,YAAY;AAAA,MACvD,OAAO;AAAA,QACH,QAAQ;AAAA,QACR,YAAY,EAAE,KAAK,KAAK;AAAA,QACxB,GAAG;AAAA,QACH,KAAK,CAAC,EAAE,iBAAiB,KAAK,GAAG,EAAE,iBAAiB,EAAE,MAAM,IAAI,EAAE,CAAC;AAAA,MACvE;AAAA,MACA,QAAQ,CAAC,IAAI;AAAA,MACb,OAAO;AAAA,IACX,CAAC;AACD,QAAI,CAAC,WAAW,OAAQ,QAAO,CAAC;AAChC,UAAM,MAAO,WAAqC,IAAI,CAAC,MAAM,EAAE,EAAE;AAGjE,UAAM,KAAK,OAAO;AAAA,MACd,KAAK;AAAA,MACL,EAAE,QAAQ,aAAa,YAAY,KAAK,QAAQ,YAAY,KAAK,YAAY,IAAI;AAAA,MACjF,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,IAAI,GAAG,QAAQ,UAAU,GAAG,OAAO,KAAK;AAAA,IAClE;AAGA,UAAM,UAAW,MAAM,KAAK,OAAO,KAAK,KAAK,YAAY;AAAA,MACrD,OAAO,EAAE,IAAI,EAAE,KAAK,IAAI,GAAG,YAAY,KAAK,QAAQ,YAAY,KAAK,QAAQ,YAAY;AAAA,IAC7F,CAAC;AACD,WAAO,QAAQ,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC;AAAA,EAC9C;AAAA,EAEA,MAAM,IAAI,IAAY,QAAkC;AACpD,UAAM,UAAW,MAAM,KAAK,OAAO,QAAQ,KAAK,YAAY;AAAA,MACxD,OAAO,EAAE,GAAG;AAAA,MACZ,QAAQ,CAAC,UAAU;AAAA,IACvB,CAAC;AACD,QAAI,CAAC,QAAS;AAEd,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI;AACJ,QAAI,gBAA+B;AACnC,QAAI,QAAuB;AAE3B,QAAI,OAAO,SAAS;AAChB,eAAS;AAAA,IACb,WAAW,OAAO,YAAY;AAC1B,eAAS;AACT,cAAQ,OAAO,SAAS;AAAA,IAC5B,WAAW,OAAO,MAAM;AACpB,eAAS;AACT,cAAQ,OAAO,SAAS;AAAA,IAC5B,OAAO;AACH,eAAS;AACT,sBAAgB,OAAO,iBAAiB;AACxC,cAAQ,OAAO,SAAS;AAAA,IAC5B;AAEA,UAAM,KAAK,OAAO;AAAA,MACd,KAAK;AAAA,MACL;AAAA,QACI;AAAA,QACA,WAAW,QAAQ,YAAY,KAAK;AAAA,QACpC,mBAAmB;AAAA,QACnB,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,iBAAiB;AAAA,QACjB;AAAA,QACA,YAAY;AAAA,MAChB;AAAA,MACA,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,MAAM;AAAA,IAClC;AAAA,EACJ;AAAA,EAEA,MAAM,KAAK,QAAsG;AAC7G,UAAM,QAAiC,CAAC;AACxC,QAAI,QAAQ,OAAQ,OAAM,SAAS,OAAO;AAC1C,QAAI,QAAQ,eAAgB,OAAM,kBAAkB,OAAO;AAC3D,UAAM,OAAQ,MAAM,KAAK,OAAO,KAAK,KAAK,YAAY,EAAE,MAAM,CAAC;AAC/D,WAAO,KAAK,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC;AAAA,EAC3C;AAAA,EAEQ,SAAS,GAA4C;AACzD,QAAI,UAAU,EAAE,WAAW,CAAC;AAC5B,QAAI,OAAO,YAAY,UAAU;AAC7B,UAAI;AAAE,kBAAU,KAAK,MAAM,OAAO;AAAA,MAAG,QAAQ;AAAE,kBAAU,CAAC;AAAA,MAAG;AAAA,IACjE;AACA,WAAO;AAAA,MACH,IAAI,EAAE;AAAA,MACN,gBAAgB,EAAE;AAAA,MAClB,aAAa,EAAE;AAAA,MACf,SAAS,EAAE;AAAA,MACX,OAAO,EAAE,SAAS;AAAA,MAClB;AAAA,MACA,gBAAgB,EAAE,mBAAmB;AAAA,MACrC,cAAc,EAAE;AAAA,MAChB,QAAQ,EAAE;AAAA,MACV,UAAU,EAAE;AAAA,MACZ,WAAW,EAAE,cAAc;AAAA,MAC3B,WAAW,EAAE,cAAc;AAAA,MAC3B,eAAe,EAAE,mBAAmB;AAAA,MACpC,iBAAiB,EAAE,qBAAqB;AAAA,MACxC,OAAO,EAAE,SAAS;AAAA,MAClB,WAAW,EAAE,cAAc;AAAA,MAC3B,WAAW,UAAU,EAAE,UAAU;AAAA,MACjC,WAAW,UAAU,EAAE,UAAU;AAAA,IACrC;AAAA,EACJ;AACJ;;;AGxQA,SAAS,cAAAC,mBAAkB;;;AC2IpB,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAC1C,YACI,SACS,MACX;AACE,UAAM,OAAO;AAFJ;AAGT,SAAK,OAAO;AAAA,EAChB;AACJ;;;ACnJA,SAAS,OAAO,oBAAoB;AAyB7B,IAAM,eAAe,aAAa,OAAO;AAAA,EAC5C,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aAAa;AAAA,EACb,MAAM;AAAA,EACN,UAAU;AAAA,EACV,WAAW;AAAA,EACX,aAAa,EAAE,QAAQ,OAAO,MAAM,OAAO,QAAQ,OAAO,QAAQ,MAAM;AAAA,EACxE,aACI;AAAA,EACJ,kBAAkB;AAAA,EAClB,WAAW;AAAA;AAAA,EACX,aAAa;AAAA,EACb,eAAe,CAAC,UAAU,OAAO,UAAU,YAAY,eAAe;AAAA,EAEtE,WAAW;AAAA,IACP,QAAQ;AAAA,MACJ,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM,EAAE,UAAU,UAAU,QAAQ,oBAAoB;AAAA,MACxD,SAAS,CAAC,UAAU,SAAS,OAAO,UAAU,YAAY,iBAAiB,YAAY;AAAA,MACvF,MAAM,CAAC,EAAE,OAAO,cAAc,OAAO,OAAO,CAAC;AAAA,MAC7C,YAAY,EAAE,UAAU,GAAG;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM,EAAE,UAAU,UAAU,QAAQ,oBAAoB;AAAA,MACxD,SAAS,CAAC,UAAU,OAAO,UAAU,YAAY,iBAAiB,SAAS,YAAY;AAAA,MACvF,QAAQ,CAAC,EAAE,OAAO,UAAU,UAAU,MAAM,OAAO,CAAC,UAAU,MAAM,EAAE,CAAC;AAAA,MACvE,MAAM,CAAC,EAAE,OAAO,cAAc,OAAO,OAAO,CAAC;AAAA,MAC7C,YAAY,EAAE,UAAU,GAAG;AAAA,IAC/B;AAAA,IACA,SAAS;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM,EAAE,UAAU,UAAU,QAAQ,oBAAoB;AAAA,MACxD,SAAS,CAAC,UAAU,OAAO,YAAY,iBAAiB,YAAY;AAAA,MACpE,QAAQ,CAAC,EAAE,OAAO,UAAU,UAAU,UAAU,OAAO,UAAU,CAAC;AAAA,MAClE,MAAM,CAAC,EAAE,OAAO,iBAAiB,OAAO,MAAM,CAAC;AAAA,MAC/C,YAAY,EAAE,UAAU,GAAG;AAAA,IAC/B;AAAA,EACJ;AAAA,EAEA,QAAQ;AAAA,IACJ,IAAI,MAAM,KAAK;AAAA,MACX,OAAO;AAAA,MACP,UAAU;AAAA,MACV,WAAW;AAAA,MACX,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,QAAQ,MAAM,KAAK;AAAA,MACf,OAAO;AAAA,MACP,UAAU;AAAA,MACV,WAAW;AAAA,MACX,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,QAAQ,MAAM,KAAK;AAAA,MACf,OAAO;AAAA,MACP,UAAU;AAAA,MACV,WAAW;AAAA,MACX,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,WAAW,MAAM,KAAK;AAAA,MAClB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,WAAW;AAAA,MACX,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,OAAO,MAAM,KAAK;AAAA,MACd,OAAO;AAAA,MACP,UAAU;AAAA,MACV,WAAW;AAAA,MACX,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,KAAK,MAAM,KAAK;AAAA,MACZ,OAAO;AAAA,MACP,UAAU;AAAA,MACV,WAAW;AAAA,MACX,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,QAAQ,MAAM,KAAK,EAAE,OAAO,UAAU,UAAU,OAAO,WAAW,GAAG,CAAC;AAAA,IACtE,cAAc,MAAM,SAAS,EAAE,OAAO,gBAAgB,UAAU,MAAM,CAAC;AAAA,IACvE,gBAAgB,MAAM,KAAK,EAAE,OAAO,eAAe,UAAU,OAAO,WAAW,IAAI,CAAC;AAAA,IACpF,YAAY,MAAM,OAAO,EAAE,OAAO,gBAAgB,UAAU,MAAM,CAAC;AAAA,IACnE,cAAc,MAAM,SAAS,EAAE,OAAO,gBAAgB,UAAU,KAAK,CAAC;AAAA,IAEtE,eAAe,MAAM,OAAO;AAAA,MACxB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,QAAQ,MAAM,KAAK;AAAA,MACf,OAAO;AAAA,MACP,UAAU;AAAA,MACV,cAAc;AAAA,MACd,WAAW;AAAA,MACX,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,UAAU,MAAM,OAAO;AAAA,MACnB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,cAAc;AAAA,MACd,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,YAAY,MAAM,KAAK,EAAE,OAAO,cAAc,UAAU,OAAO,WAAW,IAAI,CAAC;AAAA,IAC/E,YAAY,MAAM,OAAO,EAAE,OAAO,mBAAmB,UAAU,MAAM,CAAC;AAAA,IACtE,eAAe,MAAM,OAAO,EAAE,OAAO,sBAAsB,UAAU,MAAM,CAAC;AAAA,IAC5E,mBAAmB,MAAM,OAAO,EAAE,OAAO,0BAA0B,UAAU,MAAM,CAAC;AAAA,IACpF,eAAe,MAAM,OAAO,EAAE,OAAO,eAAe,UAAU,MAAM,CAAC;AAAA,IACrE,eAAe,MAAM,SAAS,EAAE,OAAO,0BAA0B,UAAU,MAAM,CAAC;AAAA,IAClF,OAAO,MAAM,SAAS,EAAE,OAAO,SAAS,UAAU,MAAM,CAAC;AAAA;AAAA;AAAA;AAAA,IAKzD,YAAY,MAAM,SAAS,EAAE,OAAO,cAAc,UAAU,KAAK,CAAC;AAAA,IAClE,YAAY,MAAM,SAAS,EAAE,OAAO,cAAc,UAAU,KAAK,CAAC;AAAA,EACtE;AAAA,EAEA,SAAS;AAAA,IACL,EAAE,QAAQ,CAAC,UAAU,WAAW,GAAG,QAAQ,KAAK;AAAA;AAAA,IAEhD,EAAE,QAAQ,CAAC,UAAU,iBAAiB,eAAe,EAAE;AAAA;AAAA,IAEvD,EAAE,QAAQ,CAAC,UAAU,YAAY,EAAE;AAAA,IACnC,EAAE,QAAQ,CAAC,UAAU,QAAQ,EAAE;AAAA,EACnC;AACJ,CAAC;AAGM,IAAM,oBAAoB;;;AFxG1B,IAAM,gBAAN,MAA2C;AAAA,EAI9C,YACqB,QACjB,MACF;AAFmB;AAGjB,QAAI,KAAK,kBAAkB,GAAG;AAC1B,YAAM,IAAI,MAAM,2CAA2C;AAAA,IAC/D;AACA,SAAK,aAAa,KAAK,cAAc;AACrC,SAAK,iBAAiB,KAAK;AAAA,EAC/B;AAAA,EAEA,MAAM,QAAQ,OAA0C;AACpD,UAAM,WAAW,MAAM,KAAK,OAAO,QAAQ,KAAK,YAAY;AAAA,MACxD,OAAO,EAAE,QAAQ,MAAM,QAAQ,WAAW,MAAM,SAAS;AAAA,MACzD,QAAQ,CAAC,IAAI;AAAA,IACjB,CAAC;AACD,QAAI,UAAU,GAAI,QAAO,SAAS;AAElC,UAAM,KAAKC,YAAW;AAGtB,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,MAAmB;AAAA,MACrB;AAAA,MACA,QAAQ,MAAM;AAAA,MACd,QAAQ,MAAM;AAAA,MACd,WAAW,MAAM;AAAA,MACjB,OAAO,MAAM;AAAA,MACb,KAAK,MAAM;AAAA,MACX,QAAQ,MAAM,UAAU;AAAA,MACxB,cAAc,MAAM,UAAU,KAAK,UAAU,MAAM,OAAO,IAAI;AAAA,MAC9D,gBAAgB,MAAM;AAAA,MACtB,YAAY,MAAM;AAAA,MAClB,cAAc,KAAK,UAAU,MAAM,WAAW,IAAI;AAAA,MAClD,eAAe,cAAc,MAAM,OAAO,KAAK,cAAc;AAAA,MAC7D,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,YAAY;AAAA,IAChB;AACA,QAAI;AACA,YAAM,KAAK,OAAO,OAAO,KAAK,YAAY,GAAG;AAC7C,aAAO;AAAA,IACX,SAAS,KAAK;AACV,YAAM,SAAS,MAAM,KAAK,OAAO,QAAQ,KAAK,YAAY;AAAA,QACtD,OAAO,EAAE,QAAQ,MAAM,QAAQ,WAAW,MAAM,SAAS;AAAA,QACzD,QAAQ,CAAC,IAAI;AAAA,MACjB,CAAC;AACD,UAAI,QAAQ,GAAI,QAAO,OAAO;AAC9B,YAAM;AAAA,IACV;AAAA,EACJ;AAAA,EAEA,MAAM,MAAM,MAAiD;AACzD,UAAM,MAAM,KAAK,OAAO,KAAK,IAAI;AAGjC,UAAM,KAAK,OAAO;AAAA,MACd,KAAK;AAAA,MACL,EAAE,QAAQ,WAAW,YAAY,MAAM,YAAY,MAAM,YAAY,IAAI;AAAA,MACzE;AAAA,QACI,OAAO;AAAA,UACH,QAAQ;AAAA,UACR,YAAY,EAAE,KAAK,MAAM,KAAK,WAAW;AAAA,QAC7C;AAAA,QACA,OAAO;AAAA,MACX;AAAA,IACJ;AAGA,UAAM,kBAAkB,KAAK,YAAY,EAAE,eAAe,KAAK,UAAU,MAAM,IAAI,CAAC;AACpF,UAAM,aAAa,MAAM,KAAK,OAAO,KAAK,KAAK,YAAY;AAAA,MACvD,OAAO;AAAA,QACH,QAAQ;AAAA,QACR,GAAG;AAAA,QACH,KAAK,CAAC,EAAE,eAAe,KAAK,GAAG,EAAE,eAAe,EAAE,MAAM,IAAI,EAAE,CAAC;AAAA,MACnE;AAAA,MACA,QAAQ,CAAC,IAAI;AAAA,MACb,OAAO,KAAK;AAAA,IAChB,CAAC;AACD,QAAI,WAAW,WAAW,EAAG,QAAO,CAAC;AAErC,UAAM,MAAO,WAAqC,IAAI,CAAC,MAAM,EAAE,EAAE;AAGjE,UAAM,KAAK,OAAO;AAAA,MACd,KAAK;AAAA,MACL,EAAE,QAAQ,aAAa,YAAY,KAAK,QAAQ,YAAY,KAAK,YAAY,IAAI;AAAA,MACjF,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,IAAI,GAAG,QAAQ,UAAU,GAAG,OAAO,KAAK;AAAA,IAClE;AAGA,UAAM,UAAW,MAAM,KAAK,OAAO,KAAK,KAAK,YAAY;AAAA,MACrD,OAAO,EAAE,IAAI,EAAE,KAAK,IAAI,GAAG,YAAY,KAAK,QAAQ,YAAY,KAAK,QAAQ,YAAY;AAAA,IAC7F,CAAC;AAED,WAAO,QAAQ,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC;AAAA,EAChD;AAAA,EAEA,MAAM,IAAI,IAAY,QAAsC;AACxD,UAAM,UAAW,MAAM,KAAK,OAAO,QAAQ,KAAK,YAAY;AAAA,MACxD,OAAO,EAAE,GAAG;AAAA,MACZ,QAAQ,CAAC,UAAU;AAAA,IACvB,CAAC;AACD,QAAI,CAAC,QAAS;AAEd,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI;AACJ,QAAI;AACJ,QAAI;AAEJ,QAAI,OAAO,SAAS;AAChB,eAAS;AACT,oBAAc;AACd,cAAQ;AAAA,IACZ,WAAW,OAAO,MAAM;AACpB,eAAS;AACT,oBAAc;AACd,cAAQ,OAAO,SAAS;AAAA,IAC5B,OAAO;AACH,eAAS;AACT,oBAAc,OAAO,eAAe;AACpC,cAAQ,OAAO,SAAS;AAAA,IAC5B;AAEA,UAAM,KAAK,OAAO;AAAA,MACd,KAAK;AAAA,MACL;AAAA,QACI;AAAA,QACA,WAAW,QAAQ,YAAY,KAAK;AAAA,QACpC,mBAAmB;AAAA,QACnB,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,eAAe,OAAO,cAAc;AAAA,QACpC,eAAe,OAAO,gBAAgB;AAAA,QACtC,eAAe;AAAA,QACf;AAAA,QACA,YAAY;AAAA,MAChB;AAAA,MACA,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,MAAM;AAAA,IAClC;AAAA,EACJ;AAAA,EAEA,MAAM,KAAK,QAAoF;AAC3F,UAAM,QAAiC,CAAC;AACxC,QAAI,QAAQ,OAAQ,OAAM,SAAS,OAAO;AAC1C,QAAI,QAAQ,OAAQ,OAAM,SAAS,OAAO;AAC1C,UAAM,OAAQ,MAAM,KAAK,OAAO,KAAK,KAAK,YAAY,EAAE,MAAM,CAAC;AAC/D,WAAO,KAAK,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC;AAAA,EAC7C;AAAA,EAEA,MAAM,UAAU,IAAmC;AAC/C,UAAM,UAAW,MAAM,KAAK,OAAO,QAAQ,KAAK,YAAY,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;AAC7E,QAAI,CAAC,SAAS;AACV,YAAM,IAAI,mBAAmB,iBAAiB,EAAE,eAAe,WAAW;AAAA,IAC9E;AACA,QAAI,QAAQ,WAAW,aAAa,QAAQ,WAAW,YAAY,QAAQ,WAAW,QAAQ;AAC1F,YAAM,IAAI;AAAA,QACN,iBAAiB,EAAE,SAAS,QAAQ,MAAM;AAAA,QAC1C;AAAA,MACJ;AAAA,IACJ;AACA,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,KAAK,OAAO;AAAA,MACd,KAAK;AAAA,MACL;AAAA,QACI,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,eAAe;AAAA,QACf,mBAAmB;AAAA,QACnB,eAAe;AAAA,QACf,eAAe;AAAA,QACf,OAAO;AAAA,QACP,YAAY;AAAA,MAChB;AAAA,MACA,EAAE,OAAO,EAAE,IAAI,QAAQ,EAAE,KAAK,CAAC,WAAW,UAAU,MAAM,EAAE,EAAE,GAAG,OAAO,MAAM;AAAA,IAClF;AACA,UAAM,QAAS,MAAM,KAAK,OAAO,QAAQ,KAAK,YAAY,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;AAC3E,QAAI,CAAC,SAAS,MAAM,WAAW,WAAW;AACtC,YAAM,IAAI,mBAAmB,iBAAiB,EAAE,oCAAoC,cAAc;AAAA,IACtG;AACA,WAAO,KAAK,WAAW,KAAK;AAAA,EAChC;AAAA,EAEQ,WAAW,GAA8B;AAC7C,WAAO;AAAA,MACH,IAAI,EAAE;AAAA,MACN,QAAQ,EAAE;AAAA,MACV,OAAO,EAAE;AAAA,MACT,UAAU,EAAE;AAAA,MACZ,OAAO,EAAE,SAAS;AAAA,MAClB,KAAK,EAAE;AAAA,MACP,QAAQ,EAAE,UAAU;AAAA,MACpB,SAAS,EAAE,eAAe,KAAK,MAAM,EAAE,YAAY,IAAI;AAAA,MACvD,eAAe,EAAE,kBAAkB;AAAA,MACnC,WAAW,EAAE,cAAc;AAAA,MAC3B,SAAS,KAAK,MAAM,EAAE,YAAY;AAAA,MAClC,QAAQ,EAAE;AAAA,MACV,UAAU,EAAE;AAAA,MACZ,WAAW,EAAE,cAAc;AAAA,MAC3B,WAAW,EAAE,cAAc;AAAA,MAC3B,aAAa,EAAE,iBAAiB;AAAA,MAChC,iBAAiB,EAAE,qBAAqB;AAAA,MACxC,cAAc,EAAE,iBAAiB;AAAA,MACjC,cAAc,EAAE,iBAAiB;AAAA,MACjC,OAAO,EAAE,SAAS;AAAA,MAClB,WAAW,UAAU,EAAE,UAAU;AAAA,MACjC,WAAW,UAAU,EAAE,UAAU;AAAA,IACrC;AAAA,EACJ;AACJ;;;AG5PO,SAAS,aAAa,MAAwD;AACjF,QAAM,QAAQ,KAAK,IAAI,CAAC,MAAM;AAC1B,UAAM,IAAI,EAAE,WAAW,CAAC;AACxB,WAAO;AAAA,MACH,gBAAgB,EAAE;AAAA,MAClB,OAAO,OAAO,EAAE,UAAU,YAAY,EAAE,QAAQ,EAAE,QAAS,EAAE,SAAS;AAAA,MACtE,MAAM,OAAO,EAAE,SAAS,YAAY,EAAE,OAAO,EAAE,OAAO;AAAA,MACtD,OAAO,EAAE;AAAA,MACT,WAAW,OAAO,EAAE,cAAc,YAAY,EAAE,YAAY,EAAE,YAAY;AAAA,IAC9E;AAAA,EACJ,CAAC;AACD,QAAM,QAAQ,MAAM;AACpB,QAAM,QAAQ,UAAU,IAAI,MAAM,CAAC,EAAE,QAAQ,YAAY,KAAK;AAC9D,QAAM,OAAO,MAAM,IAAI,CAAC,OAAO,UAAK,GAAG,KAAK,EAAE,EAAE,KAAK,IAAI;AACzD,SAAO,EAAE,OAAO,MAAM,UAAU,QAAQ,OAAO,MAAM;AACzD;;;ACbA,IAAM,sBAAuC;AAAA,EACzC,MAAM;AAAA,IACF,MAAM,UAAU;AACZ,aAAO,EAAE,UAAU;AAAA,MAAC,GAAG,QAAQ,MAAM,MAAM,QAAQ;AAAA,MAAC,EAAE;AAAA,IAC1D;AAAA,EACJ;AACJ;AAwCO,IAAM,yBAAN,MAA6B;AAAA,EAShC,YAAY,SAAwC;AAHpD,SAAQ,UAAU;AAId,UAAM,aAAa,QAAQ,cAAc;AACzC,UAAM,YAAY,QAAQ,aAAa,aAAa;AACpD,SAAK,OAAO;AAAA,MACR,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA,MAChB,UAAU,QAAQ;AAAA,MAClB,gBAAgB,QAAQ;AAAA,MACxB,SAAS,QAAQ,WAAW;AAAA,MAC5B,gBAAgB,QAAQ,kBAAkB;AAAA,MAC1C,WAAW,QAAQ,aAAa;AAAA,MAChC;AAAA,MACA;AAAA,MACA,YAAY,QAAQ,cAAc,YAAY;AAAA,MAC9C,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,MACb,QAAQ,QAAQ;AAAA,MAChB,WAAW,QAAQ;AAAA,IACvB;AAAA,EACJ;AAAA;AAAA,EAGA,QAAc;AACV,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AACf,SAAK,aAAa;AAClB,SAAK,QAAQ,YAAY,MAAM,KAAK,aAAa,GAAG,KAAK,KAAK,UAAU;AAExE,IAAC,KAAK,OAAkC,QAAQ;AAAA,EACpD;AAAA;AAAA,EAGA,MAAM,OAAsB;AACxB,QAAI,CAAC,KAAK,QAAS;AACnB,SAAK,UAAU;AACf,QAAI,KAAK,OAAO;AACZ,oBAAc,KAAK,KAAK;AACxB,WAAK,QAAQ;AAAA,IACjB;AACA,QAAI,KAAK,cAAc;AACnB,UAAI;AAAE,cAAM,KAAK;AAAA,MAAc,QAAQ;AAAA,MAAuB;AAAA,IAClE;AAAA,EACJ;AAAA;AAAA,EAGA,MAAM,OAAsB;AACxB,UAAM,KAAK,QAAQ;AAAA,EACvB;AAAA,EAEQ,eAAqB;AACzB,QAAI,KAAK,aAAc;AACvB,SAAK,eAAe,KAAK,QAAQ,EAC5B,MAAM,CAAC,QAAQ;AACZ,WAAK,KAAK,QAAQ,OAAO,wCAAwC;AAAA,QAC7D,QAAQ,KAAK,KAAK;AAAA,QAClB,OAAQ,KAAe,WAAW,OAAO,GAAG;AAAA,MAChD,CAAC;AAAA,IACL,CAAC,EACA,QAAQ,MAAM;AAAE,WAAK,eAAe;AAAA,IAAW,CAAC;AAAA,EACzD;AAAA,EAEA,MAAc,UAAyB;AACnC,UAAM,QAAQ,KAAK,KAAK;AACxB,UAAM,SAAS,iBAAiB,KAAK,KAAK,QAAQ,KAAK;AACvD,aAAS,OAAO,GAAG,OAAO,OAAO,QAAQ;AACrC,YAAM,KAAK,cAAc,SAAS,QAAQ,KAAK;AAAA,IACnD;AAAA,EACJ;AAAA,EAEA,MAAc,aAAa,OAA8B;AACrD,UAAM,SAAS,MAAM,KAAK,KAAK,QAAQ,KAAK,QAAQ,+BAA+B,KAAK,IAAI;AAAA,MACxF,OAAO,KAAK,KAAK;AAAA,MACjB,QAAQ;AAAA,IACZ,CAAC;AACD,QAAI,CAAC,OAAQ;AACb,QAAI;AACA,YAAM,UAAU,MAAM,KAAK,KAAK,OAAO,MAAM;AAAA,QACzC,QAAQ,KAAK,KAAK;AAAA,QAClB,OAAO,KAAK,KAAK;AAAA,QACjB,WAAW,EAAE,OAAO,OAAO,KAAK,KAAK,eAAe;AAAA,QACpD,YAAY,KAAK,KAAK;AAAA,MAC1B,CAAC;AACD,UAAI,QAAQ,SAAS,GAAG;AACpB,cAAM,OAAO,QAAQ,KAAK,KAAK,SAAS;AACxC,mBAAW,OAAO,SAAS;AACvB,cAAI,OAAO,UAAU,CAAC,OAAO,OAAO,EAAG;AACvC,gBAAM,KAAK,WAAW,GAAG;AAAA,QAC7B;AAAA,MACJ;AAKA,YAAM,aAAa,MAAM,KAAK,KAAK,OAAO,YAAY;AAAA,QAClD,QAAQ,KAAK,KAAK;AAAA,QAClB,OAAO,KAAK,KAAK;AAAA,QACjB,WAAW,EAAE,OAAO,OAAO,KAAK,KAAK,eAAe;AAAA,QACpD,YAAY,KAAK,KAAK;AAAA,MAC1B,CAAC;AACD,UAAI,WAAW,SAAS,GAAG;AACvB,cAAM,OAAO,QAAQ,KAAK,KAAK,SAAS;AACxC,mBAAW,SAAS,iBAAiB,UAAU,GAAG;AAC9C,cAAI,OAAO,UAAU,CAAC,OAAO,OAAO,EAAG;AACvC,gBAAM,KAAK,mBAAmB,KAAK;AAAA,QACvC;AAAA,MACJ;AAAA,IACJ,UAAE;AACE,YAAM,OAAO,QAAQ;AAAA,IACzB;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,mBAAmB,MAAmD;AAChF,UAAM,cAAc,KAAK,CAAC,EAAE;AAC5B,UAAM,YAAY,KAAK,CAAC,EAAE;AAC1B,UAAM,UAAU,KAAK,KAAK,SAAS,WAAW,WAAW;AACzD,QAAI,CAAC,SAAS;AACV,iBAAW,OAAO,MAAM;AACpB,cAAM,KAAK,KAAK,OAAO,IAAI,IAAI,IAAI,EAAE,SAAS,OAAO,OAAO,YAAY,WAAW,oBAAoB,MAAM,KAAK,CAAC;AACnH,aAAK,KAAK,YAAY,KAAK,KAAK;AAAA,MACpC;AACA;AAAA,IACJ;AAEA,UAAM,SAAS,aAAa,IAAI;AAChC,UAAM,eAA6B;AAAA,MAC/B,gBAAgB,KAAK,CAAC,EAAE;AAAA;AAAA,MACxB,gBAAgB,KAAK,CAAC,EAAE;AAAA,MACxB,OAAO,KAAK,CAAC,EAAE;AAAA,MACf,OAAO,OAAO;AAAA,MACd,MAAM,OAAO;AAAA,MACb,UAAU;AAAA,MACV,YAAY,CAAC,SAAS;AAAA,MACtB,UAAU,CAAC,WAAW;AAAA,MACtB,SAAS,EAAE,QAAQ,MAAM,OAAO,OAAO,OAAO,OAAO,OAAO,MAAM;AAAA,IACtE;AAEA,QAAI;AACJ,QAAI;AACA,eAAS,MAAM,QAAQ,KAAK,KAAK,KAAK,gBAAgB,EAAE,cAAc,SAAS,aAAa,UAAU,CAAC;AAAA,IAC3G,SAAS,KAAK;AACV,eAAS,EAAE,IAAI,OAAO,OAAQ,KAAe,WAAW,OAAO,GAAG,EAAE;AAAA,IACxE;AAEA,UAAM,aAAa,CAAC,OAAO,MAAM,QAAQ,gBAAgB,QAAQ,cAAc,OAAO,KAAK,IAAI;AAC/F,UAAM,MAAM,KAAK,KAAK,MAAM,KAAK,KAAK,IAAI;AAC1C,eAAW,OAAO,MAAM;AACpB,YAAM,MAAM,wBAAwB,QAAQ,YAAY,IAAI,UAAU,KAAK,KAAK,KAAK,GAAG;AACxF,YAAM,KAAK,KAAK,OAAO,IAAI,IAAI,IAAI,GAAG;AACtC,WAAK,KAAK,YAAY,KAAK,OAAO,EAAE;AAAA,IACxC;AAAA,EACJ;AAAA,EAEA,MAAc,WAAW,KAAgD;AACrE,UAAM,UAAU,KAAK,KAAK,SAAS,WAAW,IAAI,OAAO;AACzD,QAAI,CAAC,SAAS;AAEV,YAAM,KAAK,KAAK,OAAO,IAAI,IAAI,IAAI;AAAA,QAC/B,SAAS;AAAA,QACT,OAAO,YAAY,IAAI,OAAO;AAAA,QAC9B,MAAM;AAAA,MACV,CAAC;AACD,WAAK,KAAK,YAAY,KAAK,KAAK;AAChC;AAAA,IACJ;AAEA,UAAM,IAAI,IAAI,WAAW,CAAC;AAC1B,UAAM,eAA6B;AAAA,MAC/B,gBAAgB,IAAI;AAAA,MACpB,gBAAgB,IAAI;AAAA,MACpB,OAAO,IAAI;AAAA,MACX,OAAO,OAAO,EAAE,UAAU,WAAW,EAAE,QAAQ,IAAI,SAAS;AAAA,MAC5D,MAAM,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;AAAA,MAC5C,UAAW,EAAE,YAAyC;AAAA,MACtD,YAAY,CAAC,IAAI,WAAW;AAAA,MAC5B,UAAU,CAAC,IAAI,OAAO;AAAA,MACtB,WAAW,OAAO,EAAE,cAAc,WAAW,EAAE,YAAY;AAAA,MAC3D,SAAS;AAAA,IACb;AAEA,QAAI;AACJ,QAAI;AACA,eAAS,MAAM,QAAQ,KAAK,KAAK,KAAK,gBAAgB;AAAA,QAClD;AAAA,QACA,SAAS,IAAI;AAAA,QACb,WAAW,IAAI;AAAA,MACnB,CAAC;AAAA,IACL,SAAS,KAAK;AACV,eAAS,EAAE,IAAI,OAAO,OAAQ,KAAe,WAAW,OAAO,GAAG,EAAE;AAAA,IACxE;AAEA,UAAM,aAAa,CAAC,OAAO,MAAM,QAAQ,gBAAgB,QAAQ,cAAc,OAAO,KAAK,IAAI;AAC/F,UAAM,MAAM,KAAK,KAAK,MAAM,KAAK,KAAK,IAAI;AAC1C,UAAM,MAAM,wBAAwB,QAAQ,YAAY,IAAI,UAAU,KAAK,KAAK,KAAK,GAAG;AACxF,UAAM,KAAK,KAAK,OAAO,IAAI,IAAI,IAAI,GAAG;AACtC,SAAK,KAAK,YAAY,KAAK,OAAO,EAAE;AAAA,EACxC;AACJ;AAGA,SAAS,iBAAiB,MAAoE;AAC1F,QAAM,SAAS,oBAAI,IAA0C;AAC7D,aAAW,KAAK,MAAM;AAClB,UAAM,MAAM,EAAE,aAAa,EAAE;AAC7B,QAAI,IAAI,OAAO,IAAI,GAAG;AACtB,QAAI,CAAC,GAAG;AAAE,UAAI,CAAC;AAAG,aAAO,IAAI,KAAK,CAAC;AAAA,IAAG;AACtC,MAAE,KAAK,CAAC;AAAA,EACZ;AACA,SAAO,CAAC,GAAG,OAAO,OAAO,CAAC;AAC9B;AAGA,SAAS,iBAAiB,QAAgB,gBAAgC;AACtE,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,IAAK,KAAK,IAAI,KAAK,OAAO,WAAW,CAAC,IAAK;AAC9E,SAAO,KAAK,IAAI,CAAC,IAAI;AACzB;;;ACjTA,SAAS,YAAY,cAAAC,mBAAkB;AAYhC,IAAM,0BAA0B;AAGvC,IAAM,oBAAoB,KAAK;AAoC/B,eAAsB,SAClB,UACA,WAC2B;AAC3B,QAAM,OACF,OAAO,SAAS,YAAY,WACtB,SAAS,UACT,KAAK,UAAU,SAAS,WAAW,IAAI;AAEjD,QAAM,UAAkC;AAAA,IACpC,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,0BAA0B,SAAS;AAAA,IACnC,yBAAyB,OAAO,SAAS,WAAW,CAAC;AAAA,IACrD,GAAI,SAAS,QAAQ,EAAE,uBAAuB,SAAS,MAAM,IAAI,CAAC;AAAA,IAClE,GAAI,SAAS,WAAW,CAAC;AAAA,EAC7B;AACA,MAAI,SAAS,eAAe;AACxB,UAAM,MAAM,WAAW,UAAU,SAAS,aAAa,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;AAClF,YAAQ,yBAAyB,IAAI,UAAU,GAAG;AAAA,EACtD;AAEA,QAAM,YAAY,SAAS,aAAa;AACxC,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,SAAS;AAC5D,QAAM,QAAQ,KAAK,IAAI;AACvB,MAAI;AACA,UAAM,MAAM,MAAM,UAAU,SAAS,KAAK;AAAA,MACtC,QAAQ,SAAS,UAAU;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,QAAQ,WAAW;AAAA,IACvB,CAAC;AACD,iBAAa,KAAK;AAClB,UAAM,eAAe,MAAM,aAAa,GAAG;AAC3C,UAAM,aAAa,KAAK,IAAI,IAAI;AAChC,QAAI,IAAI,IAAI;AACR,aAAO,EAAE,SAAS,MAAM,YAAY,IAAI,QAAQ,cAAc,cAAc,WAAW;AAAA,IAC3F;AACA,UAAM,YAAY,IAAI,WAAW,OAAO,IAAI,WAAW,OAAO,IAAI,UAAU;AAC5E,WAAO;AAAA,MACH,SAAS;AAAA,MACT;AAAA,MACA,YAAY,IAAI;AAAA,MAChB,cAAc;AAAA,MACd,OAAO,QAAQ,IAAI,MAAM;AAAA,MACzB;AAAA,IACJ;AAAA,EACJ,SAAS,KAAc;AACnB,iBAAa,KAAK;AAClB,UAAM,aAAa,KAAK,IAAI,IAAI;AAChC,UAAM,IAAI;AACV,UAAM,QAAQ,GAAG,SAAS,eAAe,iBAAiB,SAAS,OAAO,GAAG,WAAW,OAAO,GAAG;AAClG,WAAO,EAAE,SAAS,OAAO,WAAW,MAAM,OAAO,WAAW;AAAA,EAChE;AACJ;AAEA,eAAe,aAAa,KAA+D;AACvF,MAAI;AACA,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,WAAO,KAAK,SAAS,oBAAoB,KAAK,MAAM,GAAG,iBAAiB,IAAI;AAAA,EAChF,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAUO,SAAS,qBACZ,eACA,MAAoB,KAAK,QACZ;AACb,QAAM,WAAW,CAAC,KAAO,KAAQ,KAAQ,KAAS,MAAW,OAAY,KAAU;AACnF,MAAI,gBAAgB,KAAK,gBAAgB,SAAS,OAAQ,QAAO;AACjE,QAAM,OAAO,SAAS,gBAAgB,CAAC;AACvC,QAAM,SAAS,MAAM,IAAI,IAAI;AAC7B,SAAO,KAAK,MAAM,OAAO,MAAM;AACnC;AAMO,SAAS,gBACZ,SACA,eACA,MAAc,KAAK,IAAI,GACvB,KACa;AACb,MAAI,QAAQ,QAAS,QAAO;AAC5B,MAAI,CAAC,QAAQ,WAAW;AACpB,WAAO;AAAA,MACH,SAAS;AAAA,MACT,YAAY,QAAQ;AAAA,MACpB,cAAc,QAAQ;AAAA,MACtB,OAAO,QAAQ;AAAA,MACf,YAAY,QAAQ;AAAA,MACpB,MAAM;AAAA,IACV;AAAA,EACJ;AACA,QAAM,QAAQ,qBAAqB,gBAAgB,GAAG,GAAG;AACzD,MAAI,UAAU,MAAM;AAChB,WAAO;AAAA,MACH,SAAS;AAAA,MACT,YAAY,QAAQ;AAAA,MACpB,cAAc,QAAQ;AAAA,MACtB,OAAO,QAAQ;AAAA,MACf,YAAY,QAAQ;AAAA,MACpB,MAAM;AAAA,IACV;AAAA,EACJ;AACA,SAAO;AAAA,IACH,SAAS;AAAA,IACT,YAAY,QAAQ;AAAA,IACpB,cAAc,QAAQ;AAAA,IACtB,OAAO,QAAQ;AAAA,IACf,YAAY,QAAQ;AAAA,IACpB,aAAa,MAAM;AAAA,EACvB;AACJ;AAGO,SAAS,gBAAwB;AACpC,SAAOA,YAAW;AACtB;;;ACjKA,IAAMC,uBAAuC;AAAA,EACzC,MAAM;AAAA,IACF,MAAM,UAAU;AACZ,aAAO,EAAE,UAAU;AAAA,MAAC,GAAG,QAAQ,MAAM,MAAM,QAAQ;AAAA,MAAC,EAAE;AAAA,IAC1D;AAAA,EACJ;AACJ;AAoCO,IAAM,iBAAN,MAAqB;AAAA,EAWxB,YAAY,SAAgC;AAH5C,SAAQ,UAAU;AAId,UAAM,aAAa,QAAQ,cAAc;AACzC,UAAM,YAAY,QAAQ,aAAa,aAAa;AACpD,SAAK,OAAO;AAAA,MACR,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA,MAChB,SAAS,QAAQ,WAAWA;AAAA,MAC5B,gBAAgB,QAAQ,kBAAkB;AAAA,MAC1C,WAAW,QAAQ,aAAa;AAAA,MAChC;AAAA,MACA;AAAA,MACA,YAAY,QAAQ,cAAc,YAAY;AAAA,MAC9C,WAAW,QAAQ;AAAA,MACnB,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,MACb,QAAQ,QAAQ;AAAA,MAChB,WAAW,QAAQ;AAAA,IACvB;AAAA,EACJ;AAAA;AAAA,EAGA,QAAc;AACV,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AACf,SAAK,aAAa;AAClB,SAAK,QAAQ,YAAY,MAAM,KAAK,aAAa,GAAG,KAAK,KAAK,UAAU;AACxE,SAAK,MAAM,QAAQ;AAAA,EACvB;AAAA;AAAA,EAGA,MAAM,OAAsB;AACxB,QAAI,CAAC,KAAK,QAAS;AACnB,SAAK,UAAU;AACf,QAAI,KAAK,OAAO;AACZ,oBAAc,KAAK,KAAK;AACxB,WAAK,QAAQ;AAAA,IACjB;AACA,QAAI,KAAK,cAAc;AACnB,UAAI;AACA,cAAM,KAAK;AAAA,MACf,QAAQ;AAAA,MAER;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA,EAGA,MAAM,OAAsB;AACxB,UAAM,KAAK,QAAQ;AAAA,EACvB;AAAA,EAEQ,eAAqB;AACzB,QAAI,KAAK,aAAc;AACvB,SAAK,eAAe,KAAK,QAAQ,EAC5B,MAAM,CAAC,QAAQ;AACZ,WAAK,KAAK,QAAQ,OAAO,gCAAgC;AAAA,QACrD,QAAQ,KAAK,KAAK;AAAA,QAClB,OAAQ,KAAe,WAAW,OAAO,GAAG;AAAA,MAChD,CAAC;AAAA,IACL,CAAC,EACA,QAAQ,MAAM;AACX,WAAK,eAAe;AAAA,IACxB,CAAC;AAAA,EACT;AAAA,EAEA,MAAc,UAAyB;AACnC,UAAM,iBAAiB,KAAK,KAAK;AACjC,UAAM,SAASC,kBAAiB,KAAK,KAAK,QAAQ,cAAc;AAChE,aAAS,OAAO,GAAG,OAAO,gBAAgB,QAAQ;AAC9C,YAAM,KAAK,SAAS,QAAQ;AAC5B,YAAM,KAAK,aAAa,CAAC;AAAA,IAC7B;AAAA,EACJ;AAAA,EAEA,MAAc,aAAa,OAA8B;AACrD,UAAM,MAAM,6BAA6B,KAAK;AAC9C,UAAM,SAAoC,MAAM,KAAK,KAAK,QAAQ,KAAK,QAAQ,KAAK;AAAA,MAChF,OAAO,KAAK,KAAK;AAAA,MACjB,QAAQ;AAAA,IACZ,CAAC;AACD,QAAI,CAAC,OAAQ;AAEb,QAAI;AACA,YAAM,UAAU,MAAM,KAAK,KAAK,OAAO,MAAM;AAAA,QACzC,QAAQ,KAAK,KAAK;AAAA,QAClB,OAAO,KAAK,KAAK;AAAA,QACjB,WAAW,EAAE,OAAO,OAAO,KAAK,KAAK,eAAe;AAAA,QACpD,YAAY,KAAK,KAAK;AAAA,QACtB,KAAK,KAAK,KAAK,MAAM;AAAA,MACzB,CAAC;AACD,UAAI,QAAQ,WAAW,EAAG;AAC1B,YAAM,OAAO,QAAQ,KAAK,KAAK,SAAS;AACxC,iBAAW,OAAO,SAAS;AACvB,YAAI,OAAO,UAAU,CAAC,OAAO,OAAO,EAAG;AACvC,cAAM,KAAK,WAAW,GAAG;AAAA,MAC7B;AAAA,IACJ,UAAE;AACE,YAAM,OAAO,QAAQ;AAAA,IACzB;AAAA,EACJ;AAAA,EAEA,MAAc,WAAW,KAAkC;AACvD,UAAM,YAAa,KAAK,KAAK,aAAc,WAAW;AAGtD,QAAI,CAAC,WAAW;AACZ,WAAK,KAAK,QAAQ,OAAO,4CAA4C,EAAE,OAAO,IAAI,GAAG,CAAC;AACtF,YAAM,KAAK,KAAK,OAAO,IAAI,IAAI,IAAI;AAAA,QAC/B,SAAS;AAAA,QACT,OAAO;AAAA,QACP,YAAY;AAAA,QACZ,MAAM;AAAA,MACV,CAAC;AACD;AAAA,IACJ;AACA,UAAM,UAAU,MAAM,SAAS,KAAK,SAAS;AAC7C,UAAM,SAAS,gBAAgB,SAAS,IAAI,UAAU,KAAK,KAAK,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,KAAK,GAAG;AACpG,UAAM,KAAK,KAAK,OAAO,IAAI,IAAI,IAAI,MAAM;AACzC,SAAK,KAAK,YAAY,KAAK,OAAO,OAAO;AAAA,EAC7C;AACJ;AAGA,SAASA,kBAAiB,QAAgB,gBAAgC;AACtE,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACpC,QAAK,IAAI,KAAK,OAAO,WAAW,CAAC,IAAK;AAAA,EAC1C;AACA,SAAO,KAAK,IAAI,CAAC,IAAI;AACzB;;;AC9LO,IAAM,sCAAsC;AA8B5C,IAAM,4BAAwD;AAAA,EACjE,EAAE,QAAQ,gBAAgB,SAAS,aAAa;AAAA,EAChD,EAAE,QAAQ,cAAc,SAAS,aAAa;AAAA,EAC9C,EAAE,QAAQ,iBAAiB,SAAS,aAAa;AAAA,EACjD,EAAE,QAAQ,2BAA2B,SAAS,aAAa;AAC/D;AAgCO,IAAM,wBAAN,MAA4B;AAAA,EAI/B,YAA6B,MAAoC;AAApC;AACzB,SAAK,MAAM,KAAK,QAAQ,MAAM,KAAK,IAAI;AACvC,SAAK,UAAU,KAAK,WAAW;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,MAAM,eAAgD;AACxD,UAAM,OAAO,KAAK,KAAK,QAAQ;AAC/B,QAAI,CAAC,MAAM;AACP,WAAK,KAAK,OAAO,KAAK,sDAAsD;AAC5E,aAAO,CAAC;AAAA,IACZ;AACA,QAAI,EAAE,gBAAgB,IAAI;AACtB,WAAK,KAAK,OAAO,KAAK,gDAAgD,aAAa,iBAAiB;AACpG,aAAO,CAAC;AAAA,IACZ;AAEA,UAAM,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,gBAAgB,KAAU,EAAE,YAAY;AAChF,UAAM,WAA2B,CAAC;AAElC,eAAW,KAAK,KAAK,SAAS;AAC1B,UAAI;AACA,cAAM,MAAM,MAAM,KAAK,OAAO,EAAE,QAAQ;AAAA;AAAA;AAAA;AAAA,UAIpC,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,EAAE,KAAK,UAAU,EAAE;AAAA,UACzC,OAAO;AAAA;AAAA;AAAA,UAGP,SAAS,EAAE,UAAU,KAAK;AAAA,QAC9B,CAAQ;AACR,cAAM,UAAU,aAAa,GAAG;AAChC,iBAAS,KAAK,EAAE,QAAQ,EAAE,QAAQ,QAAQ,CAAC;AAC3C,YAAI,YAAY,UAAa,UAAU,GAAG;AACtC,eAAK,KAAK,OAAO;AAAA,YACb,iCAAiC,WAAW,GAAG,IAAI,EAAE,MAAM,oBAAoB,SAAS;AAAA,UAC5F;AAAA,QACJ;AAAA,MACJ,SAAS,KAAK;AACV,cAAMC,OAAO,KAAe,WAAW,OAAO,GAAG;AACjD,aAAK,KAAK,OAAO,KAAK,mCAAmC,EAAE,MAAM,YAAYA,IAAG,eAAe;AAC/F,iBAAS,KAAK,EAAE,QAAQ,EAAE,QAAQ,OAAOA,KAAI,CAAC;AAAA,MAClD;AAAA,IACJ;AACA,WAAO;AAAA,EACX;AACJ;AAGA,SAAS,aAAa,KAAkC;AACpD,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,MAAI,MAAM,QAAQ,GAAG,EAAG,QAAO,IAAI;AACnC,MAAI,OAAO,OAAO,QAAQ,UAAU;AAChC,UAAM,IAAI;AACV,eAAW,KAAK,CAAC,gBAAgB,WAAW,SAAS,YAAY,cAAc,GAAG;AAC9E,UAAI,OAAO,EAAE,CAAC,MAAM,SAAU,QAAO,EAAE,CAAC;AAAA,IAC5C;AAAA,EACJ;AACA,SAAO;AACX;;;AChJO,IAAM,kBAAkB;AAGxB,IAAM,iBAAiB;AA8B9B,IAAM,QAAQ;AAQP,SAAS,YAAY,UAAkB,SAA0C;AACpF,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,SAAS,QAAQ,OAAO,CAAC,IAAI,SAAiB;AACjD,UAAM,IAAI,OAAO,SAAS,IAAI;AAC9B,WAAO,KAAK,OAAO,KAAK,OAAO,CAAC;AAAA,EACpC,CAAC;AACL;AAEA,SAAS,OAAO,KAA8B,MAAuB;AACjE,MAAI,MAAe;AACnB,aAAW,OAAO,KAAK,MAAM,GAAG,GAAG;AAC/B,QAAI,OAAO,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACnD,UAAO,IAAgC,GAAG;AAAA,EAC9C;AACA,SAAO;AACX;AAQO,SAAS,mBACZ,UACA,OACoB;AACpB,QAAM,MAA+B;AAAA,IACjC,GAAG,MAAM;AAAA,IACT,SAAS,MAAM;AAAA,IACf,OAAO,MAAM;AAAA,IACb,OAAO,MAAM,SAAS,MAAM,QAAQ;AAAA,IACpC,MAAM,MAAM,QAAQ,MAAM,QAAQ;AAAA,EACtC;AAEA,MAAI,aAAa,SAAS,WAAW,SAAS,OAAO;AACjD,UAAM,UAAU,YAAY,OAAO,SAAS,WAAW,EAAE,GAAG,GAAG,KAAK,OAAO,IAAI,SAAS,MAAM,KAAK;AACnG,UAAM,eAAe,YAAY,OAAO,SAAS,QAAQ,EAAE,GAAG,GAAG;AACjE,UAAM,SAAS,SAAS,WAAW,UAAU,SAAS,WAAW;AACjE,WAAO,SAAS,EAAE,SAAS,MAAM,aAAa,IAAI,EAAE,SAAS,MAAM,aAAa;AAAA,EACpF;AAGA,SAAO;AAAA,IACH,SAAS,OAAO,IAAI,SAAS,MAAM,KAAK;AAAA,IACxC,MAAM,OAAO,IAAI,QAAQ,EAAE;AAAA,EAC/B;AACJ;AAaO,IAAM,4BAAN,MAAgC;AAAA,EAEnC,YAA6B,MAAwC;AAAxC;AACzB,SAAK,aAAa,KAAK,cAAc;AAAA,EACzC;AAAA,EAEA,MAAM,KAAK,OAAe,SAAiB,QAA0D;AACjG,UAAM,OAAO,KAAK,KAAK,QAAQ;AAC/B,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,aAAa,iBAAiB,MAAM;AAC1C,eAAW,OAAO,YAAY;AAC1B,UAAI;AACA,cAAM,MAAM,MAAM,KAAK,QAAQ,KAAK,YAAY;AAAA,UAC5C,OAAO,EAAE,OAAO,SAAS,QAAQ,KAAK,WAAW,KAAK;AAAA,UACtD,QAAQ,CAAC,WAAW,QAAQ,QAAQ;AAAA,QACxC,CAAC;AACD,YAAI,IAAK,QAAO;AAAA,MACpB,QAAQ;AACJ,eAAO;AAAA,MACX;AAAA,IACJ;AACA,WAAO;AAAA,EACX;AACJ;AAGA,SAAS,iBAAiB,QAA2B;AACjD,QAAM,MAAgB,CAAC;AACvB,QAAM,OAAO,CAAC,MAAe;AAAE,QAAI,KAAK,CAAC,IAAI,SAAS,CAAC,EAAG,KAAI,KAAK,CAAC;AAAA,EAAG;AACvE,OAAK,MAAM;AACX,MAAI,UAAU,OAAO,SAAS,GAAG,EAAG,MAAK,OAAO,MAAM,GAAG,EAAE,CAAC,CAAC;AAC7D,OAAK,cAAc;AACnB,SAAO;AACX;;;AC1HO,IAAMC,eAAc;AA+B3B,IAAM,cAAc,CAAC,MAAuB;AAGxC,MAAI,CAAC,KAAK,KAAK,KAAK,CAAC,EAAG,QAAO;AAC/B,QAAM,KAAK,EAAE,QAAQ,GAAG;AACxB,MAAI,MAAM,KAAK,OAAO,EAAE,YAAY,GAAG,KAAK,OAAO,EAAE,SAAS,EAAG,QAAO;AACxE,QAAM,MAAM,EAAE,MAAM,KAAK,CAAC,EAAE,QAAQ,GAAG;AACvC,SAAO,MAAM,KAAK,MAAM,EAAE,SAAS,KAAK;AAC5C;AAgBO,SAAS,mBAAmB,MAA6C;AAC5E,QAAM,aAAa,KAAK,cAAcA;AACtC,QAAM,gBAAgB,KAAK,iBAAiB;AAE5C,iBAAe,eACX,KACA,MACA,WAC2B;AAC3B,QAAI,YAAY,SAAS,EAAG,QAAO;AACnC,QAAI,CAAC,KAAM,QAAO;AAClB,QAAI;AACA,YAAM,OAAO,MAAM,KAAK,QAAQ,YAAY,EAAE,OAAO,EAAE,IAAI,UAAU,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC;AAC3F,YAAM,QAAQ,MAAM;AACpB,aAAO,OAAO,UAAU,YAAY,YAAY,KAAK,IAAI,QAAQ;AAAA,IACrE,SAAS,KAAK;AACV,UAAI,OAAO,KAAK,+BAA+B,SAAS,aAAc,IAAc,OAAO,GAAG;AAC9F,aAAO;AAAA,IACX;AAAA,EACJ;AAEA,SAAO;AAAA,IACH,IAAI;AAAA,IAEJ,MAAM,KAAK,KAA8B,UAAyC;AAC9E,YAAM,QAAQ,KAAK,SAAS;AAC5B,UAAI,CAAC,OAAO;AACR,YAAI,OAAO,KAAK,yCAAyC,SAAS,SAAS,eAAe;AAC1F,eAAO,EAAE,IAAI,KAAK;AAAA,MACtB;AAEA,YAAM,IAAI,SAAS;AACnB,YAAM,UAAU,MAAM,eAAe,KAAK,KAAK,QAAQ,GAAG,SAAS,SAAS;AAC5E,UAAI,CAAC,SAAS;AACV,eAAO,EAAE,IAAI,OAAO,OAAO,mCAAmC,SAAS,SAAS,IAAI;AAAA,MACxF;AAEA,YAAM,UAAW,EAAE,WAAW,CAAC;AAC/B,YAAM,SAAS,OAAO,QAAQ,WAAW,WAAW,QAAQ,SAAS;AACrE,YAAM,WAAW,MAAM,KAAK,MAAM,KAAK,EAAE,SAAS,IAAI,SAAS,MAAM;AACrE,YAAM,WAAW,mBAAmB,UAAU;AAAA,QAC1C,OAAO,EAAE,SAAS;AAAA,QAClB;AAAA,QACA,OAAO,EAAE;AAAA,QACT,MAAM,EAAE;AAAA,MACZ,CAAC;AAED,UAAI;AACA,cAAM,SAAc,MAAM,MAAM,KAAK;AAAA,UACjC,IAAI;AAAA,UACJ,SAAS,SAAS;AAAA,UAClB,GAAI,SAAS,SAAS,SAAY,EAAE,MAAM,SAAS,KAAK,IAAI,CAAC;AAAA,UAC7D,GAAI,SAAS,SAAS,SAAY,EAAE,MAAM,SAAS,KAAK,IAAI,CAAC;AAAA,QACjE,CAAC;AACD,cAAM,KAAK,QAAQ;AACnB,eAAO,EAAE,IAAI,MAAM,YAAY,MAAM,OAAO,OAAO,EAAE,IAAI,OAAU;AAAA,MACvE,SAAS,KAAK;AACV,eAAO,EAAE,IAAI,OAAO,OAAO,sBAAuB,IAAc,OAAO,GAAG;AAAA,MAC9E;AAAA,IACJ;AAAA,IAEA,cAAc,MAA2B;AACrC,aAAO;AAAA,IACX;AAAA,EACJ;AACJ;;;ACvIA,SAAS,gBAAAC,eAAc,SAAAC,cAAa;AAiB7B,IAAM,eAAeD,cAAa,OAAO;AAAA,EAC5C,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aAAa;AAAA,EACb,MAAM;AAAA,EACN,aAAa;AAAA,EACb,WAAW;AAAA;AAAA,EACX,aAAa;AAAA,EACb,eAAe,CAAC,SAAS,WAAW,YAAY,YAAY;AAAA,EAE5D,WAAW;AAAA,IACP,MAAM;AAAA,MACF,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM,EAAE,UAAU,UAAU,QAAQ,oBAAoB;AAAA,MACxD,SAAS,CAAC,SAAS,SAAS,YAAY,YAAY;AAAA,MACpD,QAAQ,CAAC,EAAE,OAAO,WAAW,UAAU,UAAU,OAAO,oBAAoB,CAAC;AAAA,MAC7E,MAAM,CAAC,EAAE,OAAO,cAAc,OAAO,OAAO,CAAC;AAAA,MAC7C,YAAY,EAAE,UAAU,GAAG;AAAA,MAC3B,YAAY,EAAE,OAAO,cAAc,SAAS,oBAAoB;AAAA,IACpE;AAAA,EACJ;AAAA,EAEA,QAAQ;AAAA,IACJ,IAAIC,OAAM,KAAK;AAAA,MACX,OAAO;AAAA,MACP,UAAU;AAAA,MACV,UAAU;AAAA,IACd,CAAC;AAAA,IAED,SAASA,OAAM,KAAK;AAAA,MAChB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,YAAY;AAAA,IAChB,CAAC;AAAA,IAED,iBAAiBA,OAAM,KAAK;AAAA,MACxB,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,aAAaA,OAAM,KAAK;AAAA,MACpB,OAAO;AAAA,MACP,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,OAAOA,OAAM,KAAK;AAAA,MACd,OAAO;AAAA,MACP,YAAY;AAAA,IAChB,CAAC;AAAA,IAED,OAAOA,OAAM,KAAK;AAAA,MACd,OAAO;AAAA,MACP,UAAU;AAAA,IACd,CAAC;AAAA,IAED,SAASA,OAAM,SAAS;AAAA,MACpB,OAAO;AAAA,IACX,CAAC;AAAA,IAED,UAAUA,OAAM,OAAO;AAAA,MACnB,OAAO;AAAA,MACP,SAAS;AAAA,QACL,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,QAC/B,EAAE,OAAO,WAAW,OAAO,UAAU;AAAA,QACrC,EAAE,OAAO,YAAY,OAAO,WAAW;AAAA,MAC3C;AAAA,IACJ,CAAC;AAAA,IAED,YAAYA,OAAM,KAAK;AAAA,MACnB,OAAO;AAAA,IACX,CAAC;AAAA,IAED,YAAYA,OAAM,SAAS;AAAA,MACvB,OAAO;AAAA,MACP,UAAU;AAAA,IACd,CAAC;AAAA,EACL;AACJ,CAAC;;;ACjGD,SAAS,gBAAAC,eAAc,SAAAC,cAAa;AAmB7B,IAAM,sBAAsBD,cAAa,OAAO;AAAA,EACnD,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aAAa;AAAA,EACb,MAAM;AAAA,EACN,UAAU;AAAA,EACV,WAAW;AAAA,EACX,aAAa;AAAA,EACb,aAAa;AAAA,EACb,eAAe,CAAC,mBAAmB,WAAW,WAAW,SAAS,IAAI;AAAA,EAEtE,QAAQ;AAAA,IACJ,IAAIC,OAAM,KAAK;AAAA,MACX,OAAO;AAAA,MACP,UAAU;AAAA,MACV,UAAU;AAAA,IACd,CAAC;AAAA,IAED,iBAAiBA,OAAM,KAAK;AAAA,MACxB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,aAAaA,OAAM,KAAK;AAAA,MACpB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,SAASA,OAAM,KAAK;AAAA,MAChB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,YAAY;AAAA,IAChB,CAAC;AAAA,IAED,SAASA,OAAM,KAAK;AAAA,MAChB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,OAAOA,OAAM,OAAO,CAAC,aAAa,QAAQ,WAAW,WAAW,GAAG;AAAA,MAC/D,OAAO;AAAA,MACP,UAAU;AAAA,MACV,cAAc;AAAA,IAClB,CAAC;AAAA,IAED,IAAIA,OAAM,SAAS;AAAA,MACf,OAAO;AAAA,MACP,UAAU;AAAA,MACV,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,YAAYA,OAAM,SAAS;AAAA,MACvB,OAAO;AAAA,MACP,UAAU;AAAA,IACd,CAAC;AAAA,EACL;AAAA,EAEA,SAAS;AAAA,IACL,EAAE,QAAQ,CAAC,mBAAmB,WAAW,SAAS,GAAG,QAAQ,KAAK;AAAA,IAClE,EAAE,QAAQ,CAAC,WAAW,OAAO,EAAE;AAAA,EACnC;AACJ,CAAC;;;ACpFD,SAAS,gBAAAC,eAAc,SAAAC,cAAa;AAiB7B,IAAM,uBAAuBD,cAAa,OAAO;AAAA,EACpD,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aAAa;AAAA,EACb,MAAM;AAAA,EACN,UAAU;AAAA,EACV,WAAW;AAAA,EACX,aAAa;AAAA,EACb,aAAa;AAAA,EACb,eAAe,CAAC,mBAAmB,gBAAgB,WAAW,UAAU,UAAU;AAAA,EAElF,QAAQ;AAAA,IACJ,IAAIC,OAAM,KAAK,EAAE,OAAO,eAAe,UAAU,MAAM,UAAU,KAAK,CAAC;AAAA,IAEvE,iBAAiBA,OAAM,KAAK;AAAA,MACxB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,aAAa;AAAA,IACjB,CAAC;AAAA,IACD,cAAcA,OAAM,KAAK,EAAE,OAAO,kBAAkB,UAAU,MAAM,YAAY,KAAK,CAAC;AAAA,IACtF,SAASA,OAAM,KAAK,EAAE,OAAO,WAAW,UAAU,KAAK,CAAC;AAAA,IACxD,OAAOA,OAAM,KAAK,EAAE,OAAO,SAAS,YAAY,KAAK,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOtD,YAAYA,OAAM,KAAK;AAAA,MAAE,OAAO;AAAA,MAAc,YAAY;AAAA,MACtD,aAAa;AAAA,IAAgG,CAAC;AAAA,IAElH,SAASA,OAAM,KAAK;AAAA,MAChB,OAAO;AAAA,MACP,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,QAAQA,OAAM,OAAO,CAAC,WAAW,aAAa,WAAW,UAAU,QAAQ,YAAY,GAAG;AAAA,MACtF,OAAO;AAAA,MACP,UAAU;AAAA,MACV,cAAc;AAAA,IAClB,CAAC;AAAA,IAED,UAAUA,OAAM,OAAO,EAAE,OAAO,YAAY,cAAc,EAAE,CAAC;AAAA,IAC7D,eAAeA,OAAM,OAAO,EAAE,OAAO,iBAAiB,cAAc,EAAE,CAAC;AAAA,IAEvE,YAAYA,OAAM,KAAK,EAAE,OAAO,cAAc,aAAa,0BAA0B,CAAC;AAAA,IACtF,YAAYA,OAAM,OAAO,EAAE,OAAO,kBAAkB,CAAC;AAAA,IACrD,iBAAiBA,OAAM,OAAO,EAAE,OAAO,uBAAuB,CAAC;AAAA,IAC/D,mBAAmBA,OAAM,OAAO,EAAE,OAAO,yBAAyB,CAAC;AAAA,IACnE,OAAOA,OAAM,SAAS,EAAE,OAAO,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,IAMxC,YAAYA,OAAM,SAAS,EAAE,OAAO,cAAc,UAAU,KAAK,CAAC;AAAA,IAClE,YAAYA,OAAM,SAAS,EAAE,OAAO,aAAa,CAAC;AAAA,EACtD;AAAA,EAEA,SAAS;AAAA;AAAA,IAEL,EAAE,QAAQ,CAAC,mBAAmB,gBAAgB,SAAS,GAAG,QAAQ,KAAK;AAAA;AAAA,IAEvE,EAAE,QAAQ,CAAC,UAAU,iBAAiB,iBAAiB,EAAE;AAAA;AAAA,IAEzD,EAAE,QAAQ,CAAC,UAAU,YAAY,EAAE;AAAA,IACnC,EAAE,QAAQ,CAAC,iBAAiB,EAAE;AAAA;AAAA,IAE9B,EAAE,QAAQ,CAAC,cAAc,UAAU,iBAAiB,EAAE;AAAA,EAC1D;AACJ,CAAC;;;ACxFD,SAAS,gBAAAC,eAAc,SAAAC,cAAa;AAmB7B,IAAM,yBAAyBD,cAAa,OAAO;AAAA,EACtD,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aAAa;AAAA,EACb,MAAM;AAAA,EACN,UAAU;AAAA,EACV,WAAW;AAAA,EACX,aAAa;AAAA,EACb,aAAa;AAAA,EACb,eAAe,CAAC,WAAW,SAAS,WAAW,WAAW,QAAQ;AAAA,EAElE,QAAQ;AAAA,IACJ,IAAIC,OAAM,KAAK,EAAE,OAAO,iBAAiB,UAAU,MAAM,UAAU,KAAK,CAAC;AAAA,IAEzE,SAASA,OAAM,KAAK;AAAA,MAChB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,OAAOA,OAAM,KAAK;AAAA,MACd,OAAO;AAAA,MACP,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,cAAc;AAAA,MACd,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,SAASA,OAAM,KAAK;AAAA,MAChB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,cAAc;AAAA,MACd,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,SAASA,OAAM,QAAQ;AAAA,MACnB,OAAO;AAAA,MACP,cAAc;AAAA,MACd,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,QAAQA,OAAM,OAAO,CAAC,QAAQ,SAAS,QAAQ,GAAG;AAAA,MAC9C,OAAO;AAAA,MACP,UAAU;AAAA,MACV,cAAc;AAAA,MACd,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,aAAaA,OAAM,KAAK;AAAA,MACpB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,YAAYA,OAAM,SAAS,EAAE,OAAO,cAAc,UAAU,KAAK,CAAC;AAAA,IAClE,YAAYA,OAAM,SAAS,EAAE,OAAO,cAAc,UAAU,MAAM,CAAC;AAAA,EACvE;AAAA,EAEA,SAAS;AAAA,IACL,EAAE,QAAQ,CAAC,WAAW,SAAS,SAAS,GAAG,QAAQ,KAAK;AAAA,IACxD,EAAE,QAAQ,CAAC,OAAO,EAAE;AAAA,EACxB;AACJ,CAAC;;;AClFD,SAAS,gBAAAC,eAAc,SAAAC,cAAa;AAiB7B,IAAM,2BAA2BD,cAAa,OAAO;AAAA,EACxD,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aAAa;AAAA,EACb,MAAM;AAAA,EACN,UAAU;AAAA,EACV,WAAW;AAAA,EACX,aAAa;AAAA,EACb,aAAa;AAAA,EACb,eAAe,CAAC,SAAS,aAAa,WAAW,YAAY;AAAA,EAE7D,QAAQ;AAAA,IACJ,IAAIC,OAAM,KAAK,EAAE,OAAO,mBAAmB,UAAU,MAAM,UAAU,KAAK,CAAC;AAAA,IAE3E,OAAOA,OAAM,KAAK;AAAA,MACd,OAAO;AAAA,MACP,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,WAAWA,OAAM,KAAK;AAAA,MAClB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,SAASA,OAAM,QAAQ;AAAA,MACnB,OAAO;AAAA,MACP,cAAc;AAAA,MACd,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,YAAYA,OAAM,SAAS,EAAE,OAAO,cAAc,UAAU,KAAK,CAAC;AAAA,EACtE;AAAA,EAEA,SAAS;AAAA,IACL,EAAE,QAAQ,CAAC,SAAS,WAAW,GAAG,QAAQ,KAAK;AAAA,IAC/C,EAAE,QAAQ,CAAC,OAAO,EAAE;AAAA,EACxB;AACJ,CAAC;;;AC1DD,SAAS,gBAAAC,eAAc,SAAAC,cAAa;AAgB7B,IAAM,uBAAuBD,cAAa,OAAO;AAAA,EACpD,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aAAa;AAAA,EACb,MAAM;AAAA,EACN,UAAU;AAAA,EACV,WAAW;AAAA,EACX,aAAa;AAAA,EACb,aAAa;AAAA,EACb,eAAe,CAAC,SAAS,WAAW,UAAU,WAAW;AAAA,EAEzD,QAAQ;AAAA,IACJ,IAAIC,OAAM,KAAK,EAAE,OAAO,eAAe,UAAU,MAAM,UAAU,KAAK,CAAC;AAAA,IAEvE,OAAOA,OAAM,KAAK,EAAE,OAAO,SAAS,UAAU,MAAM,YAAY,KAAK,CAAC;AAAA,IAEtE,SAASA,OAAM,KAAK;AAAA,MAChB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,cAAc;AAAA,MACd,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,QAAQA,OAAM,KAAK;AAAA,MACf,OAAO;AAAA,MACP,UAAU;AAAA,MACV,cAAc;AAAA,MACd,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,SAASA,OAAM,OAAO;AAAA,MAClB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,cAAc;AAAA,IAClB,CAAC;AAAA,IAED,SAASA,OAAM,KAAK;AAAA,MAChB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,MAAMA,OAAM,SAAS;AAAA,MACjB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,QAAQA,OAAM,OAAO,CAAC,YAAY,QAAQ,QAAQ,MAAM,GAAG;AAAA,MACvD,OAAO;AAAA,MACP,UAAU;AAAA,MACV,cAAc;AAAA,IAClB,CAAC;AAAA,IAED,WAAWA,OAAM,QAAQ;AAAA,MACrB,OAAO;AAAA,MACP,cAAc;AAAA,MACd,aAAa;AAAA,IACjB,CAAC;AAAA,IAED,YAAYA,OAAM,SAAS,EAAE,OAAO,cAAc,UAAU,KAAK,CAAC;AAAA,IAClE,YAAYA,OAAM,SAAS,EAAE,OAAO,cAAc,UAAU,MAAM,CAAC;AAAA,EACvE;AAAA,EAEA,SAAS;AAAA,IACL,EAAE,QAAQ,CAAC,SAAS,WAAW,QAAQ,EAAE;AAAA,IACzC,EAAE,QAAQ,CAAC,OAAO,EAAE;AAAA,EACxB;AACJ,CAAC;;;AvBAM,IAAM,yBAAN,MAA+C;AAAA,EAWlD,YAAY,UAAyC,CAAC,GAAG;AAVzD,gBAAO;AACP,mBAAU;AACV,gBAAO;AACP,wBAAe,CAAC,iCAAiC;AAQ7C,SAAK,UAAU;AAAA,MACX,eAAe;AAAA,MACf,kBAAkB;AAAA,MAClB,gBAAgB;AAAA,MAChB,oBAAoB;AAAA,MACpB,iBAAiB,CAAC;AAAA,MAClB,eAAe;AAAA,MACf,kBAAkB;AAAA,MAClB,GAAG;AAAA,IACP;AAAA,EACJ;AAAA,EAEA,MAAM,KAAK,KAAmC;AAK1C,UAAM,UAAU,MAA+B;AAC3C,UAAI;AACA,eAAO,IAAI,WAAwB,MAAM,KAAK,IAAI,WAAwB,UAAU;AAAA,MACxF,QAAQ;AACJ,eAAO;AAAA,MACX;AAAA,IACJ;AAEA,UAAM,UAAU,IAAI,iBAAiB;AAAA,MACjC,QAAQ,IAAI;AAAA,MACZ;AAAA,MACA,iBAAiB,KAAK,QAAQ;AAAA,IAClC,CAAC;AAED,QAAI,KAAK,QAAQ,eAAe;AAC5B,cAAQ,gBAAgB,mBAAmB,EAAE,QAAQ,CAAC,CAAC;AAAA,IAC3D;AAEA,QAAI,gBAAgB,aAAa,OAAO;AASxC,QAAI,gBAAgB,gBAAgB,OAAO;AAM3C,QAAI,WAA2C,UAAU,EAAE,SAAS;AAAA,MAChE,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,SAAS;AAAA,MACT,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACJ;AAAA,MACA,yBAAyB;AAAA,QACrB;AAAA,UACI,KAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU;AAAA,UACV,OAAO;AAAA,YACH,EAAE,IAAI,gCAAgC,MAAM,UAAU,OAAO,4BAA4B,YAAY,+BAA+B,MAAM,aAAa,gBAAgB,8BAA8B;AAAA,YACrM,EAAE,IAAI,kCAAkC,MAAM,UAAU,OAAO,8BAA8B,YAAY,iCAAiC,MAAM,OAAO,gBAAgB,gCAAgC;AAAA,YACvM,EAAE,IAAI,8BAA8B,MAAM,UAAU,OAAO,0BAA0B,YAAY,6BAA6B,MAAM,aAAa,gBAAgB,4BAA4B;AAAA,UACjM;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ,CAAC;AAWD,QAAI,OAAO,IAAI,SAAS,YAAY;AAChC,UAAI,KAAK,gBAAgB,YAAY;AACjC,cAAM,SAAS,QAAQ;AACvB,YAAI,OAAQ,OAAM,KAAK,sBAAsB,QAAQ,GAAG;AAAA,MAC5D,CAAC;AAAA,IACL;AAOA,QAAI,OAAO,IAAI,SAAS,YAAY;AAChC,YAAM,gBAAgB,IAAI,0BAA0B,EAAE,QAAQ,CAAC;AAC/D,YAAM,WAAW,MAAM;AACnB,YAAI;AACA,iBAAO,IAAI,WAA4D,OAAO;AAAA,QAClF,QAAQ;AACJ,iBAAO;AAAA,QACX;AAAA,MACJ;AACA,UAAI,KAAK,gBAAgB,YAAY;AACjC,YAAI,SAAS,GAAG;AACZ,kBAAQ,gBAAgB,mBAAmB,EAAE,UAAU,SAAS,OAAO,cAAc,CAAC,CAAC;AACvF,cAAI,OAAO,KAAK,0EAA0E;AAAA,QAC9F;AAAA,MACJ,CAAC;AAAA,IACL;AAIA,QAAI,KAAK,QAAQ,oBAAoB,OAAO,IAAI,SAAS,YAAY;AACjE,UAAI,KAAK,gBAAgB,YAAY;AACjC,cAAM,SAAS,QAAQ;AACvB,YAAI,CAAC,QAAQ;AACT,cAAI,OAAO,KAAK,+FAA0F;AAC1G;AAAA,QACJ;AACA,cAAM,SAAS,IAAI,sBAAsB,QAAQ,EAAE,gBAAgB,KAAK,QAAQ,eAAe,CAAC;AAChG,gBAAQ,UAAU,MAAM;AAExB,YAAI;AACJ,YAAI;AACA,oBAAU,IAAI,WAA4B,SAAS;AAAA,QACvD,QAAQ;AACJ,oBAAU;AAAA,QACd;AAEA,aAAK,aAAa,IAAI,uBAAuB;AAAA,UACzC,QAAQ,UAAU,QAAQ,GAAG,IAAIC,YAAW,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,UACzD;AAAA,UACA,UAAU;AAAA,UACV,gBAAgB,EAAE,QAAQ,IAAI,OAAO;AAAA,UACrC;AAAA,UACA,gBAAgB,KAAK,QAAQ;AAAA,UAC7B,YAAY,KAAK,QAAQ;AAAA,UACzB,QAAQ,IAAI;AAAA,QAChB,CAAC;AACD,aAAK,WAAW,MAAM;AACtB,YAAI,OAAO;AAAA,UACP,0DAA0D,KAAK,QAAQ,cAAc,cAAc,UAAU,gBAAgB,eAAe;AAAA,QAChJ;AAKA,cAAM,aAAa,IAAI,cAAc,QAAQ,EAAE,gBAAgB,KAAK,QAAQ,eAAe,CAAC;AAC5F,gBAAQ,cAAc,UAAU;AAChC,aAAK,iBAAiB,IAAI,eAAe;AAAA,UACrC,QAAQ,QAAQ,QAAQ,GAAG,IAAIA,YAAW,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,UACvD,QAAQ;AAAA,UACR;AAAA,UACA,gBAAgB,KAAK,QAAQ;AAAA,UAC7B,YAAY,KAAK,QAAQ;AAAA,UACzB,QAAQ,IAAI;AAAA,QAChB,CAAC;AACD,aAAK,eAAe,MAAM;AAC1B,YAAI,OAAO;AAAA,UACP,wEAAwE,KAAK,QAAQ,cAAc;AAAA,QACvG;AAAA,MACJ,CAAC;AAAA,IACL;AAMA,QAAI,KAAK,QAAQ,gBAAgB,KAAK,OAAO,IAAI,SAAS,YAAY;AAClE,UAAI,KAAK,gBAAgB,YAAY;AACjC,cAAM,YAAY,IAAI,sBAAsB,EAAE,SAAS,QAAQ,IAAI,OAAO,CAAC;AAC3E,cAAM,OAAO,KAAK,QAAQ;AAC1B,cAAM,QAAQ,MAAM;AAChB,eAAK,UAAU,MAAM,IAAI,EAAE;AAAA,YAAM,CAAC,QAC9B,IAAI,OAAO,KAAK,uCAAwC,KAAe,WAAW,GAAG,EAAE;AAAA,UAC3F;AAAA,QACJ;AACA,cAAM;AACN,aAAK,iBAAiB,YAAY,OAAO,KAAK,QAAQ,gBAAgB;AACtE,aAAK,eAAe,QAAQ;AAC5B,YAAI,OAAO;AAAA,UACP,qCAAqC,IAAI,WAAW,KAAK,MAAM,KAAK,QAAQ,mBAAmB,GAAI,CAAC;AAAA,QACxG;AAAA,MACJ,CAAC;AAAA,IACL;AAEA,QAAI,OAAO;AAAA,MACP,iDAAiD,QAAQ,sBAAsB,EAAE,KAAK,IAAI,KAAK,QAAQ;AAAA,IAC3G;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAc,sBAAsB,QAAqB,KAAmC;AAGxF,UAAM,OAAQ,OAA6E;AAC3F,QAAI,OAAO,SAAS,WAAY;AAChC,UAAM,UAAU;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACJ;AACA,eAAW,OAAO,SAAS;AACvB,UAAI;AACA,cAAM,KAAK,KAAK,QAAS,IAAyB,IAAI;AAAA,MAC1D,SAAS,KAAK;AACV,YAAI,OAAO,KAAK,mCAAoC,IAAyB,IAAI,mBAAe,KAAe,WAAW,GAAG,EAAE;AAAA,MACnI;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA,EAGA,MAAM,OAAsB;AACxB,UAAM,KAAK,YAAY,KAAK;AAC5B,SAAK,aAAa;AAClB,UAAM,KAAK,gBAAgB,KAAK;AAChC,SAAK,iBAAiB;AACtB,QAAI,KAAK,gBAAgB;AACrB,oBAAc,KAAK,cAAc;AACjC,WAAK,iBAAiB;AAAA,IAC1B;AAAA,EACJ;AACJ;;;AwBpVA,SAAS,cAAAC,mBAAkB;AAgBpB,IAAM,2BAAN,MAA8D;AAAA,EAGjE,YACqB,iBAAiB,GAEjB,QAAsB,MAAM,KAAK,IAAI,GACxD;AAHmB;AAEA;AALrB,SAAiB,OAAO,oBAAI,IAAwC;AAAA,EAMjE;AAAA,EAEH,MAAM,QAAQ,OAA8C;AACxD,eAAW,KAAK,KAAK,KAAK,OAAO,GAAG;AAChC,UACI,EAAE,mBAAmB,MAAM,kBAC3B,EAAE,gBAAgB,MAAM,eACxB,EAAE,YAAY,MAAM,SACtB;AACE,eAAO,EAAE;AAAA,MACb;AAAA,IACJ;AACA,UAAM,KAAKC,YAAW;AACtB,UAAM,MAAM,KAAK,MAAM;AACvB,SAAK,KAAK,IAAI,IAAI;AAAA,MACd;AAAA,MACA,gBAAgB,MAAM;AAAA,MACtB,aAAa,MAAM;AAAA,MACnB,SAAS,MAAM;AAAA,MACf,OAAO,MAAM;AAAA,MACb,SAAS,MAAM,WAAW,CAAC;AAAA,MAC3B,gBAAgB,MAAM;AAAA;AAAA;AAAA,MAGtB,cAAc,cAAc,MAAM,aAAa,MAAM,gBAAgB,KAAK,cAAc;AAAA,MACxF,QAAQ;AAAA,MACR,UAAU;AAAA;AAAA;AAAA,MAGV,eAAe,MAAM;AAAA,MACrB,WAAW,MAAM;AAAA,MACjB,WAAW;AAAA,MACX,WAAW;AAAA,IACf,CAAC;AACD,WAAO;AAAA,EACX;AAAA,EAEA,MAAM,MAAM,MAA2D;AACnE,UAAM,MAAM,KAAK,OAAO,KAAK,MAAM;AAEnC,eAAW,KAAK,KAAK,KAAK,OAAO,GAAG;AAChC,UAAI,EAAE,WAAW,gBAAgB,EAAE,aAAa,KAAK,MAAM,KAAK,YAAY;AACxE,UAAE,SAAS;AACX,UAAE,YAAY;AACd,UAAE,YAAY;AACd,UAAE,YAAY;AAAA,MAClB;AAAA,IACJ;AACA,UAAM,MAAoC,CAAC;AAC3C,eAAW,KAAK,KAAK,KAAK,OAAO,GAAG;AAChC,UAAI,IAAI,UAAU,KAAK,MAAO;AAC9B,UAAI,EAAE,WAAW,UAAW;AAC5B,UAAI,EAAE,aAAa,KAAM;AACzB,UAAI,KAAK,aAAa,EAAE,iBAAiB,KAAK,UAAU,MAAO;AAC/D,UAAI,EAAE,iBAAiB,QAAQ,EAAE,gBAAgB,IAAK;AACtD,QAAE,SAAS;AACX,QAAE,YAAY,KAAK;AACnB,QAAE,YAAY;AACd,QAAE,YAAY;AACd,UAAI,KAAK,EAAE,GAAG,EAAE,CAAC;AAAA,IACrB;AACA,WAAO;AAAA,EACX;AAAA,EAEA,MAAM,YAAY,MAA2D;AACzE,UAAM,MAAM,KAAK,OAAO,KAAK,MAAM;AAEnC,eAAW,KAAK,KAAK,KAAK,OAAO,GAAG;AAChC,UAAI,EAAE,WAAW,gBAAgB,EAAE,aAAa,KAAK,MAAM,KAAK,YAAY;AACxE,UAAE,SAAS;AACX,UAAE,YAAY;AACd,UAAE,YAAY;AACd,UAAE,YAAY;AAAA,MAClB;AAAA,IACJ;AAGA,UAAM,MAAoC,CAAC;AAC3C,eAAW,KAAK,KAAK,KAAK,OAAO,GAAG;AAChC,UAAI,EAAE,WAAW,UAAW;AAC5B,UAAI,EAAE,aAAa,KAAM;AACzB,UAAI,KAAK,aAAa,EAAE,iBAAiB,KAAK,UAAU,MAAO;AAC/D,UAAI,EAAE,iBAAiB,QAAQ,EAAE,gBAAgB,IAAK;AACtD,QAAE,SAAS;AACX,QAAE,YAAY,KAAK;AACnB,QAAE,YAAY;AACd,QAAE,YAAY;AACd,UAAI,KAAK,EAAE,GAAG,EAAE,CAAC;AAAA,IACrB;AACA,WAAO;AAAA,EACX;AAAA,EAEA,MAAM,IAAI,IAAY,QAAkC;AACpD,UAAM,IAAI,KAAK,KAAK,IAAI,EAAE;AAC1B,QAAI,CAAC,EAAG;AACR,UAAM,MAAM,KAAK,MAAM;AACvB,MAAE,YAAY;AACd,MAAE,kBAAkB;AACpB,MAAE,YAAY;AACd,MAAE,YAAY;AACd,MAAE,YAAY;AACd,QAAI,OAAO,SAAS;AAChB,QAAE,SAAS;AACX,QAAE,gBAAgB;AAClB,QAAE,QAAQ;AAAA,IACd,WAAW,OAAO,YAAY;AAC1B,QAAE,SAAS;AACX,QAAE,QAAQ,OAAO;AAAA,IACrB,WAAW,OAAO,MAAM;AACpB,QAAE,SAAS;AACX,QAAE,QAAQ,OAAO;AAAA,IACrB,OAAO;AACH,QAAE,SAAS;AACX,QAAE,gBAAgB,OAAO;AACzB,QAAE,QAAQ,OAAO;AAAA,IACrB;AAAA,EACJ;AAAA,EAEA,MAAM,KAAK,QAAsG;AAC7G,QAAI,OAAO,CAAC,GAAG,KAAK,KAAK,OAAO,CAAC;AACjC,QAAI,QAAQ,OAAQ,QAAO,KAAK,OAAO,CAAC,MAAM,EAAE,WAAW,OAAO,MAAM;AACxE,QAAI,QAAQ,eAAgB,QAAO,KAAK,OAAO,CAAC,MAAM,EAAE,mBAAmB,OAAO,cAAc;AAChG,WAAO,KAAK,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE;AAAA,EACrC;AACJ;;;ACnJA,SAAS,cAAAC,mBAAkB;AAkBpB,IAAM,mBAAN,MAA8C;AAAA,EAA9C;AACH,SAAiB,OAAO,oBAAI,IAA0B;AAEtD;AAAA,SAAiB,QAAQ,oBAAI,IAAoB;AAAA;AAAA,EAEjD,MAAM,QAAQ,OAA0C;AACpD,UAAM,WAAW,GAAG,MAAM,MAAM,KAAK,MAAM,QAAQ;AACnD,UAAM,WAAW,KAAK,MAAM,IAAI,QAAQ;AACxC,QAAI,SAAU,QAAO;AAErB,UAAM,KAAKC,YAAW;AACtB,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,MAAoB;AAAA,MACtB;AAAA,MACA,QAAQ,MAAM;AAAA,MACd,OAAO,MAAM;AAAA,MACb,UAAU,MAAM;AAAA,MAChB,OAAO,MAAM;AAAA,MACb,KAAK,MAAM;AAAA,MACX,QAAQ,MAAM,UAAU;AAAA,MACxB,SAAS,MAAM;AAAA,MACf,eAAe,MAAM;AAAA,MACrB,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,MACf,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,WAAW;AAAA,MACX,WAAW;AAAA,IACf;AACA,SAAK,KAAK,IAAI,IAAI,GAAG;AACrB,SAAK,MAAM,IAAI,UAAU,EAAE;AAC3B,WAAO;AAAA,EACX;AAAA,EAEA,MAAM,MAAM,MAAiD;AACzD,UAAM,MAAM,KAAK,OAAO,KAAK,IAAI;AACjC,UAAM,UAA0B,CAAC;AAEjC,eAAW,OAAO,KAAK,KAAK,OAAO,GAAG;AAClC,UACI,IAAI,WAAW,eACf,IAAI,cAAc,UAClB,MAAM,IAAI,YAAY,KAAK,YAC7B;AACE,YAAI,SAAS;AACb,YAAI,YAAY;AAChB,YAAI,YAAY;AAChB,YAAI,YAAY;AAAA,MACpB;AAAA,IACJ;AAEA,eAAW,OAAO,KAAK,KAAK,OAAO,GAAG;AAClC,UAAI,QAAQ,UAAU,KAAK,MAAO;AAClC,UAAI,IAAI,WAAW,UAAW;AAC9B,UAAI,IAAI,gBAAgB,UAAa,IAAI,cAAc,IAAK;AAC5D,UAAI,KAAK,WAAW;AAChB,cAAM,IAAI,cAAc,IAAI,OAAO,KAAK,UAAU,KAAK;AACvD,YAAI,MAAM,KAAK,UAAU,MAAO;AAAA,MACpC;AACA,UAAI,SAAS;AACb,UAAI,YAAY,KAAK;AACrB,UAAI,YAAY;AAChB,UAAI,YAAY;AAChB,cAAQ,KAAK,EAAE,GAAG,IAAI,CAAC;AAAA,IAC3B;AACA,WAAO;AAAA,EACX;AAAA,EAEA,MAAM,IAAI,IAAY,QAAsC;AACxD,UAAM,MAAM,KAAK,KAAK,IAAI,EAAE;AAC5B,QAAI,CAAC,IAAK;AACV,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,YAAY;AAChB,QAAI,kBAAkB;AACtB,QAAI,YAAY;AAChB,QAAI,YAAY;AAChB,QAAI,YAAY;AAChB,QAAI,eAAe,OAAO;AAC1B,QAAI,eAAe,OAAO;AAE1B,QAAI;AACJ,QAAI,OAAO,SAAS;AAChB,eAAS;AACT,UAAI,cAAc;AAClB,UAAI,QAAQ;AAAA,IAChB,WAAW,OAAO,MAAM;AACpB,eAAS;AACT,UAAI,QAAQ,OAAO;AACnB,UAAI,cAAc;AAAA,IACtB,OAAO;AACH,eAAS;AACT,UAAI,QAAQ,OAAO;AACnB,UAAI,cAAc,OAAO;AAAA,IAC7B;AACA,QAAI,SAAS;AAAA,EACjB;AAAA,EAEA,MAAM,KAAK,QAAoF;AAC3F,QAAI,MAAM,MAAM,KAAK,KAAK,KAAK,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE;AAC9D,QAAI,QAAQ,OAAQ,OAAM,IAAI,OAAO,CAAC,MAAM,EAAE,WAAW,OAAO,MAAM;AACtE,QAAI,QAAQ,OAAQ,OAAM,IAAI,OAAO,CAAC,MAAM,EAAE,WAAW,OAAO,MAAM;AACtE,WAAO;AAAA,EACX;AAAA,EAEA,MAAM,UAAU,IAAmC;AAC/C,UAAM,MAAM,KAAK,KAAK,IAAI,EAAE;AAC5B,QAAI,CAAC,KAAK;AACN,YAAM,IAAI,mBAAmB,iBAAiB,EAAE,eAAe,WAAW;AAAA,IAC9E;AACA,QAAI,IAAI,WAAW,aAAa,IAAI,WAAW,YAAY,IAAI,WAAW,QAAQ;AAC9E,YAAM,IAAI;AAAA,QACN,iBAAiB,EAAE,SAAS,IAAI,MAAM;AAAA,QACtC;AAAA,MACJ;AAAA,IACJ;AACA,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,SAAS;AACb,QAAI,WAAW;AACf,QAAI,YAAY;AAChB,QAAI,YAAY;AAChB,QAAI,cAAc;AAClB,QAAI,QAAQ;AACZ,QAAI,eAAe;AACnB,QAAI,eAAe;AACnB,QAAI,YAAY;AAChB,WAAO,EAAE,GAAG,IAAI;AAAA,EACpB;AACJ;","names":["randomUUID","msg","msg","deliveries","delivered","randomUUID","randomUUID","randomUUID","SINGLE_NODE_CLUSTER","stableNodeOffset","msg","USER_OBJECT","ObjectSchema","Field","ObjectSchema","Field","ObjectSchema","Field","ObjectSchema","Field","ObjectSchema","Field","ObjectSchema","Field","randomUUID","randomUUID","randomUUID","randomUUID","randomUUID"]}