@clovnet/plugin-sdk 0.1.4

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/manifest.ts","../src/permissions.ts","../src/settings.ts","../src/hooks.ts","../src/define.ts","../src/routes.ts","../src/tasks.ts","../src/http.ts","../src/flows.ts","../../domain-events/src/names.ts","../../domain-events/src/versions.ts","../../domain-events/src/subjects.ts"],"sourcesContent":["import type { PluginPermissions } from \"./permissions.js\";\nimport type { PluginSettingsSchema } from \"./settings.js\";\nimport type { PluginHookName } from \"./hooks.js\";\nimport type { PluginRouteDecl } from \"./routes.js\";\nimport type { PluginDatasetDecl } from \"./datasets.js\";\nimport type { PluginJobDecl } from \"./jobs.js\";\nimport type { PluginMigrationDecl } from \"./migrations.js\";\nimport type { PluginSurfacesDecl } from \"./surfaces.js\";\nimport type { FlowHookDecl } from \"./flows.js\";\nimport type { PluginActionDecl, PluginFrontendDecl } from \"./actions.js\";\n\n/** What a plugin *is* to the platform — drives wiring (provider bridge, consumers, backoffice). */\nexport type PluginKind = \"provider\" | \"backoffice\" | \"consumer\" | \"integration\" | \"content\";\n\n/** Capabilities a provider-kind plugin's adapter implements. */\nexport type ProviderCapability =\n | \"launch\"\n | \"balance\"\n | \"bet\"\n | \"settle\"\n | \"rollback\"\n | \"closeRound\"\n | \"freeSpin\"\n | \"bonusWin\";\n\n/**\n * The declarative descriptor every plugin ships. The host reads this to\n * install, grant permissions, render/validate settings and wire hooks. The\n * manifest is snapshotted into `plugin_versions` at publish time — treat every\n * field as a stable wire contract.\n */\nexport interface PluginManifest {\n /** Unique slug, e.g. `sloterv`. Doubles as the registry key. */\n key: string;\n name: string;\n author: string;\n /** SemVer version of this plugin build. */\n version: string;\n kind: PluginKind;\n /** SemVer range against the host's RUNTIME_API_VERSION, e.g. `>=0.1.0 <0.2.0`. */\n runtimeCompat: string;\n description?: string;\n\n /** Explicit allowlists. Anything not declared here is denied and audited. */\n permissions: PluginPermissions;\n\n /** Typed per-tenant settings; the backoffice renders a form from this. */\n settings: PluginSettingsSchema;\n\n /** Provider plugins only: the provider key this plugin owns + its capabilities. */\n provider?: { providerKey: string; capabilities: ProviderCapability[] };\n\n /**\n * Game-catalog content surface (§7b). Presence of this block IS the grant:\n * the host registers a tenant-owned catalog source for (tenant, plugin) on\n * enable and exposes `ctx.catalog`. Allowed kinds: provider | content |\n * integration (doctor-enforced). `sourceName` labels the source in the\n * backoffice (defaults to the plugin name).\n */\n catalog?: { sourceName?: string };\n\n /** Lifecycle hooks this plugin implements (informational; host still probes). */\n hooks?: Partial<Record<PluginHookName, true>>;\n\n /** Regions the plugin is allowed to run in (omitted = all). */\n regions?: string[];\n\n // ── v2 developer surface ────────────────────────────────────────────────────\n\n /** HTTP routes this plugin adds to the casino API (host PluginRouter). */\n routes?: PluginRouteDecl[];\n\n /** Plugin-owned datasets, provisioned at install, accessed via `ctx.datasets`. */\n datasets?: Record<string, PluginDatasetDecl>;\n\n /** Ordered dataset/settings migrations run by `UpgradePluginCommand`. */\n migrations?: PluginMigrationDecl[];\n\n /** Scheduled jobs run per tenant by the worker's JobScheduler. */\n jobs?: Record<string, PluginJobDecl>;\n\n /**\n * Flow-stage participation (PLAYER_ACCOUNT_BUILD_PROMPT.md §2). Each entry\n * must name a catalog stage, an allowed mode, and a handler in\n * `handlers.flows`; the stage must also be granted in `permissions.flows`.\n */\n flows?: FlowHookDecl[];\n\n /**\n * Outbound HTTP allowlist for `ctx.http` — exact hosts or one-level\n * wildcards (`*.sloterv.com`). HTTPS only. Nothing declared = no egress.\n */\n network?: { allowedHosts: string[] };\n\n /** Backoffice surface descriptors (data only — no UI is built now). */\n surfaces?: PluginSurfacesDecl;\n\n /** Task type to enqueue after install commits (async dataset seeding). */\n install?: { seedTask?: string };\n\n // ── v3 frontend surface (PLUGIN_ACTIONS_RUNTIME_BUILD_PROMPT.md) ───────────\n\n /**\n * SDK-exposed action aliases of declared `public`/`player` routes, served\n * through the tenant extension catalog with JSON Schemas derived from the\n * routes' Zod declarations at publish time.\n */\n actions?: PluginActionDecl[];\n\n /** Frontend widget descriptors binding catalog actions to casino UI slots. */\n frontend?: PluginFrontendDecl;\n}\n\n/** Release channels a version can be published to. */\nexport const PluginChannels = [\"dev\", \"beta\", \"stable\"] as const;\nexport type PluginChannel = (typeof PluginChannels)[number];\n\n/** Per-tenant enablement state (host-managed, `tenant_plugins.state`). */\nexport const TenantPluginStates = [\"installed\", \"enabled\", \"disabled\", \"errored\"] as const;\nexport type TenantPluginState = (typeof TenantPluginStates)[number];\n\n/** Catalog lifecycle of a published version (`plugin_versions.status`). */\nexport const PluginVersionStatuses = [\"draft\", \"published\", \"yanked\"] as const;\nexport type PluginVersionStatus = (typeof PluginVersionStatuses)[number];\n","import type { DomainEventName } from \"@cwe/domain-events\";\nimport type { DataScope } from \"./data.js\";\n\n/**\n * Explicit allowlists — the whole permission model. The host's capability\n * layer enforces these at runtime: a command not in `commands` throws\n * `ForbiddenError` and writes a `plugin.permission_denied` audit row; an event\n * not in `events.subscribe` is never delivered; an emitted event must match\n * `events.emit` (namespaced `plugin.<key>.*`); a read model whose scope is\n * not in `dataScopes` is refused; a foreign dataset not in `datasets.read`\n * is invisible.\n */\nexport interface PluginPermissions {\n /** Command names the plugin may execute through `ctx.commands.execute`. */\n commands: string[];\n events: {\n /** Typed domain events the plugin may subscribe to. */\n subscribe: DomainEventName[];\n /** Event names the plugin may emit — must be namespaced `plugin.<key>.*`. */\n emit: string[];\n };\n /**\n * Read-model scopes for `ctx.data` (v2 vocabulary, see `data.ts`). Legacy\n * v1 `PluginScope` values are still accepted for backward compatibility but\n * grant no read models.\n */\n dataScopes?: Array<DataScope | PluginScope>;\n /** Foreign dataset reads: `\"ownerPluginKey.datasetName\"` entries. */\n datasets?: { read?: string[] };\n /**\n * Flow stages the plugin may hook (`\"signup.validate\"`, …). A `flows` entry\n * whose stage is not granted here fails publish; runtime enforcement fails\n * closed with a `plugin.permission_denied` audit like every capability.\n */\n flows?: string[];\n /** Provider plugins: which provider keys this plugin may own. */\n providerKeys?: string[];\n /**\n * Short event types (`<key>.<type>`, i.e. the emitted name minus its\n * `plugin.` prefix) allowed to fan out to player sockets on the\n * `ext.<key>` realtime channel. Must be a subset of `events.emit`\n * (doctor-enforced); anything not listed is dropped by the gateway.\n */\n frontendEvents?: string[];\n /**\n * @deprecated v1 field — use `manifest.network.allowedHosts`. The host\n * honours the union of both while plugins migrate.\n */\n networkAllow?: string[];\n}\n\n/** Coarse capability scopes (vocabulary for `dataScopes`). */\nexport const PluginScopes = {\n WalletRead: \"wallet:read\",\n WalletWrite: \"wallet:write\",\n PlayerRead: \"player:read\",\n PlayerWrite: \"player:write\",\n CatalogRead: \"catalog:read\",\n BonusWrite: \"bonus:write\",\n EventsSubscribe: \"events:subscribe\",\n EventsPublish: \"events:publish\",\n CommandsExecute: \"commands:execute\",\n} as const;\n\nexport type PluginScope = (typeof PluginScopes)[keyof typeof PluginScopes];\n\n/** Prefix every plugin-emitted event name must carry: `plugin.<key>.` */\nexport function pluginEventPrefix(pluginKey: string): string {\n return `plugin.${pluginKey}.`;\n}\n","import { z } from \"zod\";\n\n/**\n * Typed per-tenant settings. Each field pairs UI metadata (label, type,\n * secret flag) with the Zod validator actually enforced at the boundary.\n * Secret fields are stored encrypted in `plugin_secrets` and are write-only\n * over the API; non-secret values live in `plugin_settings.values`.\n */\nexport type PluginSettingsFieldType = \"string\" | \"number\" | \"boolean\" | \"enum\" | \"json\";\n\nexport interface PluginSettingsField {\n type: PluginSettingsFieldType;\n label: string;\n description?: string;\n required: boolean;\n /** Secret fields go to `plugin_secrets`, never returned, never logged. */\n secret?: boolean;\n default?: unknown;\n enumValues?: string[];\n /** The actual validator used at the boundary. */\n zod: z.ZodTypeAny;\n}\n\nexport interface PluginSettingsSchema {\n fields: Record<string, PluginSettingsField>;\n /** Optional migration when a new version changes the schema shape. */\n migrate?: (previous: Record<string, unknown>) => Record<string, unknown>;\n}\n\ntype FieldOptions = {\n label: string;\n description?: string;\n required?: boolean;\n secret?: boolean;\n default?: unknown;\n /** Override the derived validator (e.g. `z.string().url()`). */\n zod?: z.ZodTypeAny;\n};\n\nfunction finalize(\n type: PluginSettingsFieldType,\n base: z.ZodTypeAny,\n opts: FieldOptions,\n enumValues?: string[],\n): PluginSettingsField {\n const required = opts.required ?? false;\n let zod = opts.zod ?? base;\n if (!required) zod = zod.optional();\n if (opts.default !== undefined) zod = zod.default(opts.default);\n return {\n type,\n label: opts.label,\n description: opts.description,\n required,\n secret: opts.secret,\n default: opts.default,\n enumValues,\n zod,\n };\n}\n\n/** Zod-backed field helpers — the ergonomic way to author a settings schema. */\nexport const settingsField = {\n string: (opts: FieldOptions) => finalize(\"string\", z.string().min(1), opts),\n number: (opts: FieldOptions) => finalize(\"number\", z.number().finite(), opts),\n boolean: (opts: FieldOptions) => finalize(\"boolean\", z.boolean(), opts),\n enum: (opts: FieldOptions & { enumValues: [string, ...string[]] }) =>\n finalize(\"enum\", z.enum(opts.enumValues), opts, opts.enumValues),\n json: (opts: FieldOptions) =>\n finalize(\"json\", z.union([z.record(z.unknown()), z.array(z.unknown())]), opts),\n};\n\n/** Wire-safe render descriptor for one field — everything except the validator. */\nexport interface PluginSettingsFieldDescriptor {\n type: PluginSettingsFieldType;\n label: string;\n description?: string;\n required: boolean;\n secret: boolean;\n /** Omitted for secret fields — defaults could leak intended values. */\n default?: unknown;\n enumValues?: string[];\n}\n\nexport type PluginSettingsSchemaDescriptor = Record<string, PluginSettingsFieldDescriptor>;\n\n/**\n * Serialize a settings schema for the backoffice form renderer and for the\n * `plugin_versions.settingsSchema` snapshot: strips `zod`, drops secret\n * defaults. This is what `GET …/settings/schema` returns.\n */\nexport function serializeSettingsSchema(\n schema: PluginSettingsSchema,\n): PluginSettingsSchemaDescriptor {\n const out: PluginSettingsSchemaDescriptor = {};\n for (const [key, field] of Object.entries(schema.fields)) {\n out[key] = {\n type: field.type,\n label: field.label,\n ...(field.description !== undefined ? { description: field.description } : {}),\n required: field.required,\n secret: field.secret === true,\n ...(field.secret !== true && field.default !== undefined ? { default: field.default } : {}),\n ...(field.enumValues !== undefined ? { enumValues: field.enumValues } : {}),\n };\n }\n return out;\n}\n\n/**\n * Compose the Zod object validating a settings payload. `secret: \"only\"`\n * validates just the secret fields (for the write-only secrets payload),\n * `\"exclude\"` just the non-secret values, `\"include\"` everything.\n */\nexport function settingsZodObject(\n schema: PluginSettingsSchema,\n secret: \"include\" | \"exclude\" | \"only\" = \"exclude\",\n): z.ZodObject<Record<string, z.ZodTypeAny>> {\n const shape: Record<string, z.ZodTypeAny> = {};\n for (const [key, field] of Object.entries(schema.fields)) {\n const isSecret = field.secret === true;\n if (secret === \"exclude\" && isSecret) continue;\n if (secret === \"only\" && !isSecret) continue;\n // Secrets are optional at write time — absent means \"keep the stored value\".\n shape[key] = secret === \"only\" ? field.zod.optional() : field.zod;\n }\n return z.object(shape).strict();\n}\n\n/** Default non-secret values derived from the schema (used to seed on install). */\nexport function defaultSettingsValues(schema: PluginSettingsSchema): Record<string, unknown> {\n const values: Record<string, unknown> = {};\n for (const [key, field] of Object.entries(schema.fields)) {\n if (field.secret === true) continue;\n if (field.default !== undefined) values[key] = field.default;\n }\n return values;\n}\n","import type { PluginContext } from \"./context.js\";\n\n/**\n * Lifecycle hooks a plugin MAY implement. The host invokes them through the\n * hook runner (per-hook try/catch + timeout): a throwing or hanging hook marks\n * the tenant's plugin `errored` with `lastError` set — it never rolls back the\n * core transaction that triggered it.\n */\nexport const PluginHookNames = [\n \"onInstall\",\n \"onEnable\",\n \"onConfigure\",\n \"onDisable\",\n \"onUninstall\",\n \"onMigrateSettings\",\n \"onUpgrade\",\n] as const;\n\nexport type PluginHookName = (typeof PluginHookNames)[number];\n\nexport interface PluginRuntimeHooks {\n onInstall?(ctx: PluginContext): Promise<void> | void;\n onEnable?(ctx: PluginContext): Promise<void> | void;\n /** Runs after settings were validated and saved for a tenant. */\n onConfigure?(ctx: PluginContext): Promise<void> | void;\n onDisable?(ctx: PluginContext): Promise<void> | void;\n onUninstall?(ctx: PluginContext): Promise<void> | void;\n /**\n * Runs when a tenant's saved settings were validated against an older\n * schema version than the one now enabled. Returns the migrated values.\n */\n onMigrateSettings?(\n ctx: PluginContext,\n previous: Record<string, unknown>,\n ): Promise<Record<string, unknown>> | Record<string, unknown>;\n /**\n * Runs after `UpgradePluginCommand` completed all migrations and flipped\n * `installedVersion` — the plugin is already running `toVersion`.\n */\n onUpgrade?(\n ctx: PluginContext,\n info: { fromVersion: string; toVersion: string },\n ): Promise<void> | void;\n}\n","import type { PluginManifest } from \"./manifest.js\";\nimport type { PluginRuntimeHooks } from \"./hooks.js\";\nimport type { PluginContext } from \"./context.js\";\nimport type { PluginRouteHandler } from \"./routes.js\";\nimport type { PluginJobHandler } from \"./jobs.js\";\nimport type { PluginTaskHandler } from \"./tasks.js\";\nimport type { PluginMigrationHandler } from \"./migrations.js\";\nimport type { FlowHookHandler } from \"./flows.js\";\n\n/**\n * Named implementations that manifest declarations reference by string:\n * `routes[].handler`, `jobs.<name>.handler`, `install.seedTask` /\n * `ctx.tasks.start` types, and `migrations[].handler`. The host validates at\n * load time that every referenced handler exists (`plugin doctor` reports any\n * mismatch before publish).\n */\nexport interface PluginHandlers {\n routes?: Record<string, PluginRouteHandler>;\n jobs?: Record<string, PluginJobHandler>;\n tasks?: Record<string, PluginTaskHandler>;\n migrations?: Record<string, PluginMigrationHandler>;\n /** Flow-stage handlers referenced by `manifest.flows[].handler` (§2). */\n flows?: Record<string, FlowHookHandler>;\n}\n\n/**\n * The full authored shape of a plugin — what a plugin package's entry module\n * exports and what the host loads. `setup` runs when the host activates the\n * plugin for a tenant: register event handlers via `ctx.events.on` and (for\n * provider plugins) the adapter via `ctx.registerProviderAdapter`.\n */\nexport interface PluginDefinition {\n manifest: PluginManifest;\n hooks?: PluginRuntimeHooks;\n handlers?: PluginHandlers;\n setup?(ctx: PluginContext): Promise<void> | void;\n}\n\n/** Identity helper that pins the authored object to the contract type. */\nexport function definePlugin(definition: PluginDefinition): PluginDefinition {\n return definition;\n}\n","import type { z } from \"zod\";\nimport type { Actor } from \"./host-types.js\";\nimport type { PluginContext } from \"./context.js\";\n\n/**\n * Plugin HTTP routes — how a plugin adds endpoints to the casino API. Routes\n * are DECLARED in the manifest and IMPLEMENTED as named handlers in\n * `definePlugin({ handlers.routes })`; the host's PluginRouter mounts them:\n *\n * public /api/ext/:pluginKey/* no session (tenant context as always)\n * player /api/ext/:pluginKey/* player session required\n * admin /admin/ext/:pluginKey/* staff RBAC `plugin:<key>:admin`\n * callback /callbacks/:pluginKey/* mandatory signature verification\n *\n * Routes exist only for tenants where the plugin is enabled — otherwise 404\n * (never 403; installation state must not leak). Handlers never see Fastify:\n * they get a sanitized PluginRequest and return a PluginResponse.\n */\nexport type PluginRouteSurface = \"public\" | \"player\" | \"admin\" | \"callback\";\n\nexport type PluginRouteMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\";\n\nexport interface PluginRouteDecl {\n method: PluginRouteMethod;\n /** Relative path, e.g. `/lobby` or `/games/:gameKey`. */\n path: string;\n surface: PluginRouteSurface;\n /** Name of the implementation in `definePlugin({ handlers: { routes } })`. */\n handler: string;\n /** Zod at the boundary — validated by the host before the handler runs. */\n input?: { params?: z.ZodTypeAny; query?: z.ZodTypeAny; body?: z.ZodTypeAny };\n /**\n * Optional Zod schema of the success response body. Not enforced at\n * runtime — used at publish/codegen time to derive the action catalog's\n * `output` JSON Schema and the generated client's result types.\n */\n output?: z.ZodTypeAny;\n /** Per tenant+IP, Redis-backed. Host defaults apply when omitted. */\n rateLimit?: { windowSec: number; max: number };\n /** POST routes that require an `Idempotency-Key` header. */\n idempotent?: boolean;\n}\n\n/** Options for callback-surface signature verification. */\nexport interface SignatureVerifyOptions {\n /** Header carrying the signature. Default `x-signature`. */\n header?: string;\n /** HMAC algorithm. Default `sha256`. */\n algorithm?: \"sha256\" | \"sha512\";\n /** Signature encoding in the header. Default `hex`. */\n encoding?: \"hex\" | \"base64\";\n}\n\n/**\n * What a route handler receives — already Zod-validated per the declaration,\n * with a sanitized header subset (no cookies, no authorization header).\n */\nexport interface PluginRequest {\n params: unknown;\n query: unknown;\n body: unknown;\n /** Player surface only. */\n player?: { id: string };\n /** Admin surface only. */\n actor?: Actor;\n headers: Readonly<Record<string, string>>;\n /** Set when the route declared `idempotent: true`. */\n idempotencyKey?: string;\n /**\n * Callback surface: verify the request signature against the named tenant\n * secret BEFORE trusting the body. Throws on mismatch. The dev-harness\n * doctor rejects callback handlers that never call this.\n */\n verifySignature(secretName: string, opts?: SignatureVerifyOptions): Promise<void>;\n}\n\nexport interface PluginResponse {\n /** Default 200. */\n status?: number;\n body?: unknown;\n headers?: Record<string, string>;\n}\n\nexport type PluginRouteHandler = (\n req: PluginRequest,\n ctx: PluginContext,\n) => Promise<PluginResponse>;\n\n/** Host defaults enforced around every handler invocation. */\nexport const PLUGIN_ROUTE_LIMITS = {\n /** Handler timeout (ms) — public/player/admin surfaces. */\n timeoutMs: 5_000,\n /** Handler timeout (ms) — callback surface (providers can be slow). */\n callbackTimeoutMs: 10_000,\n /** Serialized response body cap in bytes. */\n maxResponseBytes: 1_048_576,\n /** Default public-surface rate limit when the manifest omits one. */\n defaultRateLimit: { windowSec: 60, max: 60 },\n} as const;\n","import type { PluginContext } from \"./context.js\";\n\n/**\n * Async plugin tasks — long-running background work (install-time dataset\n * seeding, backfills) that must never run inside a lifecycle transaction.\n * Enqueued via `StartPluginTaskCommand` (or `ctx.tasks.start`), claimed and\n * executed by the TaskRunner in `apps/worker` with the full PluginContext.\n *\n * Handlers MUST be idempotent/resumable: a retry re-invokes the handler with\n * the last saved checkpoint, and `putMany` upserts make re-runs safe.\n */\nexport interface PluginTaskProgress {\n /** Persist progress (0–100) + message so the backoffice can poll it. */\n report(percent: number, message?: string): Promise<void>;\n /** Checkpoint saved by a previous (failed/interrupted) attempt, if any. */\n readonly checkpoint: Record<string, unknown> | null;\n /** Persist a resumability cursor; survives worker crashes and retries. */\n saveCheckpoint(checkpoint: Record<string, unknown>): Promise<void>;\n}\n\nexport type PluginTaskHandler = (\n ctx: PluginContext,\n input: Record<string, unknown>,\n progress: PluginTaskProgress,\n) => Promise<void>;\n\nexport const PLUGIN_TASK_LIMITS = {\n /** Task execution timeout (ms). */\n timeoutMs: 900_000,\n} as const;\n","/**\n * Outbound HTTP (`ctx.http`) — allowlisted, HTTPS-only, SSRF-guarded egress.\n * The host enforces `manifest.network.allowedHosts` (exact host or one-level\n * wildcard `*.example.com`), blocks private/link-local/metadata IP ranges\n * after DNS resolution, and rejects redirects that leave the allowlist.\n *\n * Secrets never transit plugin code: `secretHeaders` names a tenant secret\n * per header and the HOST resolves it at send time — logs and persisted\n * exchanges show `<redacted:name>`.\n */\nexport interface PluginFetchInit {\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\" | \"HEAD\";\n headers?: Record<string, string>;\n /** Objects are JSON-serialized with `content-type: application/json`. */\n body?: string | Record<string, unknown> | Array<unknown>;\n /** Default 10s. */\n timeoutMs?: number;\n /** header name → SECRET NAME, resolved host-side, never exposed. */\n secretHeaders?: Record<string, string>;\n /**\n * Persist the redacted request/response to `plugin_http_exchanges` (same\n * mechanism as provider raw I/O persistence) for support/debugging.\n */\n persistExchange?: boolean;\n}\n\n/** Materialized response — body already read, capped at 30 MB by the host. */\nexport interface PluginFetchResponse {\n status: number;\n ok: boolean;\n headers: Readonly<Record<string, string>>;\n bodyText: string;\n /** JSON.parse of bodyText, typed for convenience. Throws on non-JSON. */\n json<T = unknown>(): T;\n}\n\nexport const PLUGIN_HTTP_LIMITS = {\n defaultTimeoutMs: 10_000,\n maxResponseBytes: 31_457_280,\n /** Retries on idempotent methods (GET/HEAD) only. */\n maxRetries: 2,\n maxRedirects: 3,\n} as const;\n","import type { PluginContext } from \"./context.js\";\n\n/**\n * The Flows framework (PLAYER_ACCOUNT_BUILD_PROMPT.md §2) — the extensibility\n * backbone that lets a plugin participate in the core signup / KYC / deposit /\n * withdrawal flows WITHOUT a workflow engine. A Flow is a named sequence of\n * stages executed inside core services; each stage exposes plugin\n * participation in one of two modes:\n *\n * - `observe` — fire-and-isolate, AFTER commit, driven off the domain event\n * in the consumers runtime. Cannot veto or modify; failures never affect the\n * flow. The default, and all compliance-sensitive stages allow only this.\n * - `intercept` — runs INSIDE the flow, before commit, and may return a typed\n * patch (`modify` / `reject`). Wrapped in the same timeout/circuit machinery\n * as plugin routes; the fail policy is per stage (closed stages reject the\n * action when the hook errors, open stages proceed without it). An intercept\n * hook can never touch money, change tenant/player identity, or bypass a\n * core validator — the host re-validates every patch.\n */\n\nexport type FlowName = \"signup\" | \"kyc\" | \"deposit\" | \"withdrawal\";\nexport type FlowStageMode = \"observe\" | \"intercept\";\nexport type FlowFailPolicy = \"closed\" | \"open\";\n\nexport interface FlowHookDecl {\n flow: FlowName;\n /** Stage name from {@link FLOW_STAGE_CATALOG} — validated at publish time. */\n stage: FlowStageName;\n mode: FlowStageMode;\n /** Named handler in `definePlugin({ handlers: { flows } })`. */\n handler: string;\n /** Intercept budget. Default 2000, max 5000. */\n timeoutMs?: number;\n}\n\n/**\n * Handler signature: return a patch (intercept) or nothing (observe). The\n * `any` defaults keep specifically-typed handlers assignable to the\n * `handlers.flows` map (inputs are host-validated per stage regardless).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type FlowHookHandler<I = any, O = any> = (\n ctx: PluginContext,\n input: Readonly<I>,\n) => Promise<O | void> | O | void;\n\n/**\n * The initial stage catalog (§2.2) — extended via decision record ONLY. Each\n * entry pins which modes a stage admits and its intercept fail policy. An\n * undeclared stage fails publish; a stage used beyond its allowed mode too.\n */\nexport const FLOW_STAGE_CATALOG = {\n \"signup.validate\": { flow: \"signup\", modes: [\"intercept\"], failPolicy: \"closed\" },\n \"signup.completed\": { flow: \"signup\", modes: [\"observe\"], failPolicy: \"open\" },\n \"kyc.requirements.resolve\": { flow: \"kyc\", modes: [\"intercept\"], failPolicy: \"closed\" },\n \"kyc.document.submitted\": { flow: \"kyc\", modes: [\"observe\"], failPolicy: \"open\" },\n \"kyc.decision\": { flow: \"kyc\", modes: [\"observe\"], failPolicy: \"open\" },\n \"deposit.validate\": { flow: \"deposit\", modes: [\"intercept\"], failPolicy: \"closed\" },\n \"deposit.completed\": { flow: \"deposit\", modes: [\"observe\"], failPolicy: \"open\" },\n \"withdrawal.validate\": { flow: \"withdrawal\", modes: [\"intercept\"], failPolicy: \"closed\" },\n \"withdrawal.review\": { flow: \"withdrawal\", modes: [\"intercept\"], failPolicy: \"open\" },\n \"withdrawal.settled\": { flow: \"withdrawal\", modes: [\"observe\"], failPolicy: \"open\" },\n} as const satisfies Record<\n string,\n { flow: FlowName; modes: readonly FlowStageMode[]; failPolicy: FlowFailPolicy }\n>;\n\nexport type FlowStageName = keyof typeof FLOW_STAGE_CATALOG;\nexport const FLOW_STAGE_NAMES = Object.keys(FLOW_STAGE_CATALOG) as FlowStageName[];\n\nexport const FLOW_HOOK_DEFAULT_TIMEOUT_MS = 2_000;\nexport const FLOW_HOOK_MAX_TIMEOUT_MS = 5_000;\n\n// ── Intercept I/O shapes (host re-validates every patch with Zod) ────────────\n\n/** Generic intercept verdict: modify a whitelisted subset, or reject. */\nexport interface FlowInterceptPatch<M = Record<string, unknown>> {\n modify?: Partial<M>;\n reject?: { code: string; message: string };\n}\n\nexport interface SignupValidateInput {\n email?: string;\n username?: string;\n profile?: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n}\nexport type SignupValidateOutput = FlowInterceptPatch<{\n profile: Record<string, unknown>;\n metadata: Record<string, unknown>;\n}>;\n\nexport interface KycRequirementsResolveInput {\n playerId: string;\n trigger: string;\n baseRequirements: Array<{ documentTypeKey: string; required: boolean }>;\n}\n/** Add/remove requirement items — only known document-type keys are honored. */\nexport interface KycRequirementsResolveOutput {\n add?: string[];\n satisfied?: string[];\n}\n\nexport interface DepositValidateInput {\n playerId: string;\n amount: string;\n currency: string;\n providerKey?: string;\n methodKey?: string;\n metadata?: Record<string, unknown>;\n}\nexport type DepositValidateOutput = FlowInterceptPatch<{ metadata: Record<string, unknown> }>;\n\nexport interface WithdrawalValidateInput {\n playerId: string;\n amount: string;\n currency: string;\n methodKey?: string;\n metadata?: Record<string, unknown>;\n}\nexport type WithdrawalValidateOutput = FlowInterceptPatch<{ metadata: Record<string, unknown> }>;\n\nexport interface WithdrawalReviewInput {\n withdrawalId: string;\n playerId: string;\n amount: string;\n currency: string;\n riskContext?: { score?: number; flags?: string[] };\n}\n/** ADVISORY — the final verdict is always core/back-office (§2.2). */\nexport interface WithdrawalReviewOutput {\n reviewVerdict?: \"approve\" | \"hold\" | \"reject\";\n reason?: string;\n}\n\n/**\n * Tenant flow ownership (§2.3): `flows.<name>.mode` tenant setting. When a\n * flow is plugin-owned, the core's default player routes for it return\n * 409 FLOW_DELEGATED with `details.pluginKey`.\n */\nexport type FlowMode = \"core\" | `plugin:${string}`;\nexport const flowModeSettingKey = (flow: FlowName): string => `flows.${flow}.mode`;\n","/**\n * Canonical domain event names, grouped by aggregate.\n *\n * Names are the *contract*. They are dot-delimited `aggregate.action` strings;\n * the NATS subject is derived by prefixing `cwe.` (see `subjects.ts`). Never\n * inline an event-name string anywhere — always reference these constants so a\n * rename is a single, type-checked edit and consumers can't drift.\n */\nexport const WalletEvents = {\n CREDITED: \"wallet.credited\",\n DEBITED: \"wallet.debited\",\n TRANSFERRED: \"wallet.transferred\",\n DEPOSIT_COMPLETED: \"wallet.deposit_completed\",\n WITHDRAWAL_REQUESTED: \"wallet.withdrawal_requested\",\n WITHDRAWAL_APPROVED: \"wallet.withdrawal_approved\",\n WITHDRAWAL_REJECTED: \"wallet.withdrawal_rejected\",\n TRANSACTION_REVERSED: \"wallet.transaction_reversed\",\n WALLET_FROZEN: \"wallet.frozen\",\n WALLET_UNFROZEN: \"wallet.unfrozen\",\n} as const;\n\nexport const PlayerEvents = {\n CREATED: \"player.created\",\n LOGGED_IN: \"player.logged_in\",\n LOGGED_OUT: \"player.logged_out\",\n SESSION_REFRESHED: \"player.session_refreshed\",\n SOCIAL_LINKED: \"player.social_linked\",\n SOCIAL_UNLINKED: \"player.social_unlinked\",\n PASSWORD_CHANGED: \"player.password_changed\",\n LOGIN_FAILED: \"player.login_failed\",\n ACCOUNT_LOCKED: \"player.account_locked\",\n // Player account surface (docs/prompts/PLAYER_ACCOUNT_BUILD_PROMPT.md §13).\n // Profile/lifecycle facts — payloads carry ids + changed-field NAMES only,\n // never old/new PII values.\n UPDATED: \"player.updated\",\n /** KYC level-up: the player reached a higher verified level. */\n VERIFIED: \"player.verified\",\n CLOSED: \"player.closed\",\n /** Staff suspended the account (backoffice action; payload = ids + reason only). */\n SUSPENDED: \"player.suspended\",\n /** Staff lifted a suspension, returning the account to active. */\n REACTIVATED: \"player.reactivated\",\n EMAIL_VERIFIED: \"player.email_verified\",\n PHONE_VERIFIED: \"player.phone_verified\",\n SESSION_REVOKED: \"player.session_revoked\",\n PREFERENCES_UPDATED: \"player.preferences_updated\",\n // Responsible gaming (compliance-critical; observe-only for plugins).\n LIMIT_CHANGED: \"player.limit_changed\",\n COOL_OFF_STARTED: \"player.cool_off_started\",\n SELF_EXCLUDED: \"player.self_excluded\",\n /**\n * Reality-check tick (§8.2): produced by the worker sweep for active game\n * sessions, delivered to the frontend via the realtime `player` channel.\n * Transport fact only — carries session aggregates, never money truth.\n */\n REALITY_CHECK: \"player.reality_check\",\n // Presence transitions (docs/PROPOSED_DECISIONS.md §10). Emitted on\n // offline→online / online→offline flips ONLY — never per heartbeat.\n ONLINE: \"player.online\",\n OFFLINE: \"player.offline\",\n} as const;\n\n/**\n * Dynamic-KYC lifecycle events (PLAYER_ACCOUNT_BUILD_PROMPT.md §5). Payloads\n * carry ids, document-type KEYS and coded statuses/reasons only — never\n * document content, storage refs, file names or signed URLs.\n */\nexport const KycEvents = {\n CONFIG_UPDATED: \"kyc.config_updated\",\n REQUEST_CREATED: \"kyc.request_created\",\n DOCUMENT_UPLOADED: \"kyc.document_uploaded\",\n DOCUMENT_APPROVED: \"kyc.document_approved\",\n DOCUMENT_REJECTED: \"kyc.document_rejected\",\n REQUEST_SUBMITTED: \"kyc.request_submitted\",\n REQUEST_APPROVED: \"kyc.request_approved\",\n REQUEST_REJECTED: \"kyc.request_rejected\",\n REQUEST_NEEDS_MORE: \"kyc.request_needs_more\",\n // TASK-013 — BO escalation: a submitted request moved to the in_review lane.\n REQUEST_ESCALATED: \"kyc.request_escalated\",\n} as const;\n\nexport const BonusEvents = {\n GRANTED: \"bonus.granted\",\n REVOKED: \"bonus.revoked\",\n // Bonus platform lifecycle (docs/BONUS_PLATFORM.md §3.3).\n OFFERED: \"bonus.offered\",\n CLAIMED: \"bonus.claimed\",\n ACTIVATED: \"bonus.activated\",\n // Throttled: emitted on 10% progress milestones, not per settled round.\n WAGERING_PROGRESSED: \"bonus.wagering_progressed\",\n WAGERING_COMPLETED: \"bonus.wagering_completed\",\n CONVERTED: \"bonus.converted\",\n EXPIRED: \"bonus.expired\",\n FORFEITED: \"bonus.forfeited\",\n VOIDED: \"bonus.voided\",\n GRANT_QUEUED: \"bonus.grant_queued\",\n GRANT_REJECTED: \"bonus.grant_rejected\",\n CONSTRAINT_BREACHED: \"bonus.constraint_breached\",\n} as const;\n\n/**\n * Tournament lifecycle events. Emitted by promo plugins through the SDK's\n * typed `emitDomain` surface (BONUS_PLATFORM.md G7) so CRM/analytics can\n * consume them uniformly; never free-form `plugin.<key>.*` strings.\n */\nexport const TournamentEvents = {\n STARTED: \"tournament.started\",\n ENDED: \"tournament.ended\",\n PRIZE_AWARDED: \"tournament.prize_awarded\",\n} as const;\n\n/**\n * Gamification events (missions, achievements, levels) — plugin-emitted via\n * the typed SDK surface, same rationale as `TournamentEvents`.\n */\nexport const GamificationEvents = {\n ACHIEVEMENT_UNLOCKED: \"gamification.achievement_unlocked\",\n MISSION_COMPLETED: \"gamification.mission_completed\",\n LEVEL_UP: \"gamification.level_up\",\n} as const;\n\n/** Loyalty-points events — plugin-emitted via the typed SDK surface. */\nexport const LoyaltyEvents = {\n POINTS_EARNED: \"loyalty.points_earned\",\n POINTS_REDEEMED: \"loyalty.points_redeemed\",\n} as const;\n\nexport const BetEvents = {\n PLACED: \"bet.placed\",\n SETTLED: \"bet.settled\",\n} as const;\n\nexport const AffiliateEvents = {\n // Acquisition funnel (docs/AFFILIATE_ANALYTICS.md §C1): click → registration → FTD.\n // CLICK_RECORDED is the irreversible capture — click IDs (gclid/fbclid/…) exist\n // only if stored at click time.\n CLICK_RECORDED: \"affiliate.click_recorded\",\n REGISTRATION_ATTRIBUTED: \"affiliate.registration_attributed\",\n /** The one-time, permanent player→affiliate assignment (one per player, ever). */\n ASSIGNED: \"affiliate.assigned\",\n COMMISSION_CREATED: \"affiliate.commission_created\",\n COMMISSION_SETTLED: \"affiliate.commission_settled\",\n} as const;\n\n/**\n * Player classification & audience events (System family, like Plugin/Provider).\n * Emitted by the `@cwe/classification` engine and the audience/conversion\n * exporters — tags, segment membership, and the ad-platform feedback loop\n * (docs/AFFILIATE_ANALYTICS.md Parts G–H). Projection-side facts: they never\n * carry money truth, only classification state changes.\n */\nexport const ClassificationEvents = {\n PLAYER_TAG_ASSIGNED: \"classification.tag_assigned\",\n PLAYER_TAG_REMOVED: \"classification.tag_removed\",\n SEGMENT_MEMBERSHIP_CHANGED: \"classification.segment_membership_changed\",\n AUDIENCE_EXPORTED: \"classification.audience_exported\",\n CONVERSION_FEEDBACK_SENT: \"classification.conversion_feedback_sent\",\n} as const;\n\n/**\n * Catalog (game & provider catalog) events. Global/control-plane events\n * (source/provider/category/game definition changes) do NOT carry a tenant scope\n * in their payload; tenant-scoped events (provider toggle, game overlay) do.\n */\nexport const CatalogEvents = {\n SOURCE_REGISTERED: \"catalog.source_registered\",\n SOURCE_IMPORTED: \"catalog.source_imported\",\n PROVIDER_UPSERTED: \"catalog.provider_upserted\",\n CATEGORY_UPSERTED: \"catalog.category_upserted\",\n GAME_CREATED: \"catalog.game_created\",\n GAME_UPDATED: \"catalog.game_updated\",\n GAME_RETIRED: \"catalog.game_retired\",\n TENANT_PROVIDER_TOGGLED: \"catalog.tenant_provider_toggled\",\n TENANT_GAME_OVERLAID: \"catalog.tenant_game_overlaid\",\n GAMES_BULK_UPDATED: \"catalog.games_bulk_updated\",\n CACHE_INVALIDATED: \"catalog.cache_invalidated\",\n} as const;\n\n/**\n * Cashier (money-in / money-out) events — the payment lifecycle on top of the\n * wallet's financial events. The wallet still emits `wallet.*` for the actual\n * balance moves; these carry the PSP/payment facts the funnel + NGR waterfall\n * need (payment method, itemized fees, chargebacks, FX snapshot).\n */\nexport const CashierEvents = {\n DEPOSIT_INITIATED: \"cashier.deposit_initiated\",\n DEPOSIT_COMPLETED: \"cashier.deposit_completed\",\n DEPOSIT_FAILED: \"cashier.deposit_failed\",\n WITHDRAWAL_REQUESTED: \"cashier.withdrawal_requested\",\n WITHDRAWAL_APPROVED: \"cashier.withdrawal_approved\",\n WITHDRAWAL_REJECTED: \"cashier.withdrawal_rejected\",\n WITHDRAWAL_PAID: \"cashier.withdrawal_paid\",\n WITHDRAWAL_FAILED: \"cashier.withdrawal_failed\",\n /** Player/BO cancelled a not-yet-processing withdrawal; locked funds released. */\n WITHDRAWAL_CANCELLED: \"cashier.withdrawal_cancelled\",\n PAYMENT_FEE_RECORDED: \"cashier.payment_fee_recorded\",\n CHARGEBACK_RECORDED: \"cashier.chargeback_recorded\",\n PAYMENT_INSTRUMENT_ADDED: \"cashier.payment_instrument_added\",\n} as const;\n\n/**\n * Plugin-platform lifecycle events (System family). Emitted by the plugin host\n * commands (install/enable/configure/publish/…), never by plugin code itself —\n * plugin-emitted events are namespaced `plugin.<key>.*` and flow through the\n * outbox as raw subjects, not through this typed catalog.\n */\nexport const PluginEvents = {\n INSTALLED: \"plugin.installed\",\n ENABLED: \"plugin.enabled\",\n DISABLED: \"plugin.disabled\",\n UNINSTALLED: \"plugin.uninstalled\",\n CONFIGURED: \"plugin.configured\",\n PUBLISHED: \"plugin.published\",\n VERSION_YANKED: \"plugin.version_yanked\",\n // v2 developer surface: upgrades, async tasks, scheduled jobs, data retention.\n UPGRADED: \"plugin.upgraded\",\n TASK_STARTED: \"plugin.task_started\",\n TASK_COMPLETED: \"plugin.task_completed\",\n TASK_FAILED: \"plugin.task_failed\",\n JOB_FAILED: \"plugin.job_failed\",\n DATA_PURGED: \"plugin.data_purged\",\n} as const;\n\n/**\n * Provider lifecycle events (System family). `provider.enabled` fires when a\n * provider-kind plugin is enabled for a tenant and its adapter is registered.\n */\nexport const ProviderEvents = {\n ENABLED: \"provider.enabled\",\n DISABLED: \"provider.disabled\",\n} as const;\n\n/**\n * Backoffice-platform events (System family). Emitted by the `packages/backoffice`\n * services (view/dashboard save, export completion, PII reveal, translations).\n * Projection/operational facts only — never money truth. PII_REVEALED carries the\n * subject id + revealed field KEYS only, never the revealed values.\n */\nexport const BackofficeEvents = {\n VIEW_SAVED: \"backoffice.view_saved\",\n DASHBOARD_SAVED: \"backoffice.dashboard_saved\",\n EXPORT_COMPLETED: \"backoffice.export_completed\",\n PII_REVEALED: \"backoffice.pii_revealed\",\n TRANSLATIONS_UPDATED: \"backoffice.translations_updated\",\n /**\n * TASK-005: staff reassigned a player's CRM owner. Carries staff user ids\n * (before/after, either may be null for unassign) + reason. No player PII.\n */\n OWNER_ASSIGNED: \"backoffice.owner_assigned\",\n /**\n * TASK-007: staff logged a typed CRM activity on any entity record.\n * Payload carries ids + kind + ownerActorId + dueAt/completedAt only —\n * NO body text (PII exfiltration risk), NO authorActorId (retrievable\n * from the audit row).\n */\n ACTIVITY_CREATED: \"backoffice.activity_created\",\n /** TASK-007: staff marked an activity complete. Payload = ids + timestamps. */\n ACTIVITY_COMPLETED: \"backoffice.activity_completed\",\n} as const;\n\n/**\n * Platform/system events (System family). Emitted by worker jobs and settings\n * commands — cache-busting + config-change signals, never money truth.\n * `FX_RATES_UPDATED` lets conversion caches invalidate after a rate sync;\n * `TENANT_SETTINGS_UPDATED` fires on any tenant-settings write (e.g. a reporting\n * currency change) so read caches refresh (CURRENCY_SYNC_BUILD.md §3/§4.3).\n */\nexport const SystemEvents = {\n FX_RATES_UPDATED: \"system.fx_rates_updated\",\n TENANT_SETTINGS_UPDATED: \"system.tenant_settings_updated\",\n} as const;\n\n/**\n * Union of every known event name.\n *\n * NOTE: families share some constant KEYS (e.g. both Wallet and Cashier define\n * `DEPOSIT_COMPLETED`, with distinct *values* `wallet.*` vs `cashier.*`). A flat\n * spread would let a later family's value overwrite an earlier one's at that key,\n * silently dropping it from the type. So the runtime object is kept for\n * convenience, but the `DomainEventName` union is derived from each family's\n * VALUE type — guaranteeing every event value is in the union regardless of key\n * collisions.\n */\nexport const DomainEventNames = {\n ...WalletEvents,\n ...PlayerEvents,\n ...KycEvents,\n ...BonusEvents,\n ...BetEvents,\n ...AffiliateEvents,\n ...ClassificationEvents,\n ...CatalogEvents,\n ...CashierEvents,\n ...PluginEvents,\n ...ProviderEvents,\n ...TournamentEvents,\n ...GamificationEvents,\n ...LoyaltyEvents,\n ...BackofficeEvents,\n ...SystemEvents,\n} as const;\n\ntype EventValues<T> = T[keyof T];\nexport type DomainEventName =\n | EventValues<typeof WalletEvents>\n | EventValues<typeof PlayerEvents>\n | EventValues<typeof KycEvents>\n | EventValues<typeof BonusEvents>\n | EventValues<typeof BetEvents>\n | EventValues<typeof AffiliateEvents>\n | EventValues<typeof ClassificationEvents>\n | EventValues<typeof CatalogEvents>\n | EventValues<typeof CashierEvents>\n | EventValues<typeof PluginEvents>\n | EventValues<typeof ProviderEvents>\n | EventValues<typeof TournamentEvents>\n | EventValues<typeof GamificationEvents>\n | EventValues<typeof LoyaltyEvents>\n | EventValues<typeof BackofficeEvents>\n | EventValues<typeof SystemEvents>;\n","import {\n WalletEvents,\n PlayerEvents,\n KycEvents,\n BonusEvents,\n BetEvents,\n AffiliateEvents,\n ClassificationEvents,\n CatalogEvents,\n CashierEvents,\n PluginEvents,\n ProviderEvents,\n TournamentEvents,\n GamificationEvents,\n LoyaltyEvents,\n BackofficeEvents,\n SystemEvents,\n type DomainEventName,\n} from \"./names.js\";\n\n/**\n * Current schema version per event. Bump when the payload changes in a\n * backward-incompatible way; consumers can branch on `version` to support old\n * and new payloads during a migration window.\n */\nexport const DomainEventVersions: Record<DomainEventName, number> = {\n [WalletEvents.CREDITED]: 1,\n [WalletEvents.DEBITED]: 1,\n [WalletEvents.TRANSFERRED]: 1,\n [WalletEvents.DEPOSIT_COMPLETED]: 1,\n [WalletEvents.WITHDRAWAL_REQUESTED]: 1,\n [WalletEvents.WITHDRAWAL_APPROVED]: 1,\n [WalletEvents.WITHDRAWAL_REJECTED]: 1,\n [WalletEvents.TRANSACTION_REVERSED]: 2, // v2: + gameId\n [WalletEvents.WALLET_FROZEN]: 1,\n [WalletEvents.WALLET_UNFROZEN]: 1,\n [PlayerEvents.CREATED]: 1,\n [PlayerEvents.LOGGED_IN]: 1,\n [PlayerEvents.LOGGED_OUT]: 1,\n [PlayerEvents.SESSION_REFRESHED]: 1,\n [PlayerEvents.SOCIAL_LINKED]: 1,\n [PlayerEvents.SOCIAL_UNLINKED]: 1,\n [PlayerEvents.PASSWORD_CHANGED]: 1,\n [PlayerEvents.LOGIN_FAILED]: 1,\n [PlayerEvents.ACCOUNT_LOCKED]: 1,\n [PlayerEvents.UPDATED]: 1,\n [PlayerEvents.VERIFIED]: 1,\n [PlayerEvents.CLOSED]: 1,\n [PlayerEvents.SUSPENDED]: 1,\n [PlayerEvents.REACTIVATED]: 1,\n [PlayerEvents.EMAIL_VERIFIED]: 1,\n [PlayerEvents.PHONE_VERIFIED]: 1,\n [PlayerEvents.SESSION_REVOKED]: 1,\n [PlayerEvents.PREFERENCES_UPDATED]: 1,\n [PlayerEvents.LIMIT_CHANGED]: 1,\n [PlayerEvents.COOL_OFF_STARTED]: 1,\n [PlayerEvents.SELF_EXCLUDED]: 1,\n [PlayerEvents.REALITY_CHECK]: 1,\n [PlayerEvents.ONLINE]: 1,\n [PlayerEvents.OFFLINE]: 1,\n [KycEvents.CONFIG_UPDATED]: 1,\n [KycEvents.REQUEST_CREATED]: 1,\n [KycEvents.DOCUMENT_UPLOADED]: 1,\n [KycEvents.DOCUMENT_APPROVED]: 1,\n [KycEvents.DOCUMENT_REJECTED]: 1,\n [KycEvents.REQUEST_SUBMITTED]: 1,\n [KycEvents.REQUEST_APPROVED]: 1,\n [KycEvents.REQUEST_REJECTED]: 1,\n [KycEvents.REQUEST_NEEDS_MORE]: 1,\n [KycEvents.REQUEST_ESCALATED]: 1,\n [BonusEvents.GRANTED]: 1,\n [BonusEvents.REVOKED]: 1,\n [BonusEvents.OFFERED]: 1,\n [BonusEvents.CLAIMED]: 1,\n [BonusEvents.ACTIVATED]: 1,\n [BonusEvents.WAGERING_PROGRESSED]: 1,\n [BonusEvents.WAGERING_COMPLETED]: 1,\n [BonusEvents.CONVERTED]: 1,\n [BonusEvents.EXPIRED]: 1,\n [BonusEvents.FORFEITED]: 1,\n [BonusEvents.VOIDED]: 1,\n [BonusEvents.GRANT_QUEUED]: 1,\n [BonusEvents.GRANT_REJECTED]: 1,\n [BonusEvents.CONSTRAINT_BREACHED]: 1,\n [BetEvents.PLACED]: 2, // v2: + gameId\n [BetEvents.SETTLED]: 2, // v2: + gameId\n [AffiliateEvents.CLICK_RECORDED]: 1,\n [AffiliateEvents.REGISTRATION_ATTRIBUTED]: 1,\n [AffiliateEvents.ASSIGNED]: 1,\n [AffiliateEvents.COMMISSION_CREATED]: 2, // v2: ledger semantics (entryId, kind, before/after, FX)\n [AffiliateEvents.COMMISSION_SETTLED]: 1,\n [ClassificationEvents.PLAYER_TAG_ASSIGNED]: 1,\n [ClassificationEvents.PLAYER_TAG_REMOVED]: 1,\n [ClassificationEvents.SEGMENT_MEMBERSHIP_CHANGED]: 1,\n [ClassificationEvents.AUDIENCE_EXPORTED]: 1,\n [ClassificationEvents.CONVERSION_FEEDBACK_SENT]: 1,\n [CatalogEvents.SOURCE_REGISTERED]: 1,\n [CatalogEvents.SOURCE_IMPORTED]: 1,\n [CatalogEvents.PROVIDER_UPSERTED]: 1,\n [CatalogEvents.CATEGORY_UPSERTED]: 1,\n [CatalogEvents.GAME_CREATED]: 1,\n [CatalogEvents.GAME_UPDATED]: 1,\n [CatalogEvents.GAME_RETIRED]: 1,\n [CatalogEvents.TENANT_PROVIDER_TOGGLED]: 1,\n [CatalogEvents.TENANT_GAME_OVERLAID]: 1,\n [CatalogEvents.GAMES_BULK_UPDATED]: 1,\n [CatalogEvents.CACHE_INVALIDATED]: 1,\n [CashierEvents.DEPOSIT_INITIATED]: 1,\n [CashierEvents.DEPOSIT_COMPLETED]: 1,\n [CashierEvents.DEPOSIT_FAILED]: 1,\n [CashierEvents.WITHDRAWAL_REQUESTED]: 1,\n [CashierEvents.WITHDRAWAL_APPROVED]: 1,\n [CashierEvents.WITHDRAWAL_REJECTED]: 1,\n [CashierEvents.WITHDRAWAL_PAID]: 1,\n [CashierEvents.WITHDRAWAL_FAILED]: 1,\n [CashierEvents.WITHDRAWAL_CANCELLED]: 1,\n [CashierEvents.PAYMENT_FEE_RECORDED]: 1,\n [CashierEvents.CHARGEBACK_RECORDED]: 1,\n [CashierEvents.PAYMENT_INSTRUMENT_ADDED]: 1,\n [PluginEvents.INSTALLED]: 1,\n [PluginEvents.ENABLED]: 1,\n [PluginEvents.DISABLED]: 1,\n [PluginEvents.UNINSTALLED]: 1,\n [PluginEvents.CONFIGURED]: 1,\n [PluginEvents.PUBLISHED]: 1,\n [PluginEvents.VERSION_YANKED]: 1,\n [PluginEvents.UPGRADED]: 1,\n [PluginEvents.TASK_STARTED]: 1,\n [PluginEvents.TASK_COMPLETED]: 1,\n [PluginEvents.TASK_FAILED]: 1,\n [PluginEvents.JOB_FAILED]: 1,\n [PluginEvents.DATA_PURGED]: 1,\n [ProviderEvents.ENABLED]: 1,\n [ProviderEvents.DISABLED]: 1,\n [TournamentEvents.STARTED]: 1,\n [TournamentEvents.ENDED]: 1,\n [TournamentEvents.PRIZE_AWARDED]: 1,\n [GamificationEvents.ACHIEVEMENT_UNLOCKED]: 1,\n [GamificationEvents.MISSION_COMPLETED]: 1,\n [GamificationEvents.LEVEL_UP]: 1,\n [LoyaltyEvents.POINTS_EARNED]: 1,\n [LoyaltyEvents.POINTS_REDEEMED]: 1,\n [BackofficeEvents.VIEW_SAVED]: 1,\n [BackofficeEvents.DASHBOARD_SAVED]: 1,\n [BackofficeEvents.EXPORT_COMPLETED]: 1,\n [BackofficeEvents.PII_REVEALED]: 1,\n [BackofficeEvents.TRANSLATIONS_UPDATED]: 1,\n [BackofficeEvents.OWNER_ASSIGNED]: 1,\n [BackofficeEvents.ACTIVITY_CREATED]: 1,\n [BackofficeEvents.ACTIVITY_COMPLETED]: 1,\n [SystemEvents.FX_RATES_UPDATED]: 1,\n [SystemEvents.TENANT_SETTINGS_UPDATED]: 1,\n};\n\nexport function currentVersion(name: DomainEventName): number {\n return DomainEventVersions[name];\n}\n","import type { DomainEventName } from \"./names.js\";\n\n/**\n * NATS subject derivation. Every domain event maps to `cwe.<name>`, e.g.\n * `wallet.credited` → `cwe.wallet.credited`. A single JetStream stream captures\n * `cwe.>`, and consumers filter by subject.\n */\nexport const SUBJECT_ROOT = \"cwe\";\n\nexport function toSubject(name: DomainEventName | string): string {\n return `${SUBJECT_ROOT}.${name}`;\n}\n\n/** The JetStream stream that captures every `cwe.*` event. */\nexport const STREAM_NAME = \"CWE_EVENTS\";\nexport const STREAM_SUBJECT = `${SUBJECT_ROOT}.>`;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAkHO,IAAM,iBAAiB,CAAC,OAAO,QAAQ,QAAQ;AAI/C,IAAM,qBAAqB,CAAC,aAAa,WAAW,YAAY,SAAS;AAIzE,IAAM,wBAAwB,CAAC,SAAS,aAAa,QAAQ;;;ACtE7D,IAAM,eAAe;AAAA,EAC1B,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,iBAAiB;AACnB;AAKO,SAAS,kBAAkB,WAA2B;AAC3D,SAAO,UAAU,SAAS;AAC5B;;;ACrEA,SAAS,SAAS;AAuClB,SAAS,SACP,MACA,MACA,MACA,YACqB;AACrB,QAAM,WAAW,KAAK,YAAY;AAClC,MAAI,MAAM,KAAK,OAAO;AACtB,MAAI,CAAC,SAAU,OAAM,IAAI,SAAS;AAClC,MAAI,KAAK,YAAY,OAAW,OAAM,IAAI,QAAQ,KAAK,OAAO;AAC9D,SAAO;AAAA,IACL;AAAA,IACA,OAAO,KAAK;AAAA,IACZ,aAAa,KAAK;AAAA,IAClB;AAAA,IACA,QAAQ,KAAK;AAAA,IACb,SAAS,KAAK;AAAA,IACd;AAAA,IACA;AAAA,EACF;AACF;AAGO,IAAM,gBAAgB;AAAA,EAC3B,QAAQ,CAAC,SAAuB,SAAS,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,IAAI;AAAA,EAC1E,QAAQ,CAAC,SAAuB,SAAS,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;AAAA,EAC5E,SAAS,CAAC,SAAuB,SAAS,WAAW,EAAE,QAAQ,GAAG,IAAI;AAAA,EACtE,MAAM,CAAC,SACL,SAAS,QAAQ,EAAE,KAAK,KAAK,UAAU,GAAG,MAAM,KAAK,UAAU;AAAA,EACjE,MAAM,CAAC,SACL,SAAS,QAAQ,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI;AACjF;AAqBO,SAAS,wBACd,QACgC;AAChC,QAAM,MAAsC,CAAC;AAC7C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,MAAM,GAAG;AACxD,QAAI,GAAG,IAAI;AAAA,MACT,MAAM,MAAM;AAAA,MACZ,OAAO,MAAM;AAAA,MACb,GAAI,MAAM,gBAAgB,SAAY,EAAE,aAAa,MAAM,YAAY,IAAI,CAAC;AAAA,MAC5E,UAAU,MAAM;AAAA,MAChB,QAAQ,MAAM,WAAW;AAAA,MACzB,GAAI,MAAM,WAAW,QAAQ,MAAM,YAAY,SAAY,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,MACzF,GAAI,MAAM,eAAe,SAAY,EAAE,YAAY,MAAM,WAAW,IAAI,CAAC;AAAA,IAC3E;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,kBACd,QACA,SAAyC,WACE;AAC3C,QAAM,QAAsC,CAAC;AAC7C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,MAAM,GAAG;AACxD,UAAM,WAAW,MAAM,WAAW;AAClC,QAAI,WAAW,aAAa,SAAU;AACtC,QAAI,WAAW,UAAU,CAAC,SAAU;AAEpC,UAAM,GAAG,IAAI,WAAW,SAAS,MAAM,IAAI,SAAS,IAAI,MAAM;AAAA,EAChE;AACA,SAAO,EAAE,OAAO,KAAK,EAAE,OAAO;AAChC;AAGO,SAAS,sBAAsB,QAAuD;AAC3F,QAAM,SAAkC,CAAC;AACzC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,MAAM,GAAG;AACxD,QAAI,MAAM,WAAW,KAAM;AAC3B,QAAI,MAAM,YAAY,OAAW,QAAO,GAAG,IAAI,MAAM;AAAA,EACvD;AACA,SAAO;AACT;;;ACjIO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;;;ACuBO,SAAS,aAAa,YAAgD;AAC3E,SAAO;AACT;;;ACgDO,IAAM,sBAAsB;AAAA;AAAA,EAEjC,WAAW;AAAA;AAAA,EAEX,mBAAmB;AAAA;AAAA,EAEnB,kBAAkB;AAAA;AAAA,EAElB,kBAAkB,EAAE,WAAW,IAAI,KAAK,GAAG;AAC7C;;;ACxEO,IAAM,qBAAqB;AAAA;AAAA,EAEhC,WAAW;AACb;;;ACOO,IAAM,qBAAqB;AAAA,EAChC,kBAAkB;AAAA,EAClB,kBAAkB;AAAA;AAAA,EAElB,YAAY;AAAA,EACZ,cAAc;AAChB;;;ACSO,IAAM,qBAAqB;AAAA,EAChC,mBAAmB,EAAE,MAAM,UAAU,OAAO,CAAC,WAAW,GAAG,YAAY,SAAS;AAAA,EAChF,oBAAoB,EAAE,MAAM,UAAU,OAAO,CAAC,SAAS,GAAG,YAAY,OAAO;AAAA,EAC7E,4BAA4B,EAAE,MAAM,OAAO,OAAO,CAAC,WAAW,GAAG,YAAY,SAAS;AAAA,EACtF,0BAA0B,EAAE,MAAM,OAAO,OAAO,CAAC,SAAS,GAAG,YAAY,OAAO;AAAA,EAChF,gBAAgB,EAAE,MAAM,OAAO,OAAO,CAAC,SAAS,GAAG,YAAY,OAAO;AAAA,EACtE,oBAAoB,EAAE,MAAM,WAAW,OAAO,CAAC,WAAW,GAAG,YAAY,SAAS;AAAA,EAClF,qBAAqB,EAAE,MAAM,WAAW,OAAO,CAAC,SAAS,GAAG,YAAY,OAAO;AAAA,EAC/E,uBAAuB,EAAE,MAAM,cAAc,OAAO,CAAC,WAAW,GAAG,YAAY,SAAS;AAAA,EACxF,qBAAqB,EAAE,MAAM,cAAc,OAAO,CAAC,WAAW,GAAG,YAAY,OAAO;AAAA,EACpF,sBAAsB,EAAE,MAAM,cAAc,OAAO,CAAC,SAAS,GAAG,YAAY,OAAO;AACrF;AAMO,IAAM,mBAAmB,OAAO,KAAK,kBAAkB;AAEvD,IAAM,+BAA+B;AACrC,IAAM,2BAA2B;AAsEjC,IAAM,qBAAqB,CAAC,SAA2B,SAAS,IAAI;;;ACrIpE,IAAM,eAAe;AAAA,EAC1B,UAAU;AAAA,EACV,SAAS;AAAA,EACT,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,sBAAsB;AAAA,EACtB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,sBAAsB;AAAA,EACtB,eAAe;AAAA,EACf,iBAAiB;AACnB;AAEO,IAAM,eAAe;AAAA,EAC1B,SAAS;AAAA,EACT,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,mBAAmB;AAAA,EACnB,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,cAAc;AAAA,EACd,gBAAgB;AAAA;AAAA;AAAA;AAAA,EAIhB,SAAS;AAAA;AAAA,EAET,UAAU;AAAA,EACV,QAAQ;AAAA;AAAA,EAER,WAAW;AAAA;AAAA,EAEX,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,qBAAqB;AAAA;AAAA,EAErB,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMf,eAAe;AAAA;AAAA;AAAA,EAGf,QAAQ;AAAA,EACR,SAAS;AACX;AAOO,IAAM,YAAY;AAAA,EACvB,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,oBAAoB;AAAA;AAAA,EAEpB,mBAAmB;AACrB;AAEO,IAAM,cAAc;AAAA,EACzB,SAAS;AAAA,EACT,SAAS;AAAA;AAAA,EAET,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AAAA;AAAA,EAEX,qBAAqB;AAAA,EACrB,oBAAoB;AAAA,EACpB,WAAW;AAAA,EACX,SAAS;AAAA,EACT,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,qBAAqB;AACvB;AAOO,IAAM,mBAAmB;AAAA,EAC9B,SAAS;AAAA,EACT,OAAO;AAAA,EACP,eAAe;AACjB;AAMO,IAAM,qBAAqB;AAAA,EAChC,sBAAsB;AAAA,EACtB,mBAAmB;AAAA,EACnB,UAAU;AACZ;AAGO,IAAM,gBAAgB;AAAA,EAC3B,eAAe;AAAA,EACf,iBAAiB;AACnB;AAEO,IAAM,YAAY;AAAA,EACvB,QAAQ;AAAA,EACR,SAAS;AACX;AAEO,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA,EAI7B,gBAAgB;AAAA,EAChB,yBAAyB;AAAA;AAAA,EAEzB,UAAU;AAAA,EACV,oBAAoB;AAAA,EACpB,oBAAoB;AACtB;AASO,IAAM,uBAAuB;AAAA,EAClC,qBAAqB;AAAA,EACrB,oBAAoB;AAAA,EACpB,4BAA4B;AAAA,EAC5B,mBAAmB;AAAA,EACnB,0BAA0B;AAC5B;AAOO,IAAM,gBAAgB;AAAA,EAC3B,mBAAmB;AAAA,EACnB,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,cAAc;AAAA,EACd,cAAc;AAAA,EACd,yBAAyB;AAAA,EACzB,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,mBAAmB;AACrB;AAQO,IAAM,gBAAgB;AAAA,EAC3B,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,gBAAgB;AAAA,EAChB,sBAAsB;AAAA,EACtB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,iBAAiB;AAAA,EACjB,mBAAmB;AAAA;AAAA,EAEnB,sBAAsB;AAAA,EACtB,sBAAsB;AAAA,EACtB,qBAAqB;AAAA,EACrB,0BAA0B;AAC5B;AAQO,IAAM,eAAe;AAAA,EAC1B,WAAW;AAAA,EACX,SAAS;AAAA,EACT,UAAU;AAAA,EACV,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,gBAAgB;AAAA;AAAA,EAEhB,UAAU;AAAA,EACV,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,aAAa;AACf;AAMO,IAAM,iBAAiB;AAAA,EAC5B,SAAS;AAAA,EACT,UAAU;AACZ;AAQO,IAAM,mBAAmB;AAAA,EAC9B,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,cAAc;AAAA,EACd,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKtB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOhB,kBAAkB;AAAA;AAAA,EAElB,oBAAoB;AACtB;AASO,IAAM,eAAe;AAAA,EAC1B,kBAAkB;AAAA,EAClB,yBAAyB;AAC3B;AAaO,IAAM,mBAAmB;AAAA,EAC9B,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;;;ACnRO,IAAM,sBAAuD;AAAA,EAClE,CAAC,aAAa,QAAQ,GAAG;AAAA,EACzB,CAAC,aAAa,OAAO,GAAG;AAAA,EACxB,CAAC,aAAa,WAAW,GAAG;AAAA,EAC5B,CAAC,aAAa,iBAAiB,GAAG;AAAA,EAClC,CAAC,aAAa,oBAAoB,GAAG;AAAA,EACrC,CAAC,aAAa,mBAAmB,GAAG;AAAA,EACpC,CAAC,aAAa,mBAAmB,GAAG;AAAA,EACpC,CAAC,aAAa,oBAAoB,GAAG;AAAA;AAAA,EACrC,CAAC,aAAa,aAAa,GAAG;AAAA,EAC9B,CAAC,aAAa,eAAe,GAAG;AAAA,EAChC,CAAC,aAAa,OAAO,GAAG;AAAA,EACxB,CAAC,aAAa,SAAS,GAAG;AAAA,EAC1B,CAAC,aAAa,UAAU,GAAG;AAAA,EAC3B,CAAC,aAAa,iBAAiB,GAAG;AAAA,EAClC,CAAC,aAAa,aAAa,GAAG;AAAA,EAC9B,CAAC,aAAa,eAAe,GAAG;AAAA,EAChC,CAAC,aAAa,gBAAgB,GAAG;AAAA,EACjC,CAAC,aAAa,YAAY,GAAG;AAAA,EAC7B,CAAC,aAAa,cAAc,GAAG;AAAA,EAC/B,CAAC,aAAa,OAAO,GAAG;AAAA,EACxB,CAAC,aAAa,QAAQ,GAAG;AAAA,EACzB,CAAC,aAAa,MAAM,GAAG;AAAA,EACvB,CAAC,aAAa,SAAS,GAAG;AAAA,EAC1B,CAAC,aAAa,WAAW,GAAG;AAAA,EAC5B,CAAC,aAAa,cAAc,GAAG;AAAA,EAC/B,CAAC,aAAa,cAAc,GAAG;AAAA,EAC/B,CAAC,aAAa,eAAe,GAAG;AAAA,EAChC,CAAC,aAAa,mBAAmB,GAAG;AAAA,EACpC,CAAC,aAAa,aAAa,GAAG;AAAA,EAC9B,CAAC,aAAa,gBAAgB,GAAG;AAAA,EACjC,CAAC,aAAa,aAAa,GAAG;AAAA,EAC9B,CAAC,aAAa,aAAa,GAAG;AAAA,EAC9B,CAAC,aAAa,MAAM,GAAG;AAAA,EACvB,CAAC,aAAa,OAAO,GAAG;AAAA,EACxB,CAAC,UAAU,cAAc,GAAG;AAAA,EAC5B,CAAC,UAAU,eAAe,GAAG;AAAA,EAC7B,CAAC,UAAU,iBAAiB,GAAG;AAAA,EAC/B,CAAC,UAAU,iBAAiB,GAAG;AAAA,EAC/B,CAAC,UAAU,iBAAiB,GAAG;AAAA,EAC/B,CAAC,UAAU,iBAAiB,GAAG;AAAA,EAC/B,CAAC,UAAU,gBAAgB,GAAG;AAAA,EAC9B,CAAC,UAAU,gBAAgB,GAAG;AAAA,EAC9B,CAAC,UAAU,kBAAkB,GAAG;AAAA,EAChC,CAAC,UAAU,iBAAiB,GAAG;AAAA,EAC/B,CAAC,YAAY,OAAO,GAAG;AAAA,EACvB,CAAC,YAAY,OAAO,GAAG;AAAA,EACvB,CAAC,YAAY,OAAO,GAAG;AAAA,EACvB,CAAC,YAAY,OAAO,GAAG;AAAA,EACvB,CAAC,YAAY,SAAS,GAAG;AAAA,EACzB,CAAC,YAAY,mBAAmB,GAAG;AAAA,EACnC,CAAC,YAAY,kBAAkB,GAAG;AAAA,EAClC,CAAC,YAAY,SAAS,GAAG;AAAA,EACzB,CAAC,YAAY,OAAO,GAAG;AAAA,EACvB,CAAC,YAAY,SAAS,GAAG;AAAA,EACzB,CAAC,YAAY,MAAM,GAAG;AAAA,EACtB,CAAC,YAAY,YAAY,GAAG;AAAA,EAC5B,CAAC,YAAY,cAAc,GAAG;AAAA,EAC9B,CAAC,YAAY,mBAAmB,GAAG;AAAA,EACnC,CAAC,UAAU,MAAM,GAAG;AAAA;AAAA,EACpB,CAAC,UAAU,OAAO,GAAG;AAAA;AAAA,EACrB,CAAC,gBAAgB,cAAc,GAAG;AAAA,EAClC,CAAC,gBAAgB,uBAAuB,GAAG;AAAA,EAC3C,CAAC,gBAAgB,QAAQ,GAAG;AAAA,EAC5B,CAAC,gBAAgB,kBAAkB,GAAG;AAAA;AAAA,EACtC,CAAC,gBAAgB,kBAAkB,GAAG;AAAA,EACtC,CAAC,qBAAqB,mBAAmB,GAAG;AAAA,EAC5C,CAAC,qBAAqB,kBAAkB,GAAG;AAAA,EAC3C,CAAC,qBAAqB,0BAA0B,GAAG;AAAA,EACnD,CAAC,qBAAqB,iBAAiB,GAAG;AAAA,EAC1C,CAAC,qBAAqB,wBAAwB,GAAG;AAAA,EACjD,CAAC,cAAc,iBAAiB,GAAG;AAAA,EACnC,CAAC,cAAc,eAAe,GAAG;AAAA,EACjC,CAAC,cAAc,iBAAiB,GAAG;AAAA,EACnC,CAAC,cAAc,iBAAiB,GAAG;AAAA,EACnC,CAAC,cAAc,YAAY,GAAG;AAAA,EAC9B,CAAC,cAAc,YAAY,GAAG;AAAA,EAC9B,CAAC,cAAc,YAAY,GAAG;AAAA,EAC9B,CAAC,cAAc,uBAAuB,GAAG;AAAA,EACzC,CAAC,cAAc,oBAAoB,GAAG;AAAA,EACtC,CAAC,cAAc,kBAAkB,GAAG;AAAA,EACpC,CAAC,cAAc,iBAAiB,GAAG;AAAA,EACnC,CAAC,cAAc,iBAAiB,GAAG;AAAA,EACnC,CAAC,cAAc,iBAAiB,GAAG;AAAA,EACnC,CAAC,cAAc,cAAc,GAAG;AAAA,EAChC,CAAC,cAAc,oBAAoB,GAAG;AAAA,EACtC,CAAC,cAAc,mBAAmB,GAAG;AAAA,EACrC,CAAC,cAAc,mBAAmB,GAAG;AAAA,EACrC,CAAC,cAAc,eAAe,GAAG;AAAA,EACjC,CAAC,cAAc,iBAAiB,GAAG;AAAA,EACnC,CAAC,cAAc,oBAAoB,GAAG;AAAA,EACtC,CAAC,cAAc,oBAAoB,GAAG;AAAA,EACtC,CAAC,cAAc,mBAAmB,GAAG;AAAA,EACrC,CAAC,cAAc,wBAAwB,GAAG;AAAA,EAC1C,CAAC,aAAa,SAAS,GAAG;AAAA,EAC1B,CAAC,aAAa,OAAO,GAAG;AAAA,EACxB,CAAC,aAAa,QAAQ,GAAG;AAAA,EACzB,CAAC,aAAa,WAAW,GAAG;AAAA,EAC5B,CAAC,aAAa,UAAU,GAAG;AAAA,EAC3B,CAAC,aAAa,SAAS,GAAG;AAAA,EAC1B,CAAC,aAAa,cAAc,GAAG;AAAA,EAC/B,CAAC,aAAa,QAAQ,GAAG;AAAA,EACzB,CAAC,aAAa,YAAY,GAAG;AAAA,EAC7B,CAAC,aAAa,cAAc,GAAG;AAAA,EAC/B,CAAC,aAAa,WAAW,GAAG;AAAA,EAC5B,CAAC,aAAa,UAAU,GAAG;AAAA,EAC3B,CAAC,aAAa,WAAW,GAAG;AAAA,EAC5B,CAAC,eAAe,OAAO,GAAG;AAAA,EAC1B,CAAC,eAAe,QAAQ,GAAG;AAAA,EAC3B,CAAC,iBAAiB,OAAO,GAAG;AAAA,EAC5B,CAAC,iBAAiB,KAAK,GAAG;AAAA,EAC1B,CAAC,iBAAiB,aAAa,GAAG;AAAA,EAClC,CAAC,mBAAmB,oBAAoB,GAAG;AAAA,EAC3C,CAAC,mBAAmB,iBAAiB,GAAG;AAAA,EACxC,CAAC,mBAAmB,QAAQ,GAAG;AAAA,EAC/B,CAAC,cAAc,aAAa,GAAG;AAAA,EAC/B,CAAC,cAAc,eAAe,GAAG;AAAA,EACjC,CAAC,iBAAiB,UAAU,GAAG;AAAA,EAC/B,CAAC,iBAAiB,eAAe,GAAG;AAAA,EACpC,CAAC,iBAAiB,gBAAgB,GAAG;AAAA,EACrC,CAAC,iBAAiB,YAAY,GAAG;AAAA,EACjC,CAAC,iBAAiB,oBAAoB,GAAG;AAAA,EACzC,CAAC,iBAAiB,cAAc,GAAG;AAAA,EACnC,CAAC,iBAAiB,gBAAgB,GAAG;AAAA,EACrC,CAAC,iBAAiB,kBAAkB,GAAG;AAAA,EACvC,CAAC,aAAa,gBAAgB,GAAG;AAAA,EACjC,CAAC,aAAa,uBAAuB,GAAG;AAC1C;;;ACjJO,IAAM,eAAe;AAQrB,IAAM,iBAAiB,GAAG,YAAY;","names":[]}
@@ -0,0 +1,275 @@
1
+ import { NormalizedGame, NormalizedProvider, NormalizedCategory, NormalizedRestrictionGroup, NormalizedCurrencyGroup, PluginManifest, PluginDefinition, PluginHandlers, PluginRuntimeHooks, ReadModelName, Actor, PluginContext, PluginEmittedEvent, PluginCatalogSourceAdapter, CatalogImportSummary, PluginRouteDecl, PluginResponse } from './index.js';
2
+ export { ActionValidationIssues, GenerateClientOptions, GeneratedClientFiles, PluginActionSchema, PluginActionSchemas, deriveActionSchemas, generateClientPackage, jsonSchemaToTsType, pascalCase, validateActionDecls } from './index.js';
3
+ import 'zod';
4
+
5
+ /**
6
+ * In-memory `ctx.catalog` for `createTestContext` — behavioral parity with
7
+ * the host CatalogBridge is the target (asserted by the shared conformance
8
+ * suite): own-source store only, batch cap, unknown-provider auto-stub,
9
+ * unknown group/category reference → ValidationError naming the externalId,
10
+ * deactivate-only (`setGameStatus(false)` retires, never deletes).
11
+ */
12
+ interface MockCatalogGameRow {
13
+ game: NormalizedGame;
14
+ status: "active" | "retired";
15
+ }
16
+ interface MockCatalogStores {
17
+ providers: Map<string, NormalizedProvider>;
18
+ categories: Map<string, NormalizedCategory>;
19
+ restrictionGroups: Map<string, NormalizedRestrictionGroup>;
20
+ currencyGroups: Map<string, NormalizedCurrencyGroup>;
21
+ games: Map<string, MockCatalogGameRow>;
22
+ }
23
+ declare function catalogGrantedFor(manifest: PluginManifest): boolean;
24
+
25
+ /**
26
+ * `createTestContext` — a fully working in-memory PluginContext plus a
27
+ * harness that records and controls everything. Behavioral parity with the
28
+ * real host is a test target: anything the host rejects (permission miss,
29
+ * quota, batch cap, non-allowlisted host, event namespace), this context
30
+ * rejects with the same error `name`/`code`. The platform repo runs a shared
31
+ * conformance suite against both so the mock cannot silently diverge.
32
+ */
33
+ interface TestHttpRequest {
34
+ url: string;
35
+ method: string;
36
+ headers: Record<string, string>;
37
+ body?: string;
38
+ }
39
+ interface TestHttpResponse {
40
+ status: number;
41
+ json?: unknown;
42
+ text?: string;
43
+ headers?: Record<string, string>;
44
+ }
45
+ /** Response shape returned by a host-keyed `options.http` mock (§10). */
46
+ interface TestHttpHostResponse {
47
+ status: number;
48
+ /** A string passes through verbatim as the body text; anything else is JSON-serialized. */
49
+ body?: unknown;
50
+ headers?: Record<string, string>;
51
+ }
52
+ /** A host-keyed outbound-HTTP mock: receives the (secret-resolved) request. */
53
+ type TestHttpHostMock = (req: TestHttpRequest) => TestHttpHostResponse | Promise<TestHttpHostResponse>;
54
+ interface CreateTestContextOptions {
55
+ /** Full definition (preferred) — enables runHook/invokeRoute/runJob/… */
56
+ definition?: PluginDefinition;
57
+ /** Or a bare manifest (permissions are ENFORCED in tests too). */
58
+ manifest?: PluginManifest;
59
+ handlers?: PluginHandlers;
60
+ hooks?: PluginRuntimeHooks;
61
+ tenantId?: string;
62
+ region?: string;
63
+ settings?: Record<string, unknown>;
64
+ secrets?: Record<string, string>;
65
+ /** Stubbed command results by command name (value, factory, or Error). */
66
+ commandResults?: Record<string, unknown | Error | ((input: unknown) => unknown | Promise<unknown>)>;
67
+ /** `ctx.http` interception (after the allowlist check). */
68
+ httpMock?: (req: TestHttpRequest) => TestHttpResponse | Promise<TestHttpResponse>;
69
+ /** Read-model fixtures by model name (value or factory by params). */
70
+ readModels?: Partial<Record<ReadModelName, unknown | ((params: unknown) => unknown)>>;
71
+ /** Per-dataset record quota override (simulate quota exhaustion). */
72
+ datasetQuota?: number;
73
+ /** Foreign shared datasets available to this plugin: "owner.dataset" → rows. */
74
+ sharedDatasets?: Record<string, Array<{
75
+ key: string;
76
+ value: unknown;
77
+ }>>;
78
+ /**
79
+ * Seed own-dataset fixtures at construction — the same arrange-only path
80
+ * as `harness.seedDataset` (fixtures are trusted; the schema is not run).
81
+ */
82
+ datasets?: Record<string, Array<{
83
+ key: string;
84
+ value: unknown;
85
+ }>>;
86
+ /**
87
+ * Host-keyed outbound-HTTP mocks, e.g. `{ "api.sloterv.com": (req) => … }`.
88
+ * A matching host wins over `httpMock` (which stays the catch-all beneath
89
+ * it). The HTTPS + allowlist checks still run FIRST — a non-allowlisted
90
+ * host throws the same egress error even when a mock exists for it.
91
+ */
92
+ http?: Record<string, TestHttpHostMock>;
93
+ /** Per-dataset record quotas; a dataset named here wins over `datasetQuota`. */
94
+ quotas?: Record<string, number>;
95
+ }
96
+ interface RecordedCommand {
97
+ name: string;
98
+ input: unknown;
99
+ idempotencyKey?: string;
100
+ }
101
+ interface RecordedHttpExchange {
102
+ url: string;
103
+ method: string;
104
+ /** Secret headers appear as `<redacted:name>` — never the value. */
105
+ headers: Record<string, string>;
106
+ status: number;
107
+ }
108
+ interface RecordedLog {
109
+ level: string;
110
+ message: string;
111
+ extra: Record<string, unknown>;
112
+ }
113
+ interface InvokeRouteRequest {
114
+ params?: Record<string, string>;
115
+ query?: unknown;
116
+ body?: unknown;
117
+ player?: {
118
+ id: string;
119
+ };
120
+ actor?: Actor;
121
+ headers?: Record<string, string>;
122
+ idempotencyKey?: string;
123
+ /**
124
+ * Callback surface: either provide `rawBody` + a real signature header, or
125
+ * set `trustSignature: true` to make verifySignature succeed in the test.
126
+ */
127
+ rawBody?: string;
128
+ trustSignature?: boolean;
129
+ }
130
+ interface StoredRecord {
131
+ key: string;
132
+ value: Record<string, unknown>;
133
+ }
134
+ /** Options accepted alongside a definition in the two-argument call shape. */
135
+ type TestContextOptions = Omit<CreateTestContextOptions, "definition" | "manifest" | "handlers" | "hooks">;
136
+ /**
137
+ * Two call shapes, one return type:
138
+ *
139
+ * createTestContext(plugin, { settings, … }) // §10 definition-first
140
+ * createTestContext({ definition, settings, … }) // original options object
141
+ *
142
+ * Both return the same enriched object: `{ ctx, harness }` exactly as before,
143
+ * plus top-level aliases of the harness runners and a `recorder` view.
144
+ *
145
+ * The options overload is deliberately LAST: `Parameters<typeof
146
+ * createTestContext>[0]` resolves against the final overload, and existing
147
+ * consumers (the scaffold template among them) rely on it being the options
148
+ * object.
149
+ */
150
+ declare function createTestContext(definition: PluginDefinition, options?: TestContextOptions): TestContext;
151
+ declare function createTestContext(options: CreateTestContextOptions): TestContext;
152
+ declare function buildTestContext(options: CreateTestContextOptions): {
153
+ ctx: PluginContext;
154
+ harness: {
155
+ ctx: PluginContext;
156
+ commands: RecordedCommand[];
157
+ events: PluginEmittedEvent[];
158
+ httpExchanges: RecordedHttpExchange[];
159
+ logs: RecordedLog[];
160
+ tasksStarted: {
161
+ type: string;
162
+ input?: Record<string, unknown>;
163
+ }[];
164
+ datasetStores: Map<string, Map<string, StoredRecord>>;
165
+ /** In-memory catalog stores (undefined unless `manifest.catalog` granted). */
166
+ catalogStores: MockCatalogStores | undefined;
167
+ providerAdapter: unknown;
168
+ /** Captured by `ctx.registerCatalogSource` during `runSetup()`. */
169
+ catalogSourceAdapter: PluginCatalogSourceAdapter | undefined;
170
+ /**
171
+ * Run the registered catalog source adapter the way the host's reserved
172
+ * `catalog:import` task does: `ingest(ctx)` → apply as a full snapshot
173
+ * (upsert every family, RETIRE games absent from the snapshot) → summary.
174
+ */
175
+ runImport(): Promise<CatalogImportSummary>;
176
+ /** Deliver a domain event to handlers registered via `ctx.events.on`. */
177
+ dispatch(event: {
178
+ name: string;
179
+ payload?: unknown;
180
+ } & Record<string, unknown>): Promise<void>;
181
+ runHook<K extends keyof PluginRuntimeHooks>(name: K, ...args: unknown[]): Promise<unknown>;
182
+ runSetup(): Promise<void>;
183
+ /** Zod-validate inputs per the declaration, enforce surface auth, invoke. */
184
+ invokeRoute(ref: string | PluginRouteDecl, request?: InvokeRouteRequest): Promise<PluginResponse>;
185
+ runJob(name: string): Promise<void>;
186
+ runTask(type: string, input?: Record<string, unknown>, checkpoint?: Record<string, unknown> | null): Promise<{
187
+ progress: number;
188
+ message?: string;
189
+ checkpoint: Record<string, unknown> | null;
190
+ }>;
191
+ /** Run declared migrations with semver `toVersion` (in-memory datasets). */
192
+ runMigration(toVersion: string): Promise<{
193
+ processed: number;
194
+ }>;
195
+ /** Seed a dataset store directly (bypasses schema — arrange step only). */
196
+ seedDataset(dataset: string, records: Array<{
197
+ key: string;
198
+ value: Record<string, unknown>;
199
+ }>): void;
200
+ setSecret(key: string, value: string | undefined): void;
201
+ setSettings(next: Record<string, unknown>): void;
202
+ };
203
+ recorder: {
204
+ commands: RecordedCommand[];
205
+ events: PluginEmittedEvent[];
206
+ http: RecordedHttpExchange[];
207
+ logs: RecordedLog[];
208
+ tasksStarted: {
209
+ type: string;
210
+ input?: Record<string, unknown>;
211
+ }[];
212
+ };
213
+ runHook: <K extends keyof PluginRuntimeHooks>(name: K, ...args: unknown[]) => Promise<unknown>;
214
+ runJob: (name: string) => Promise<void>;
215
+ runTask: (type: string, input?: Record<string, unknown>, checkpoint?: Record<string, unknown> | null) => Promise<{
216
+ progress: number;
217
+ message?: string;
218
+ checkpoint: Record<string, unknown> | null;
219
+ }>;
220
+ runMigration: (toVersion: string) => Promise<{
221
+ processed: number;
222
+ }>;
223
+ invokeRoute: (ref: string | PluginRouteDecl, request?: InvokeRouteRequest) => Promise<PluginResponse>;
224
+ dispatch: (event: {
225
+ name: string;
226
+ payload?: unknown;
227
+ } & Record<string, unknown>) => Promise<void>;
228
+ runSetup: () => Promise<void>;
229
+ seedDataset: (dataset: string, records: Array<{
230
+ key: string;
231
+ value: Record<string, unknown>;
232
+ }>) => void;
233
+ setSettings: (next: Record<string, unknown>) => void;
234
+ setSecret: (key: string, value: string | undefined) => void;
235
+ };
236
+ /** The enriched object BOTH `createTestContext` call shapes return. */
237
+ type TestContext = ReturnType<typeof buildTestContext>;
238
+ type TestHarness = TestContext["harness"];
239
+ type TestRecorder = TestContext["recorder"];
240
+
241
+ /**
242
+ * "Plugin doctor lite" — the static checks a plugin author can run without a
243
+ * runtime: handler references, callback signature usage, dataset keyField /
244
+ * index declarations, cron/semver syntax, surface routes. The full doctor
245
+ * (`POST /dev/plugins/:key/validate` on a dev runtime) additionally compares
246
+ * against the published catalog; run it via `cwe-plugin validate`.
247
+ */
248
+ interface ManifestValidationResult {
249
+ ok: boolean;
250
+ errors: string[];
251
+ warnings: string[];
252
+ }
253
+ /** 5-field cron, minute resolution — syntax-level check (host parses fully). */
254
+ declare function isValidCronSyntax(expression: string): boolean;
255
+ declare function validateManifest(definition: PluginDefinition): ManifestValidationResult;
256
+
257
+ /**
258
+ * Test-context errors. The SDK is contract-only — it cannot import the
259
+ * platform's runtime error classes — so the mock throws errors whose `name`,
260
+ * `code` and `statusCode` match the host's exactly. The conformance suite in
261
+ * the platform repo asserts this parity (same assertions run against the mock
262
+ * and the real capability layer).
263
+ */
264
+ declare class TestContextError extends Error {
265
+ readonly statusCode: number;
266
+ readonly code: string;
267
+ readonly details?: unknown;
268
+ constructor(name: string, message: string, opts: {
269
+ statusCode: number;
270
+ code: string;
271
+ details?: unknown;
272
+ });
273
+ }
274
+
275
+ export { type CreateTestContextOptions, type InvokeRouteRequest, type ManifestValidationResult, type MockCatalogGameRow, type MockCatalogStores, type RecordedCommand, type RecordedHttpExchange, type RecordedLog, type TestContext, TestContextError, type TestContextOptions, type TestHarness, type TestHttpHostMock, type TestHttpHostResponse, type TestHttpRequest, type TestHttpResponse, type TestRecorder, catalogGrantedFor, createTestContext, isValidCronSyntax, validateManifest };