fix(gateway): resolve InteractionCoordinationService handoff factory via optional DI token (#1145) #1167

Merged
jason.woltje merged 1 commits from fix/1145-coord-di-compiled-boot into next 2026-08-11 23:57:43 +00:00
Owner

fix(gateway): resolve InteractionCoordinationService handoff factory via optional DI token (#1145)

Fixes stack#1145 — InteractionCoordinationService fails Nest DI on compiled boot.

Root cause

The constructor's 3rd param handoffIdFactory: () => string = () => crypto.randomUUID() had a TypeScript default but no @Inject. Under emitDecoratorMetadata (tsc) — and under the gateway's vitest unplugin-swc with decoratorMetadata: true — the reflected design:paramtypes entry is Function. CoordModule lists the service as a bare provider, so Nest tries to resolve token Function, finds no provider, and throws argument Function at index [2]. Direct-construction unit tests (which pass the factory positionally) and tsx dev runs masked it.

Fix (preferred, lowest blast radius)

  • Import Optional from @nestjs/common; export HANDOFF_ID_FACTORY = Symbol('HANDOFF_ID_FACTORY').
  • Decorate the 3rd param @Optional() @Inject(HANDOFF_ID_FACTORY), keeping the = () => crypto.randomUUID() default.
  • @Inject(...) overrides the reflected Function token; @Optional() + no provider registered means Nest injects undefined, so the TS default applies → production handoff-id generation is unchanged.
  • coord.module.ts NOT modified; existing positional construction in interaction-coordination.service.test.ts NOT modified.

Files (2, path-fenced)

  • apps/gateway/src/coord/interaction-coordination.service.ts (fix)
  • apps/gateway/src/coord/interaction-coordination.di.test.ts (new regression test — boots real CoordModule via @nestjs/testing; overrides only AuthGuard to isolate the module from the app-global AuthModule)

Red-first proof (in-process; SWC emits paramtypes, so no compiled-dist needed)

FAIL src/coord/interaction-coordination.di.test.ts > CoordModule DI (compiled-metadata boot)
Nest can't resolve dependencies of the InteractionCoordinationService
(Symbol(COORDINATION_PORT), Symbol(COORDINATION_CONFIG), ?).
... the argument Function at index [2] is available in the CoordModule module.

Independent gates (author = pi ≠ verifier ≠ reviewer)

  • Integrator-verify: PASS — lineage/path-fence (exactly the 2 files; parent == base 9185b0cc); focused coord 9/9; full gateway 697 passed / 17 skipped; typecheck/lint/build rc=0. Masking re-derivation: reverted ONLY the service fix (kept the AuthGuard-override test) → test went RED again with Function at index [2] → the guard override does NOT mask the DI defect; the test is genuine.
  • Code + security review: PASS — fix mechanism correct; undefined→TS-default keeps production crypto.randomUUID(); no provider added; guard override is orthogonal to provider-constructor resolution (separate Nest code paths) and lives only in the test; no secrets, no auth bypass in production.

Traceability: issue #1145; design 18e0488d0; plan 575ad743a; base-pin SSOT 5f3ababd8. Branched off next @ 9185b0cc; disjoint from #1166 (types) so the merge into current next is conflict-free. Closes #1145.

## fix(gateway): resolve InteractionCoordinationService handoff factory via optional DI token (#1145) Fixes stack#1145 — `InteractionCoordinationService` fails Nest DI on **compiled boot**. ### Root cause The constructor's 3rd param `handoffIdFactory: () => string = () => crypto.randomUUID()` had a TypeScript default but no `@Inject`. Under `emitDecoratorMetadata` (tsc) — and under the gateway's vitest `unplugin-swc` with `decoratorMetadata: true` — the reflected `design:paramtypes` entry is `Function`. `CoordModule` lists the service as a bare provider, so Nest tries to resolve token `Function`, finds no provider, and throws `argument Function at index [2]`. Direct-construction unit tests (which pass the factory positionally) and `tsx` dev runs masked it. ### Fix (preferred, lowest blast radius) - Import `Optional` from `@nestjs/common`; export `HANDOFF_ID_FACTORY = Symbol('HANDOFF_ID_FACTORY')`. - Decorate the 3rd param `@Optional() @Inject(HANDOFF_ID_FACTORY)`, **keeping** the `= () => crypto.randomUUID()` default. - `@Inject(...)` overrides the reflected `Function` token; `@Optional()` + **no provider registered** means Nest injects `undefined`, so the TS default applies → production handoff-id generation is unchanged. - `coord.module.ts` NOT modified; existing positional construction in `interaction-coordination.service.test.ts` NOT modified. ### Files (2, path-fenced) - `apps/gateway/src/coord/interaction-coordination.service.ts` (fix) - `apps/gateway/src/coord/interaction-coordination.di.test.ts` (new regression test — boots real `CoordModule` via `@nestjs/testing`; overrides only `AuthGuard` to isolate the module from the app-global `AuthModule`) ### Red-first proof (in-process; SWC emits paramtypes, so no compiled-dist needed) ``` FAIL src/coord/interaction-coordination.di.test.ts > CoordModule DI (compiled-metadata boot) Nest can't resolve dependencies of the InteractionCoordinationService (Symbol(COORDINATION_PORT), Symbol(COORDINATION_CONFIG), ?). ... the argument Function at index [2] is available in the CoordModule module. ``` ### Independent gates (author = pi ≠ verifier ≠ reviewer) - **Integrator-verify:** PASS — lineage/path-fence (exactly the 2 files; parent == base 9185b0cc); focused coord 9/9; full gateway 697 passed / 17 skipped; typecheck/lint/build rc=0. **Masking re-derivation:** reverted ONLY the service fix (kept the AuthGuard-override test) → test went RED again with `Function at index [2]` → the guard override does NOT mask the DI defect; the test is genuine. - **Code + security review:** PASS — fix mechanism correct; `undefined`→TS-default keeps production `crypto.randomUUID()`; no provider added; guard override is orthogonal to provider-constructor resolution (separate Nest code paths) and lives only in the test; no secrets, no auth bypass in production. Traceability: issue #1145; design `18e0488d0`; plan `575ad743a`; base-pin SSOT `5f3ababd8`. Branched off `next` @ `9185b0cc`; disjoint from #1166 (types) so the merge into current `next` is conflict-free. `Closes #1145`.
jason.woltje added 1 commit 2026-08-11 23:52:57 +00:00
root cause: emitDecoratorMetadata reflected the third constructor parameter as Function and Nest attempted to resolve it

fix: optional HANDOFF_ID_FACTORY injection token, no production provider, preserving undefined -> crypto.randomUUID() default and unchanged positional construction

TDD: real CoordModule red at Function index [2], then green; test overrides only unrelated AuthGuard because its AUTH provider comes from AppModule's global AuthModule context

Closes #1145
jason.woltje merged commit fb9f9cda5a into next 2026-08-11 23:57:43 +00:00
Sign in to join this conversation.