import 'reflect-metadata'; import { Global, Module } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'; import type { HarnessAdapter, HarnessConversationService } from '@mosaicstack/types'; import { AgentService } from '../agent/agent.service.js'; import { AuthGuard } from '../auth/auth.guard.js'; import { CommandsModule } from '../commands/commands.module.js'; import { HarnessModule } from '../harness/harness.module.js'; import { ChatModule } from './chat.module.js'; import { ChatGateway } from './chat.gateway.js'; import { HarnessRegistry } from '../harness/harness.registry.js'; import { HARNESS_CONVERSATION_SERVICE, HARNESS_CONVERSATION_SERVICE_UNAVAILABLE, HARNESS_REGISTRY, type HarnessConversationServiceBinding, } from '../harness/harness.tokens.js'; import { ChatRuntimeRouter } from './chat-runtime-router.js'; import { ChatRuntimeUnavailableError, ownConversation, type ChatRuntime, type ChatRuntimeMode, type LegacyEmbeddedChatPort, type LegacyRuntimeStream, type LegacySessionPresentation, type LegacySocketTurnLease, type OwnedConversationContext, } from './chat-runtime.js'; import { AppModule } from '../app.module.js'; import { ProviderService } from '../agent/provider.service.js'; /** * Task Five, Step One (router). Proves the `ChatRuntimeRouter` resolves exactly one * runtime by mode, fails closed at init when `pi-rpc` preconditions are unmet, and * never downgrades `pi-rpc` to embedded execution. Red-first: the router is an * unimplemented stub, so every behavioural assertion below fails until Step Three. */ const embedded: ChatRuntime = { kind: 'embedded' }; const harness: ChatRuntime = { kind: 'harness' }; /** A structurally-complete, non-sentinel conversation service. Its methods are never invoked here. */ const boundConversationService = { attach: () => Promise.reject(new Error('unused')), detach: () => Promise.reject(new Error('unused')), send: () => Promise.reject(new Error('unused')), subscribeFrom: async function* () { throw new Error('unused'); }, } as unknown as HarnessConversationService; function registryWith(adapterIds: readonly string[]): HarnessRegistry { const registry = new HarnessRegistry(); for (const id of adapterIds) { registry.register({ id, describe: () => Promise.reject(new Error('unused')), catalog: () => Promise.reject(new Error('unused')), create: () => Promise.reject(new Error('unused')), resume: () => Promise.reject(new Error('unused')), } as HarnessAdapter); } return registry; } function buildRouter( mode: ChatRuntimeMode, opts: { adapters: readonly string[]; service: HarnessConversationServiceBinding }, ): ChatRuntimeRouter { return new ChatRuntimeRouter(registryWith(opts.adapters), opts.service, embedded, harness, mode); } /** * Tear down a module that was deliberately driven to a fail-closed init. * `NestApplicationContext.close()` re-awaits the module's `initializationPromise` before disposing * (nest-application-context.js:127); when `init()` rejected, that await re-throws the SAME typed * startup error, this time into teardown. Each caller here has already captured and asserted that * exact `ChatRuntimeUnavailableError` via `initError`, so the re-throw is expected teardown noise — * swallow ONLY that error, and surface anything else so a genuine teardown fault still fails loudly. */ async function closeIgnoringFailedInit(moduleRef: TestingModule): Promise { await moduleRef.close().catch((err: unknown) => { if (err instanceof ChatRuntimeUnavailableError) return; throw err; }); } describe('ChatRuntimeRouter', () => { it('resolves only the harness runtime in pi-rpc mode when pi adapter and conversation service are present', () => { const router = buildRouter('pi-rpc', { adapters: ['pi'], service: boundConversationService, }); expect(() => router.onModuleInit()).not.toThrow(); expect(router.active).toBe(harness); expect(router.active.kind).toBe('harness'); }); it('resolves only the embedded runtime in legacy mode and skips the pi preconditions', () => { // Empty registry + unavailable service: legacy must ignore both and still start. const router = buildRouter('legacy', { adapters: [], service: HARNESS_CONVERSATION_SERVICE_UNAVAILABLE, }); expect(() => router.onModuleInit()).not.toThrow(); expect(router.active).toBe(embedded); expect(router.active.kind).toBe('embedded'); }); it('fails closed at init when pi-rpc mode has no registered pi adapter', () => { const router = buildRouter('pi-rpc', { adapters: [], service: boundConversationService, }); expect(() => router.onModuleInit()).toThrow(ChatRuntimeUnavailableError); try { router.onModuleInit(); expect.unreachable('onModuleInit must throw when the pi adapter is absent'); } catch (err) { expect(err).toBeInstanceOf(ChatRuntimeUnavailableError); expect((err as ChatRuntimeUnavailableError).reason).toBe('adapter_unavailable'); expect((err as ChatRuntimeUnavailableError).code).toBe('runtime_unsupported'); } }); it('fails closed at init when pi-rpc mode has the unavailable conversation-service sentinel', () => { const router = buildRouter('pi-rpc', { adapters: ['pi'], service: HARNESS_CONVERSATION_SERVICE_UNAVAILABLE, }); try { router.onModuleInit(); expect.unreachable('onModuleInit must throw when the conversation service is unbound'); } catch (err) { expect(err).toBeInstanceOf(ChatRuntimeUnavailableError); expect((err as ChatRuntimeUnavailableError).reason).toBe('conversation_service_unavailable'); expect((err as ChatRuntimeUnavailableError).code).toBe('runtime_unsupported'); } }); it('never falls back to embedded execution when pi-rpc preconditions are unmet', () => { const router = buildRouter('pi-rpc', { adapters: [], service: HARNESS_CONVERSATION_SERVICE_UNAVAILABLE, }); expect(() => router.onModuleInit()).toThrow(ChatRuntimeUnavailableError); // A failed pi-rpc init must not silently expose the embedded runtime. expect(() => router.active).toThrow(); let leaked: ChatRuntime | undefined; try { leaked = router.active; } catch { leaked = undefined; } expect(leaked).not.toBe(embedded); }); it('exposes only fixed, browser-safe failure text (no raw provider or exception detail)', () => { const router = buildRouter('pi-rpc', { adapters: [], service: boundConversationService, }); try { router.onModuleInit(); expect.unreachable('onModuleInit must throw'); } catch (err) { const message = (err as ChatRuntimeUnavailableError).message; expect(message).toBe( 'The pi-rpc chat runtime is unavailable: no "pi" harness adapter is registered.', ); expect(message).not.toMatch(/Error:|\bat \b|node_modules|Symbol\(/); } }); }); /** * Task Five, Step Three — legacy port operations fail closed under pi-rpc (direct valid-input). * * The unit suite above constructs the router but never invokes a legacy port operation, so the * six per-operation inner `if (this.mode === 'pi-rpc')` guards are unexercised — a mutation that * deletes one of them SURVIVES for lack of a test that drives that operation. This group closes * that gap the right way: it drives each of the six operations DIRECTLY, in pi-rpc mode, with a * valid branded {@link OwnedConversationContext} and valid input, against a recording embedded * stub whose method returns a distinguishable `ok:true` success and increments a per-op counter. * * For each operation: * - pi-rpc test asserts the exact frozen `{ ok:false, code:'runtime_unsupported', retryable:false }` * result AND that the embedded stub was touched zero times (no effects); * - the paired legacy test proves that same stub method IS reached and returns its distinguishable * success when the mode does not refuse — so the pi-rpc zero-invocation assertion is meaningful, * not vacuously true because the stub could never be called. * * Deleting ONLY one operation's inner guard makes THAT operation's pi-rpc test behaviorally RED * (the router returns the embedded `ok:true` value and records the call), with every outer guard * and the other five inner guards intact. `next` is untouched; nothing here changes production. */ describe('ChatRuntimeRouter — legacy port ops fail closed under pi-rpc (Task Five, Step Three)', () => { const RUNTIME_UNSUPPORTED = { ok: false, code: 'runtime_unsupported', retryable: false, } as const; const PRESENTATION: LegacySessionPresentation = { provider: 'embedded-provider', modelId: 'embedded-model', thinkingLevel: 'low', availableThinkingLevels: ['low', 'high'], }; const stream: LegacyRuntimeStream = { channelId: 'websocket:test-socket', onEvent: () => {}, }; const ctx = (): OwnedConversationContext => ownConversation('conversation-1', { userId: 'user-1', tenantId: 'tenant-1' }); /** * Per-operation invocation counters with declared keys (not an index signature) so each * `calls.` is definitely `number` under `noUncheckedIndexedAccess`. */ type LegacyPortCallCounts = { completeLegacyRestTurn: number; prepareLegacySocketTurn: number; setLegacyThinking: number; abortLegacyTurn: number; applyLegacyModelOverride: number; readLegacySessionPresentation: number; dispatchVerifiedDiscordIngress: number; }; /** * An embedded port that records every invocation and returns a distinguishable `ok:true` * value per operation. If a router op reaches it (its guard removed), both the recorded call * count and the returned `ok:true` value diverge from the frozen `runtime_unsupported` result. */ function recordingEmbeddedPort(): { port: ChatRuntime & LegacyEmbeddedChatPort; calls: LegacyPortCallCounts; } { const calls: LegacyPortCallCounts = { completeLegacyRestTurn: 0, prepareLegacySocketTurn: 0, setLegacyThinking: 0, abortLegacyTurn: 0, applyLegacyModelOverride: 0, readLegacySessionPresentation: 0, dispatchVerifiedDiscordIngress: 0, }; const lease: LegacySocketTurnLease = { presentation: PRESENTATION, dispatch: () => Promise.resolve({ ok: true, value: undefined }), dispose: () => Promise.resolve(), }; const port: ChatRuntime & LegacyEmbeddedChatPort = { kind: 'embedded', completeLegacyRestTurn: () => { calls.completeLegacyRestTurn += 1; return Promise.resolve({ ok: true, value: { text: 'EMBEDDED-REST', presentation: PRESENTATION }, }); }, prepareLegacySocketTurn: () => { calls.prepareLegacySocketTurn += 1; return Promise.resolve({ ok: true, value: lease }); }, setLegacyThinking: () => { calls.setLegacyThinking += 1; return { ok: true, value: PRESENTATION }; }, abortLegacyTurn: () => { calls.abortLegacyTurn += 1; return Promise.resolve({ ok: true, value: undefined }); }, applyLegacyModelOverride: () => { calls.applyLegacyModelOverride += 1; return { ok: true, value: PRESENTATION }; }, readLegacySessionPresentation: () => { calls.readLegacySessionPresentation += 1; return { ok: true, value: PRESENTATION }; }, dispatchVerifiedDiscordIngress: () => { calls.dispatchVerifiedDiscordIngress += 1; return Promise.resolve({ ok: true, value: { presentation: PRESENTATION, dispatch: () => Promise.resolve({ ok: true, value: undefined }), dispose: () => Promise.resolve(), }, }); }, }; return { port, calls }; } function piRouter(port: ChatRuntime & LegacyEmbeddedChatPort): ChatRuntimeRouter { return new ChatRuntimeRouter( registryWith(['pi']), boundConversationService, port, harness, 'pi-rpc', ); } function legacyRouter(port: ChatRuntime & LegacyEmbeddedChatPort): ChatRuntimeRouter { return new ChatRuntimeRouter( registryWith([]), boundConversationService, port, harness, 'legacy', ); } // completeLegacyRestTurn --------------------------------------------------- it('completeLegacyRestTurn refuses with runtime_unsupported and never touches embedded under pi-rpc', async () => { const { port, calls } = recordingEmbeddedPort(); const result = await piRouter(port).completeLegacyRestTurn(ctx(), { content: 'hello' }); expect(result).toEqual(RUNTIME_UNSUPPORTED); expect(calls.completeLegacyRestTurn).toBe(0); }); it('completeLegacyRestTurn delegates to embedded under legacy (guard is the sole gate)', async () => { const { port, calls } = recordingEmbeddedPort(); const result = await legacyRouter(port).completeLegacyRestTurn(ctx(), { content: 'hello' }); expect(result.ok).toBe(true); expect(calls.completeLegacyRestTurn).toBe(1); }); // prepareLegacySocketTurn -------------------------------------------------- it('prepareLegacySocketTurn refuses with runtime_unsupported and never touches embedded under pi-rpc', async () => { const { port, calls } = recordingEmbeddedPort(); const result = await piRouter(port).prepareLegacySocketTurn( ctx(), { content: 'hello' }, stream, ); expect(result).toEqual(RUNTIME_UNSUPPORTED); expect(calls.prepareLegacySocketTurn).toBe(0); }); it('prepareLegacySocketTurn delegates to embedded under legacy (guard is the sole gate)', async () => { const { port, calls } = recordingEmbeddedPort(); const result = await legacyRouter(port).prepareLegacySocketTurn( ctx(), { content: 'hello' }, stream, ); expect(result.ok).toBe(true); expect(calls.prepareLegacySocketTurn).toBe(1); }); // setLegacyThinking (sync) ------------------------------------------------- it('setLegacyThinking refuses with runtime_unsupported and never touches embedded under pi-rpc', () => { const { port, calls } = recordingEmbeddedPort(); const result = piRouter(port).setLegacyThinking(ctx(), 'high'); expect(result).toEqual(RUNTIME_UNSUPPORTED); expect(calls.setLegacyThinking).toBe(0); }); it('setLegacyThinking delegates to embedded under legacy (guard is the sole gate)', () => { const { port, calls } = recordingEmbeddedPort(); const result = legacyRouter(port).setLegacyThinking(ctx(), 'high'); expect(result.ok).toBe(true); expect(calls.setLegacyThinking).toBe(1); }); // abortLegacyTurn ---------------------------------------------------------- it('abortLegacyTurn refuses with runtime_unsupported and never touches embedded under pi-rpc', async () => { const { port, calls } = recordingEmbeddedPort(); const result = await piRouter(port).abortLegacyTurn(ctx()); expect(result).toEqual(RUNTIME_UNSUPPORTED); expect(calls.abortLegacyTurn).toBe(0); }); it('abortLegacyTurn delegates to embedded under legacy (guard is the sole gate)', async () => { const { port, calls } = recordingEmbeddedPort(); const result = await legacyRouter(port).abortLegacyTurn(ctx()); expect(result.ok).toBe(true); expect(calls.abortLegacyTurn).toBe(1); }); // applyLegacyModelOverride (sync) ------------------------------------------ it('applyLegacyModelOverride refuses with runtime_unsupported and never touches embedded under pi-rpc', () => { const { port, calls } = recordingEmbeddedPort(); const result = piRouter(port).applyLegacyModelOverride(ctx(), 'model-x'); expect(result).toEqual(RUNTIME_UNSUPPORTED); expect(calls.applyLegacyModelOverride).toBe(0); }); it('applyLegacyModelOverride delegates to embedded under legacy (guard is the sole gate)', () => { const { port, calls } = recordingEmbeddedPort(); const result = legacyRouter(port).applyLegacyModelOverride(ctx(), 'model-x'); expect(result.ok).toBe(true); expect(calls.applyLegacyModelOverride).toBe(1); }); // readLegacySessionPresentation (sync) ------------------------------------- it('readLegacySessionPresentation refuses with runtime_unsupported and never touches embedded under pi-rpc', () => { const { port, calls } = recordingEmbeddedPort(); const result = piRouter(port).readLegacySessionPresentation(ctx()); expect(result).toEqual(RUNTIME_UNSUPPORTED); expect(calls.readLegacySessionPresentation).toBe(0); }); it('readLegacySessionPresentation delegates to embedded under legacy (guard is the sole gate)', () => { const { port, calls } = recordingEmbeddedPort(); const result = legacyRouter(port).readLegacySessionPresentation(ctx()); expect(result.ok).toBe(true); expect(calls.readLegacySessionPresentation).toBe(1); }); // dispatchVerifiedDiscordIngress delegates in BOTH modes (embedded-only, no guard) --------- it('dispatchVerifiedDiscordIngress delegates to embedded under pi-rpc (embedded-only, no mode guard)', async () => { const { port, calls } = recordingEmbeddedPort(); const discordCtx = ctx() as unknown as Parameters< ChatRuntimeRouter['dispatchVerifiedDiscordIngress'] >[0]; const result = await piRouter(port).dispatchVerifiedDiscordIngress(discordCtx, stream); expect(result.ok).toBe(true); expect(calls.dispatchVerifiedDiscordIngress).toBe(1); }); }); /** * Task Five, Step Two — group 1 (real Nest module-graph readiness). * * The unit suite above constructs the router directly. This group drives the SAME contract * through a real NestJS graph: it imports the production `HarnessModule` (the proven-booting * idiom from harness.controller.spec.ts) so the router resolves the REAL, empty `HarnessRegistry` * via the real `HARNESS_REGISTRY` token, then runs the router's `OnModuleInit` through the Nest * lifecycle (`moduleRef.init()`). Red-first: the router is an unimplemented stub whose * `onModuleInit` throws a generic Error, so: * - readiness cases fail because the graph never comes up (init rejects), and * - fail-closed cases fail because a generic stub throw is NOT the SPECIFIC typed * `ChatRuntimeUnavailableError` (reason/code) the contract demands — a stub that * "throws anything" cannot mask these greens. * The router is NOT wired into a production module yet, so it is provided here via a factory * over the real registry token. Importing the real `ChatModule` bare is deliberately avoided: * it injects `AgentService` without importing `AgentModule`, so its graph fails to RESOLVE — a * collection/DI error, not a behavioural red. `next` is untouched; nothing here implements the router. */ describe('ChatRuntimeRouter — real Nest module-graph readiness (Task Five, Step Two group 1)', () => { async function bootRouterGraph( mode: ChatRuntimeMode, opts: { adapters: readonly string[]; service: HarnessConversationServiceBinding }, ) { const moduleRef = await Test.createTestingModule({ imports: [HarnessModule], providers: [ { provide: ChatRuntimeRouter, useFactory: (registry: HarnessRegistry) => new ChatRuntimeRouter(registry, opts.service, embedded, harness, mode), inject: [HARNESS_REGISTRY], }, ], }) // The imported HarnessModule's controllers reference AuthGuard (an HTTP-only concern, // never exercised here); stub it so the graph resolves. The registry is NOT overridden — // group 1 asserts against the genuine production HarnessRegistry. .overrideGuard(AuthGuard) .useValue({ canActivate: () => true }) .compile(); // Resolve the production registry singleton and register the requested adapters ON IT, so // the router (which injects the same singleton) sees them when its lifecycle hook runs. const registry = moduleRef.get(HARNESS_REGISTRY, { strict: false }); for (const id of opts.adapters) { registry.register({ id, describe: () => Promise.reject(new Error('unused')), catalog: () => Promise.reject(new Error('unused')), create: () => Promise.reject(new Error('unused')), resume: () => Promise.reject(new Error('unused')), } as HarnessAdapter); } return moduleRef; } // Capture an init rejection without letting a resolved init masquerade as success. const initError = (moduleRef: { init(): Promise }): Promise => moduleRef.init().then( () => new Error('module init resolved but the contract requires it to reject'), (err: unknown) => err, ); it('brings the graph up and resolves only the harness runtime in pi-rpc mode (pi adapter + bound service)', async () => { const moduleRef = await bootRouterGraph('pi-rpc', { adapters: ['pi'], service: boundConversationService, }); try { await moduleRef.init(); const router = moduleRef.get(ChatRuntimeRouter, { strict: false }); expect(router.active).toBe(harness); expect(router.active.kind).toBe('harness'); } finally { await moduleRef.close(); } }); it('brings the graph up in legacy mode over the REAL empty HarnessRegistry and resolves only the embedded runtime', async () => { const moduleRef = await bootRouterGraph('legacy', { adapters: [], service: HARNESS_CONVERSATION_SERVICE_UNAVAILABLE, }); try { // Defense-in-depth: the production module wires the genuine registry, empty by default — // guards against a test-double registry silently satisfying the readiness check. const registry = moduleRef.get(HARNESS_REGISTRY, { strict: false }); expect(registry).toBeInstanceOf(HarnessRegistry); expect(registry.list()).toHaveLength(0); await moduleRef.init(); const router = moduleRef.get(ChatRuntimeRouter, { strict: false }); expect(router.active).toBe(embedded); expect(router.active.kind).toBe('embedded'); } finally { await moduleRef.close(); } }); it('fails closed at module init when pi-rpc mode has no registered pi adapter (specific typed error, not a stub throw)', async () => { const moduleRef = await bootRouterGraph('pi-rpc', { adapters: [], service: boundConversationService, }); try { const err = await initError(moduleRef); expect(err).toBeInstanceOf(ChatRuntimeUnavailableError); expect((err as ChatRuntimeUnavailableError).reason).toBe('adapter_unavailable'); expect((err as ChatRuntimeUnavailableError).code).toBe('runtime_unsupported'); } finally { await closeIgnoringFailedInit(moduleRef); } }); it('fails closed at module init when pi-rpc mode has the unavailable conversation-service sentinel', async () => { const moduleRef = await bootRouterGraph('pi-rpc', { adapters: ['pi'], service: HARNESS_CONVERSATION_SERVICE_UNAVAILABLE, }); try { const err = await initError(moduleRef); expect(err).toBeInstanceOf(ChatRuntimeUnavailableError); expect((err as ChatRuntimeUnavailableError).reason).toBe('conversation_service_unavailable'); expect((err as ChatRuntimeUnavailableError).code).toBe('runtime_unsupported'); } finally { await closeIgnoringFailedInit(moduleRef); } }); it('surfaces only fixed, browser-safe failure text when the graph fails closed (no stub/exception detail)', async () => { const moduleRef = await bootRouterGraph('pi-rpc', { adapters: [], service: boundConversationService, }); try { const err = await initError(moduleRef); expect(err).toBeInstanceOf(ChatRuntimeUnavailableError); const message = (err as ChatRuntimeUnavailableError).message; expect(message).toBe( 'The pi-rpc chat runtime is unavailable: no "pi" harness adapter is registered.', ); expect(message).not.toMatch(/Error:|\bat \b|node_modules|Symbol\(|not implemented/); } finally { await closeIgnoringFailedInit(moduleRef); } }); }); /** * Task Five, Step Two — group 1b (production ChatModule wiring, declaration proof). * * Correction #1 (Scrappy fe3e02) asked for a red that imports the real `ChatModule` and calls * `module.init()`. Investigated and found impractical/masking-prone: `ChatModule` provides * `ChatGateway`, whose 10-argument constructor injects app-global providers (AgentService, AUTH, * BRAIN, RoutingEngineService) plus the Commands/GC/Mcp/Reload subsystems across a forwardRef * cycle. Booting it in isolation is a full-app integration boot — "override only unrelated * dependencies" balloons into faking ~4 subsystems, and `overrideProvider` cannot even grant the * cross-module export-scope visibility ChatGateway needs (probe: `ChatGateway` unresolved at * `CommandExecutorService`). That is exactly the STOP-and-return branch of the directive. * * The faithful, unmaskable cover instead of a fragile boot: read the PRODUCTION `ChatModule`'s own * Nest `@Module` metadata to prove it DECLARES the exclusive router provider and imports the real * `HarnessModule` (the genuine registry source). This inspects the actual module object — not * source text, not a test factory — so nothing can mask it. Group 1 above separately proves the * router RESOLVES against the real, empty `HarnessRegistry` through the Nest lifecycle; the union * of the two covers "the router is wired through ChatModule to the real registry" without the * impractical single-graph boot. RED today (ChatModule provides only ChatGateway and imports only * CommandsModule); GREEN once Step Three registers the router and imports HarnessModule. */ describe('ChatModule production wiring (Task Five, Step Two group 1b — declaration proof)', () => { // Unwrap a forwardRef(() => Module) import to the module it references; pass others through. const resolveImport = (imp: unknown): unknown => imp && typeof imp === 'object' && typeof (imp as { forwardRef?: unknown }).forwardRef === 'function' ? (imp as { forwardRef: () => unknown }).forwardRef() : imp; // A provider entry is either a class (shorthand) or a { provide, ... } object; take its token. const providerToken = (provider: unknown): unknown => typeof provider === 'function' ? provider : (provider as { provide?: unknown })?.provide; it('declares the exclusive ChatRuntimeRouter as a provider on the production ChatModule', () => { const providers: unknown[] = Reflect.getMetadata('providers', ChatModule) ?? []; expect(providers.map(providerToken)).toContain(ChatRuntimeRouter); }); it('imports the real HarnessModule into the production ChatModule (registry source, not a test double)', () => { const imports: unknown[] = Reflect.getMetadata('imports', ChatModule) ?? []; expect(imports.map(resolveImport)).toContain(HarnessModule); }); }); /** * Task Five, Step Two — group 1c (bounded real-`ChatModule` boot). * * Scrappy adjudication d67d2b (option c): boot the ACTUAL production `ChatModule` as the SUT and * assert the exclusive router resolves THROUGH it — the single-graph proof group 1 (router over the * real registry) and group 1b (production-module metadata) each cover only a half of. The heavy, * UNRELATED cycle is the only thing bounded away, per the established isolation pattern in * `apps/gateway/src/agent/hermes-runtime-reachability.e2e.test.ts`: * - `CommandsModule` (drags the Commands <-> Reload <-> Chat forwardRef cycle plus GC/Mcp/queue) * is replaced wholesale with an empty module via `.overrideModule(...).useModule(...)`; * - `ChatGateway` (10-arg constructor, an HTTP/socket concern never exercised here) is replaced * with an inert value; * - the sole legacy-controller dependency, `AgentService`, is supplied by a tiny `@Global()` stub; * - the HTTP-only `AuthGuard` is stubbed. * Nothing about the router, `HarnessModule`, the registry, or the conversation-service binding is * faked in the production-legacy case — those are retrieved from the REAL `ChatModule` graph. Mode * is driven only through the production `CHAT_HARNESS_RUNTIME` env contract (`resolveChatRuntimeMode`). * * Red-first: today `ChatModule` neither imports `HarnessModule` nor provides `ChatRuntimeRouter`, so * the booted graph contains no router/registry/conversation-service tokens. `init()` may resolve * (there is no router lifecycle hook yet to reject), so every case fails on the MISSING actual * router/registry/service wiring — not on unrelated DI, which is bounded away. GREEN at Step Three * once `ChatModule` imports `HarnessModule`, provides the exclusive router, and binds the * conversation-service token (defaulting to the unavailable sentinel). */ describe('ChatModule bounded real boot (Task Five, Step Two group 1c)', () => { // The unrelated heavy cycle, replaced wholesale — not stubbed provider-by-provider. @Module({}) class EmptyCommandsModule {} // The ONLY genuine legacy dependency of the real ChatController, supplied inertly and globally so // the pre-refactor controller instantiates without dragging AgentModule into the graph. @Global() @Module({ providers: [{ provide: AgentService, useValue: {} }], exports: [AgentService], }) class LegacyControllerDepsModule {} const ORIGINAL_RUNTIME_ENV = process.env['CHAT_HARNESS_RUNTIME']; afterEach(() => { if (ORIGINAL_RUNTIME_ENV === undefined) delete process.env['CHAT_HARNESS_RUNTIME']; else process.env['CHAT_HARNESS_RUNTIME'] = ORIGINAL_RUNTIME_ENV; }); /** * Boot the real ChatModule with only the unrelated cycle bounded away. `mode` is set through the * genuine production env contract before providers instantiate. The optional overrides replace * the registry / conversation-service the router injects, exercising the pi-rpc precondition * branches through the ACTUAL module (they are no-ops today because those tokens are not yet in * the graph — which is exactly why the router-retrieval assertions go red). */ async function bootChatModule( mode: ChatRuntimeMode, overrides: { registryAdapters?: readonly string[]; conversationService?: HarnessConversationServiceBinding; } = {}, ): Promise { if (mode === 'pi-rpc') process.env['CHAT_HARNESS_RUNTIME'] = 'pi-rpc'; else delete process.env['CHAT_HARNESS_RUNTIME']; let builder = Test.createTestingModule({ imports: [LegacyControllerDepsModule, ChatModule], }) .overrideModule(CommandsModule) .useModule(EmptyCommandsModule) .overrideProvider(ChatGateway) .useValue({}) .overrideGuard(AuthGuard) .useValue({ canActivate: () => true }); if (overrides.registryAdapters) { builder = builder .overrideProvider(HARNESS_REGISTRY) .useValue(registryWith(overrides.registryAdapters)); } if (overrides.conversationService !== undefined) { builder = builder .overrideProvider(HARNESS_CONVERSATION_SERVICE) .useValue(overrides.conversationService); } return builder.compile(); } // Capture an init rejection without letting a resolved init masquerade as success. const initError = (moduleRef: TestingModule): Promise => moduleRef.init().then( () => new Error('module init resolved but the contract requires it to reject'), (err: unknown) => err, ); it('legacy mode: the actual router resolves the embedded runtime, the actual registry is empty, and the conversation-service token is the unavailable sentinel', async () => { const moduleRef = await bootChatModule('legacy'); try { await moduleRef.init(); const router = moduleRef.get(ChatRuntimeRouter, { strict: false }); expect(router.active.kind).toBe('embedded'); const registry = moduleRef.get(HARNESS_REGISTRY, { strict: false }); expect(registry).toBeInstanceOf(HarnessRegistry); expect(registry.list()).toHaveLength(0); const service = moduleRef.get( HARNESS_CONVERSATION_SERVICE, { strict: false, }, ); expect(service).toBe(HARNESS_CONVERSATION_SERVICE_UNAVAILABLE); } finally { await moduleRef.close(); } }); it('pi-rpc mode over the REAL empty registry fails closed at init with the typed adapter-unavailable error', async () => { const moduleRef = await bootChatModule('pi-rpc'); try { const err = await initError(moduleRef); expect(err).toBeInstanceOf(ChatRuntimeUnavailableError); expect((err as ChatRuntimeUnavailableError).reason).toBe('adapter_unavailable'); expect((err as ChatRuntimeUnavailableError).code).toBe('runtime_unsupported'); } finally { await closeIgnoringFailedInit(moduleRef); } }); it('pi-rpc mode with a pi adapter present but the sentinel conversation service fails closed with the typed conversation-service-unavailable error', async () => { const moduleRef = await bootChatModule('pi-rpc', { registryAdapters: ['pi'], conversationService: HARNESS_CONVERSATION_SERVICE_UNAVAILABLE, }); try { const err = await initError(moduleRef); expect(err).toBeInstanceOf(ChatRuntimeUnavailableError); expect((err as ChatRuntimeUnavailableError).reason).toBe('conversation_service_unavailable'); expect((err as ChatRuntimeUnavailableError).code).toBe('runtime_unsupported'); } finally { await closeIgnoringFailedInit(moduleRef); } }); it('pi-rpc mode with a pi adapter and a bound conversation service: the actual router selects the harness runtime', async () => { const moduleRef = await bootChatModule('pi-rpc', { registryAdapters: ['pi'], conversationService: boundConversationService, }); try { await moduleRef.init(); const router = moduleRef.get(ChatRuntimeRouter, { strict: false }); expect(router.active.kind).toBe('harness'); } finally { await moduleRef.close(); } }); }); /** * Task Five, Step Two — group 2 (WHOLE production `AppModule` boot, legacy end-to-end wiring). * * The groups above bound away the heavy cycle to isolate the router. This group instead boots the * ACTUAL production `AppModule` (the exact graph `main.ts` runs) in the default LEGACY chat-runtime * mode, overriding ONLY the storage/network side-effect adapters so the boot is bounded and offline * — never the chat/router/harness/reload/commands surface under test. The bounded fakes are exactly * the disk/network leaves: * - `ProviderService` (the #1 hang risk: its real `onModuleInit` starts an unref'd health-check * `setInterval` and fetches Ollama over HTTP) → inert no-op instance; * - `DB_HANDLE`/`DB` → a fake Drizzle-shaped handle that satisfies `runPgliteMigrations` (the local * tier's `DatabaseModule.onModuleInit`) AND `DefaultRoutingRulesSeed.onModuleInit` (which reads a * system-rule count — the fake reports rules already present so the seed insert is skipped), * opening no real database; * - `STORAGE_ADAPTER`/`MEMORY`/`MEMORY_ADAPTER`/`AUTH`/`BRAIN`/`LOG_SERVICE` → inert fakes so no * storage/auth/log backend is contacted. * Local tier (the repo's `mosaic.config.json`) already disables BullMQ/Redis and the queue handles; * Discord/Telegram/MCP plugins are env-gated and disarmed by deleting their tokens. Nothing about the * router, `ChatModule`, `HarnessModule`, or `ChatGateway` is faked — those come from the REAL graph. * * The boot+init MUST SUCCEED cleanly (proven by `beforeAll` completing and the ChatGateway test * passing). Red-first: on this branch `ChatRuntimeRouter` is registered in NO module (ChatModule * provides only ChatGateway), so `moduleRef.get(ChatRuntimeRouter)` throws `UnknownElementException` * — a WIRING gap, NOT an init failure. That single retrieval is the intended behavioural red; it * flips green once Step Three registers the exclusive router. The ChatGateway retrieval and its * browser-facing method surface are asserted alongside and pass today, pinning that the boot itself * is healthy so the router failure cannot be mistaken for a mis-shaped fake or an unbounded side * effect. */ describe('AppModule production boot — legacy ChatRuntimeRouter wiring (Task Five, Step Two group 2)', () => { // A Drizzle-shaped fake that satisfies both DB consumers reached during a local-tier init: // • runPgliteMigrations(): reads handle.db.$client.exec + handle.db.execute(SELECT hashes); // exec is a no-op and execute yields an empty ledger, so migration statements no-op through. // • DefaultRoutingRulesSeed.seedDefaultRules(): db.select().from().where() must resolve to a // row set — we report a non-zero system-rule count so the seeding INSERT branch is skipped. const fakeDb = { $client: { exec: async (): Promise => {} }, execute: async (): Promise<{ rows: unknown[] }> => ({ rows: [] }), select: () => ({ from: () => ({ where: async (): Promise> => [{ count: 1 }], }), }), insert: () => ({ values: async (): Promise => {} }), }; const fakeDbHandle = { db: fakeDb, close: async (): Promise => {} }; const fakeStorageAdapter = { name: 'fake', migrate: async (): Promise => {}, close: async (): Promise => {}, }; // Inert stand-in for the real ProviderService: no health-check interval, no Ollama fetch. const fakeProviderService = { onModuleInit: async (): Promise => {}, onModuleDestroy: (): void => {}, getRegistry: () => ({ getAvailable: () => [], getAll: () => [], find: () => undefined, }), getDefaultModel: () => undefined, listAvailableModels: () => [], listProviders: () => [], getAdapter: () => undefined, getProvidersHealth: () => [], }; const fakeBrain = { conversations: {}, agents: {} }; const BOOT_TIMEOUT_MS = 120_000; let moduleRef: TestingModule; let envSnapshot: Record; beforeAll(async () => { envSnapshot = { ...process.env }; // Env hygiene: disarm the network-facing plugins/adapters and pin the legacy runtime mode. delete process.env['DATABASE_URL']; delete process.env['DISCORD_BOT_TOKEN']; delete process.env['TELEGRAM_BOT_TOKEN']; delete process.env['MCP_SERVERS']; delete process.env['CHAT_HARNESS_RUNTIME']; // resolveChatRuntimeMode → 'legacy' process.env['MOSAIC_STORAGE_TIER'] = 'local'; moduleRef = await Test.createTestingModule({ imports: [AppModule] }) // Storage/network side-effect adapters ONLY — never the router/chat/harness surface under test. .overrideProvider('DB_HANDLE') .useValue(fakeDbHandle) .overrideProvider('DB') .useValue(fakeDb) .overrideProvider('STORAGE_ADAPTER') .useValue(fakeStorageAdapter) .overrideProvider('AUTH') .useValue({}) .overrideProvider('BRAIN') .useValue(fakeBrain) .overrideProvider('LOG_SERVICE') .useValue({}) .overrideProvider('MEMORY') .useValue({}) .overrideProvider('MEMORY_ADAPTER') .useValue({}) .overrideProvider(ProviderService) .useValue(fakeProviderService) .compile(); // The boot itself MUST succeed cleanly — a rejection here is a bounding failure, not the red. await moduleRef.init(); }, BOOT_TIMEOUT_MS); afterAll(async () => { if (moduleRef) await moduleRef.close(); for (const key of Object.keys(process.env)) { if (!(key in envSnapshot)) delete process.env[key]; } for (const [key, value] of Object.entries(envSnapshot)) { if (value === undefined) delete process.env[key]; else process.env[key] = value; } }); // Passes TODAY: the real ChatGateway is provided by the real ChatModule and its browser-facing // surface exists. This pins that the whole-AppModule boot came up healthy, so the router failure // below is unambiguously a wiring gap and not a mis-shaped fake or an unbounded side effect. it('boots the whole AppModule and exposes the real ChatGateway with its browser-facing methods', () => { const gateway = moduleRef.get(ChatGateway, { strict: false }); expect(typeof gateway.broadcastReload).toBe('function'); expect(typeof gateway.getModelOverride).toBe('function'); expect(typeof gateway.setModelOverride).toBe('function'); expect(typeof gateway.broadcastSessionInfo).toBe('function'); }); // RED TODAY: ChatRuntimeRouter is registered in no module on this branch, so this retrieval throws // UnknownElementException — the intended red-first wiring failure. GREEN once Step Three registers // the exclusive router in the production graph, where legacy mode resolves the embedded runtime. it('resolves the exclusive ChatRuntimeRouter to the embedded runtime in legacy mode', () => { const router = moduleRef.get(ChatRuntimeRouter, { strict: false }); expect(router.active.kind).toBe('embedded'); }); });