From 7032cbf7acd4c4e56e4a74330727640ac5330e06 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 1 Sep 2026 05:18:14 +0000 Subject: [PATCH 1/2] feat(spec): remove dangling postgres/nats values from ClusterDriverSchema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both values validated in ClusterDriverSchema while no package implemented them — the only non-test registerClusterDriver() caller is service-cluster-redis — so a schema-valid driver value reached defineCluster()'s unconditional "not registered" throw at runtime. Removes the two enum values, corrects the now-false postgres-only prose around useExistingPool/url (the ledgered field itself stays), replaces the postgres doc examples with redis, registers the semantic migration entry under protocol major 18, and pins the shipped roster plus the by-name rejection of the two removed spellings. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01Mciyv38maJ6HYVMiaM26T1 --- .../cluster-driver-dangling-values-removed.md | 49 +++++++++++++++++++ content/docs/kernel/cluster.mdx | 24 +++++---- content/docs/references/kernel/cluster.mdx | 11 ++--- .../services/service-cluster/src/cluster.ts | 10 ++-- .../service-cluster/src/memory/counter.ts | 2 +- .../service-cluster/src/memory/pubsub.ts | 4 +- .../services/service-cluster/src/testing.ts | 3 +- .../spec/src/contracts/cluster-service.ts | 2 +- packages/spec/src/kernel/cluster.test.ts | 30 ++++++++++-- packages/spec/src/kernel/cluster.zod.ts | 36 ++++++++------ ....cluster-driver-dangling-values-removed.ts | 42 ++++++++++++++++ packages/spec/src/migrations/registry.ts | 38 ++++++++++++++ 12 files changed, 208 insertions(+), 43 deletions(-) create mode 100644 .changeset/cluster-driver-dangling-values-removed.md create mode 100644 packages/spec/src/migrations/entries/semantic/18.cluster-driver-dangling-values-removed.ts diff --git a/.changeset/cluster-driver-dangling-values-removed.md b/.changeset/cluster-driver-dangling-values-removed.md new file mode 100644 index 0000000000..e989d83855 --- /dev/null +++ b/.changeset/cluster-driver-dangling-values-removed.md @@ -0,0 +1,49 @@ +--- +"@objectstack/spec": minor +"@objectstack/service-cluster": patch +--- + +feat(spec): remove the dangling `postgres` and `nats` values from `ClusterDriverSchema` (#13393) + + + +**BREAKING** accept-set narrowing on `ClusterDriverSchema` +(`kernel/cluster.zod.ts`), shipped as `minor` under the repo's launch-window +convention for breaking changes; the migration prescription is registered +under protocol major 18. + +`postgres` and `nats` validated in `ClusterDriverSchema` but no package +implemented either — the only non-test `registerClusterDriver()` caller is +`@objectstack/service-cluster-redis` — so `defineCluster({ driver: 'postgres' })` +(or `'nats'`) passed schema validation and then reached the unconditional +`Cluster driver "" is not registered` throw at runtime. Maintainer +ruling on objectstack-ai/cloud#1626 (2026-08-24, option B adopted): the +DB-first postgres driver is not built absent concrete customer pull, and — +the ruling's principle rider — a schema-valid value must not be an +unconditional runtime throw. The honest schema states the accept set the +runtime serves. + +FROM → TO: + +- `cluster: { driver: 'postgres' }` → `cluster: { driver: 'redis', url }` + (`@objectstack/service-cluster-redis`, the production recommendation), or + `cluster: { driver: 'custom' }` + `registerClusterDriver(name, factory)` + for a self-provided transport. Same mapping for `'nats'`. One-line fix: + pick a driver that ships. No stored config breaks at rest — a config + naming either value never survived boot in the first place. +- `ClusterDriver` (the `z.input` type) no longer includes the two spellings; + TypeScript call sites typing them fail `tsc` on upgrade with the same + remedy. +- The `useExistingPool` field **stays** (it is a ledgered authorable field); + only its postgres-only prose was corrected — it is forwarded verbatim to + the registered driver factory and is meaningful for database-backed + `custom` drivers. + +If a future ruling flips under the recorded reversal condition (a concrete +multi-node customer/contract), a value returns to the enum in the same +release that ships its implementation. + +`@objectstack/service-cluster` patch: doc comments no longer instruct the +removed spellings (`defineCluster({ driver: 'postgres' })` → +`{ driver: 'redis' }` in the `registerClusterDriver()` example); no runtime +behaviour change. diff --git a/content/docs/kernel/cluster.mdx b/content/docs/kernel/cluster.mdx index a491e23ec7..d4899a7b1a 100644 --- a/content/docs/kernel/cluster.mdx +++ b/content/docs/kernel/cluster.mdx @@ -595,14 +595,17 @@ operator declares a multi-node topology via `OS_EXPECT_MULTI_NODE=true` or |--------------|-----------------------|----------------------------|---------------------|----------------|-------------| | `memory` | in-process fan-out | per-key FIFO queue + TTL | Map | Map of bigints | ✅ `@objectstack/service-cluster` | | `redis` | `PUBLISH`/`SUBSCRIBE` (at-most-once) | `SET … NX PX` + Lua release/renew | `WATCH`/`MULTI` | `INCRBY` | ✅ `@objectstack/service-cluster-redis` | -| `postgres` | `LISTEN/NOTIFY` | advisory locks | dedicated KV table | sequence | ❌ not built | -| `nats` | NATS subjects + JetStream | KV bucket lock | KV bucket | KV INCR | ❌ not built | -Only `memory` and `redis` are implemented. `postgres` and `nats` are accepted -by `ClusterDriverSchema` but no package provides them — `defineCluster()` -throws `Cluster driver "" is not registered` for either. The `custom` -driver value resolves whatever a plugin registered via -`registerClusterDriver(name, factory)`. +`ClusterDriverSchema` accepts exactly the drivers that ship — `memory` and +`redis` — plus `custom`, which resolves whatever a plugin registered via +`registerClusterDriver(name, factory)`. It used to also accept `postgres` and +`nats` with no package behind either, so a schema-valid config reached +`defineCluster()`'s unconditional `Cluster driver "" is not registered` +throw; both values were removed from the enum (maintainer ruling on cloud#1626, +2026-08-24 — a schema-valid value must not be an unconditional runtime throw). +Their design sketches — `postgres` as `LISTEN/NOTIFY` + advisory locks + a KV +table + a sequence, `nats` as subjects + JetStream KV — stay recorded in +Phase 5 below for whoever builds one. The `redis` driver is the recommended starting point for production ObjectStack deployments: it accepts a pre-built ioredis client via @@ -704,8 +707,11 @@ of the declarative wiring below exists yet. Postgres driver downgraded to community/optional — useful for the "one binary, one container" deployment archetype that wants to skip Redis. NATS deferred until a customer reports throughput needs that -exceed Redis PUBSUB. No protocol changes required at that point — only -a new implementation that calls `registerClusterDriver()`. +exceed Redis PUBSUB. A community driver ships under `driver: 'custom'` +via `registerClusterDriver()` with no protocol change; a first-party +driver restores its enum value in the same release that ships the +implementation (the cloud#1626 ruling records that reversal condition — +the value returns only with an implementation behind it). ## 11. Non-goals (v1) diff --git a/content/docs/references/kernel/cluster.mdx b/content/docs/references/kernel/cluster.mdx index 2e42592e54..3a2c13cdb2 100644 --- a/content/docs/references/kernel/cluster.mdx +++ b/content/docs/references/kernel/cluster.mdx @@ -9,8 +9,9 @@ description: Cluster protocol schemas Defines the runtime semantics required for ObjectStack to behave correctly when more than one Node.js process is involved. The protocol layer codifies -**intent** (scope, delivery, leadership); concrete implementations -(`memory`, `redis`, `postgres`, `nats`) live in `@objectstack/service-cluster`. +**intent** (scope, delivery, leadership); concrete implementations live in +`@objectstack/service-cluster` (`memory`) and +`@objectstack/service-cluster-redis` (`redis`). The full design rationale is in `content/docs/kernel/cluster.mdx`. Read it before changing @@ -41,9 +42,9 @@ Cluster capability configuration for the stack. | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **driver** | `Enum<'memory' \| 'redis' \| 'postgres' \| 'nats' \| 'custom'>` | optional (default: `"memory"`) | Cluster transport driver. Defaults to in-memory single-process. | +| **driver** | `Enum<'memory' \| 'redis' \| 'custom'>` | optional (default: `"memory"`) | Cluster transport driver. Defaults to in-memory single-process. | | **url** | `string` | optional | Driver-specific connection URL. | -| **useExistingPool** | `boolean` | optional (default: `true`) | Reuse the main DB pool for the postgres driver. | +| **useExistingPool** | `boolean` | optional (default: `true`) | Reuse the main DB pool for database-backed custom drivers. | | **nodeId** | `string` | optional | Stable node identifier. Auto-generated when absent. | | **heartbeatMs** | `integer` | optional (default: `5000`) | Leader-election heartbeat interval in milliseconds. | | **lockTtlMs** | `integer` | optional (default: `15000`) | Leader-election lock TTL in milliseconds (≥ 3× heartbeatMs). | @@ -61,8 +62,6 @@ Cluster transport driver. * `memory` * `redis` -* `postgres` -* `nats` * `custom` diff --git a/packages/services/service-cluster/src/cluster.ts b/packages/services/service-cluster/src/cluster.ts index 757545b5b1..36f5ec0dfd 100644 --- a/packages/services/service-cluster/src/cluster.ts +++ b/packages/services/service-cluster/src/cluster.ts @@ -45,8 +45,8 @@ export class ComposedClusterService implements IClusterService { /** * Build an `IClusterService` from a `ClusterCapabilityConfig`. The only - * driver shipped from this package is `memory`; other drivers (postgres, - * redis, nats) live in dedicated packages and register themselves via + * driver shipped from this package is `memory`; other drivers (e.g. + * `redis`) live in dedicated packages and register themselves via * `registerClusterDriver()`. * * @example @@ -83,7 +83,7 @@ export function defineCluster( } // --------------------------------------------------------------------------- -// Driver registry (for postgres/redis/nats/custom drivers) +// Driver registry (for redis/custom drivers) // --------------------------------------------------------------------------- export interface DriverFactoryConfig { @@ -103,8 +103,8 @@ const driverRegistry = new Map(); /** * Register a custom cluster driver. Driver packages (e.g. - * `@objectstack/service-cluster-postgres`) should call this at module - * load time so `defineCluster({ driver: 'postgres' })` resolves them. + * `@objectstack/service-cluster-redis`) should call this at module + * load time so `defineCluster({ driver: 'redis' })` resolves them. */ export function registerClusterDriver( name: string, diff --git a/packages/services/service-cluster/src/memory/counter.ts b/packages/services/service-cluster/src/memory/counter.ts index 0dd9c3735c..4635450516 100644 --- a/packages/services/service-cluster/src/memory/counter.ts +++ b/packages/services/service-cluster/src/memory/counter.ts @@ -4,7 +4,7 @@ import type { ICounter, CounterIncrOptions } from '@objectstack/spec/contracts'; /** * In-memory monotonic counter. Single-process only — for cross-node id - * allocation, use the postgres or redis driver. + * allocation, use the redis driver. */ export class MemoryCounter implements ICounter { private readonly counters = new Map(); diff --git a/packages/services/service-cluster/src/memory/pubsub.ts b/packages/services/service-cluster/src/memory/pubsub.ts index 2449ca6bad..cb5e04aeea 100644 --- a/packages/services/service-cluster/src/memory/pubsub.ts +++ b/packages/services/service-cluster/src/memory/pubsub.ts @@ -22,8 +22,8 @@ import type { * to at that moment is a silent no-op. This matches what `IPubSub` * documents: no shipped driver exceeds at-most-once, so handlers must be * idempotent **and** tolerate loss. - * - No cross-process delivery — use the redis/postgres/nats driver for - * real multi-node setups. + * - No cross-process delivery — use the redis driver (or a registered + * custom driver) for real multi-node setups. */ export interface MemoryPubSubOptions { /** Optional error sink for handler exceptions. Defaults to console.error. */ diff --git a/packages/services/service-cluster/src/testing.ts b/packages/services/service-cluster/src/testing.ts index f37062cf17..0920bc35bf 100644 --- a/packages/services/service-cluster/src/testing.ts +++ b/packages/services/service-cluster/src/testing.ts @@ -4,7 +4,8 @@ * Generic contract tests for cluster primitives. * * These are written once and run against any driver. The memory driver - * suite calls them directly; future postgres/redis driver packages will + * suite calls them directly; driver packages (the redis driver today, + * any future ones) * `import { runPubSubContract } from '@objectstack/service-cluster/testing'` * to get the same coverage for free. */ diff --git a/packages/spec/src/contracts/cluster-service.ts b/packages/spec/src/contracts/cluster-service.ts index 2ee7f58f29..bbcad4dd05 100644 --- a/packages/spec/src/contracts/cluster-service.ts +++ b/packages/spec/src/contracts/cluster-service.ts @@ -263,7 +263,7 @@ export interface ICounter { export interface IClusterService { /** Stable identifier of this node within the cluster. */ readonly nodeId: string; - /** Driver name in use ('memory' | 'redis' | 'postgres' | 'nats' | 'custom'). */ + /** Driver name in use ('memory' | 'redis' | 'custom', or a runtime-registered driver name). */ readonly driver: string; readonly pubsub: IPubSub; readonly lock: ILock; diff --git a/packages/spec/src/kernel/cluster.test.ts b/packages/spec/src/kernel/cluster.test.ts index e51a384727..082c087bcb 100644 --- a/packages/spec/src/kernel/cluster.test.ts +++ b/packages/spec/src/kernel/cluster.test.ts @@ -144,12 +144,12 @@ describe('cluster.zod', () => { expect(parsed.useExistingPool).toBe(true); }); - it('parses a postgres driver config', () => { + it('parses a custom driver config', () => { const parsed = ClusterCapabilityConfigSchema.parse({ - driver: 'postgres', + driver: 'custom', nodeId: 'node-prod-1', }); - expect(parsed.driver).toBe('postgres'); + expect(parsed.driver).toBe('custom'); expect(parsed.nodeId).toBe('node-prod-1'); }); @@ -162,6 +162,30 @@ describe('cluster.zod', () => { expect(parsed.url).toBe('redis://localhost:6379'); }); + it('enumerates exactly the drivers that ship plus custom', () => { + // Pin the roster: a value must not re-enter this enum without an + // implementation behind it (cloud#1626 ruling, 2026-08-24). + expect(ClusterDriverSchema.options).toEqual(['memory', 'redis', 'custom']); + }); + + it('rejects the removed dangling drivers postgres and nats by name', () => { + for (const removed of ['postgres', 'nats']) { + const result = ClusterDriverSchema.safeParse(removed); + expect(result.success).toBe(false); + if (!result.success) { + const issue = result.error.issues[0]; + // zod v4 invalid_value issue: `values` is the accept set — the + // removed spelling must not be in it. + expect(issue.code).toBe('invalid_value'); + expect((issue as { values?: unknown[] }).values).toEqual([ + 'memory', 'redis', 'custom', + ]); + } + const config = ClusterCapabilityConfigSchema.safeParse({ driver: removed }); + expect(config.success).toBe(false); + } + }); + it('rejects unknown driver', () => { expect(() => ClusterDriverSchema.parse('etcd'), diff --git a/packages/spec/src/kernel/cluster.zod.ts b/packages/spec/src/kernel/cluster.zod.ts index 8936b3c525..37fdc590c1 100644 --- a/packages/spec/src/kernel/cluster.zod.ts +++ b/packages/spec/src/kernel/cluster.zod.ts @@ -8,8 +8,9 @@ import { lazySchema } from '../shared/lazy-schema'; * * Defines the runtime semantics required for ObjectStack to behave correctly * when more than one Node.js process is involved. The protocol layer codifies - * **intent** (scope, delivery, leadership); concrete implementations - * (`memory`, `redis`, `postgres`, `nats`) live in `@objectstack/service-cluster`. + * **intent** (scope, delivery, leadership); concrete implementations live in + * `@objectstack/service-cluster` (`memory`) and + * `@objectstack/service-cluster-redis` (`redis`). * * The full design rationale is in * `content/docs/kernel/cluster.mdx`. Read it before changing @@ -202,16 +203,21 @@ export type ServiceClusterAnnotationsParsed = z.infer z.object({ .describe('Cluster transport driver. Defaults to in-memory single-process.'), /** - * Driver-specific connection string. Required for `redis` and `nats`, - * optional for `postgres` (defaults to the main DB pool when - * `useExistingPool` is true). + * Driver-specific connection string. Required for `redis`; a `custom` + * driver reads it as its factory defines. */ url: z.string().url().optional() .describe('Driver-specific connection URL.'), /** - * When `driver === 'postgres'`, reuse the main application database - * pool instead of opening a dedicated one. Recommended for small/medium - * deployments — zero new infrastructure. + * Reuse the main application database pool instead of opening a + * dedicated one. Forwarded verbatim to the registered driver factory; + * meaningful only for database-backed `custom` drivers — the built-in + * `memory` and `redis` drivers ignore it. * @default true */ useExistingPool: z.boolean().optional().default(true) - .describe('Reuse the main DB pool for the postgres driver.'), + .describe('Reuse the main DB pool for database-backed custom drivers.'), /** * Stable identifier for this node. Used by leader election and trace diff --git a/packages/spec/src/migrations/entries/semantic/18.cluster-driver-dangling-values-removed.ts b/packages/spec/src/migrations/entries/semantic/18.cluster-driver-dangling-values-removed.ts new file mode 100644 index 0000000000..a9ca6bacf7 --- /dev/null +++ b/packages/spec/src/migrations/entries/semantic/18.cluster-driver-dangling-values-removed.ts @@ -0,0 +1,42 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import type { SemanticMigration } from '../../types.js'; + +export const entry: SemanticMigration = { + id: 'cluster-driver-dangling-values-removed', + surface: 'kernel.cluster.driver (ClusterDriverSchema, kernel/cluster.zod.ts) ' + + '- the `postgres` and `nats` enum values', + replacement: 'the drivers that actually ship - `memory` (single-process ' + + 'default), `redis` (@objectstack/service-cluster-redis, the production ' + + 'recommendation), or `custom` + registerClusterDriver(name, factory) for ' + + 'a self-provided transport. A config naming `postgres` or `nats` never ' + + 'worked: pick `redis`, or register the transport yourself under `custom`', + reason: + 'Maintainer ruling on objectstack-ai/cloud#1626 (2026-08-24, option B ' + + 'adopted): single-node is the ObjectOS EE boundary, multi-node is Cloud ' + + 'differentiation, and a DB-first postgres cluster driver is not built ' + + 'absent concrete customer pull. The ruling\'s principle rider decides ' + + 'this entry: a schema-valid value must not be an unconditional runtime ' + + 'throw. Both removed values were dangling by the same measurement - the ' + + 'only non-test registerClusterDriver() caller is service-cluster-redis, ' + + 'so `driver: \'postgres\'` or `driver: \'nats\'` passed schema ' + + 'validation and then reached defineCluster()\'s unconditional `Cluster ' + + 'driver "" is not registered` throw. It is a SEMANTIC entry ' + + 'rather than a mechanical conversion because the right replacement is a ' + + 'deployment decision (which transport actually backs this cluster), not ' + + 'a rename a codemod could apply; nothing at rest breaks, because a ' + + 'stored config naming either value never survived boot in the first ' + + 'place. The ruling records its own reversal condition: a value returns ' + + 'to the enum only in the release that ships an implementation behind ' + + 'it. No authorable KEY was retired (the `useExistingPool` field stays, ' + + 'reworded), so nothing lands in RETIRED_KEYS_BY_MAJOR.', + acceptanceCriteria: + 'No `cluster.driver` config names `postgres` or `nats`; ' + + '`ClusterDriverSchema.parse` on the chosen driver value succeeds; a ' + + 'deployment that needed a distributed transport boots on `redis` (or ' + + 'its `custom` registration) and `defineCluster()` no longer throws ' + + '`Cluster driver "" is not registered` at startup. TypeScript ' + + 'call sites that typed the removed spellings against `ClusterDriver` ' + + 'fail tsc on upgrade; the fix is choosing a shipped driver, never ' + + 'widening a local mirror of the enum.', +}; diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index b68b3df91f..d755c8224f 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -5649,6 +5649,44 @@ const step18: MigrationStep = { + 'that passed while asserting on `deleted` was asserting on `undefined` and needs ' + 'rewriting, not renaming.', }, + { + id: 'cluster-driver-dangling-values-removed', + surface: 'kernel.cluster.driver (ClusterDriverSchema, kernel/cluster.zod.ts) ' + + '- the `postgres` and `nats` enum values', + replacement: 'the drivers that actually ship - `memory` (single-process ' + + 'default), `redis` (@objectstack/service-cluster-redis, the production ' + + 'recommendation), or `custom` + registerClusterDriver(name, factory) for ' + + 'a self-provided transport. A config naming `postgres` or `nats` never ' + + 'worked: pick `redis`, or register the transport yourself under `custom`', + reason: + 'Maintainer ruling on objectstack-ai/cloud#1626 (2026-08-24, option B ' + + 'adopted): single-node is the ObjectOS EE boundary, multi-node is Cloud ' + + 'differentiation, and a DB-first postgres cluster driver is not built ' + + 'absent concrete customer pull. The ruling\'s principle rider decides ' + + 'this entry: a schema-valid value must not be an unconditional runtime ' + + 'throw. Both removed values were dangling by the same measurement - the ' + + 'only non-test registerClusterDriver() caller is service-cluster-redis, ' + + 'so `driver: \'postgres\'` or `driver: \'nats\'` passed schema ' + + 'validation and then reached defineCluster()\'s unconditional `Cluster ' + + 'driver "" is not registered` throw. It is a SEMANTIC entry ' + + 'rather than a mechanical conversion because the right replacement is a ' + + 'deployment decision (which transport actually backs this cluster), not ' + + 'a rename a codemod could apply; nothing at rest breaks, because a ' + + 'stored config naming either value never survived boot in the first ' + + 'place. The ruling records its own reversal condition: a value returns ' + + 'to the enum only in the release that ships an implementation behind ' + + 'it. No authorable KEY was retired (the `useExistingPool` field stays, ' + + 'reworded), so nothing lands in RETIRED_KEYS_BY_MAJOR.', + acceptanceCriteria: + 'No `cluster.driver` config names `postgres` or `nats`; ' + + '`ClusterDriverSchema.parse` on the chosen driver value succeeds; a ' + + 'deployment that needed a distributed transport boots on `redis` (or ' + + 'its `custom` registration) and `defineCluster()` no longer throws ' + + '`Cluster driver "" is not registered` at startup. TypeScript ' + + 'call sites that typed the removed spellings against `ClusterDriver` ' + + 'fail tsc on upgrade; the fix is choosing a shipped driver, never ' + + 'widening a local mirror of the enum.', + }, { id: 'dashboard-header-modal-target-page-only', surface: From 33d5649661e0f8cfbdd434c7f3d0b31da60bf902 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 1 Sep 2026 06:59:53 +0000 Subject: [PATCH 2/2] test(service-cluster): use redis as the unlisted-driver sample in the registry pin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The registry read-vs-throw agreement pin used postgres as its schema-valid but never-registered sample; with postgres removed from ClusterDriverSchema the config now fails parse before the registry is consulted, so the expected "not registered" throw is unreachable. redis keeps the pin's exact intent: in the enum, but this suite never imports the package whose load-time side effect registers it, so in this module instance it is the requested-but-not- registered case — the same shape as the EE boot the suite's header records. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01Mciyv38maJ6HYVMiaM26T1 --- .../src/cluster-driver-registry.test.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/services/service-cluster/src/cluster-driver-registry.test.ts b/packages/services/service-cluster/src/cluster-driver-registry.test.ts index c39cbe5dc0..43b9e4dcdd 100644 --- a/packages/services/service-cluster/src/cluster-driver-registry.test.ts +++ b/packages/services/service-cluster/src/cluster-driver-registry.test.ts @@ -50,11 +50,15 @@ describe('the driver registry can be read, not only written (#13330)', () => { }); it('agrees with defineCluster — unlisted means the documented throw', () => { - // The other direction. `postgres` is accepted by the schema and shipped by - // nobody, which is exactly the "requested but not registered" case. - expect(listClusterDrivers()).not.toContain('postgres'); - expect(() => defineCluster({ driver: 'postgres' })).toThrow( - /Cluster driver "postgres" is not registered/, + // The other direction. `redis` is accepted by the schema, but this suite + // never imports the driver package whose load-time side effect registers + // it, so in THIS module instance it is exactly the "requested but not + // registered" case — the same shape as the EE boot in the header. (The + // original sample value `postgres` left the schema in #13393: it now + // fails parse inside defineCluster before the registry is consulted.) + expect(listClusterDrivers()).not.toContain('redis'); + expect(() => defineCluster({ driver: 'redis' })).toThrow( + /Cluster driver "redis" is not registered/, ); }); });