11// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
22
33/**
4- * ADR-0006 Phase 2 — the generic `kernelResolver` seam.
4+ * ADR-0006 — the generic `kernelResolver` seam (Phase 5 semantics) .
55 *
6- * The dispatcher gains an optional host-injected resolver that owns
7- * per-request kernel selection. It coexists with the legacy `kernelManager`
8- * path and TAKES PRECEDENCE over it for primary routing. The framework ships
9- * no multi-tenant resolver — these tests pin the seam contract (precedence +
10- * undefined → defaultKernel fallback) that cloud's resolver plugs into.
6+ * The host's resolver owns per-request ENVIRONMENT RESOLUTION + kernel
7+ * selection: the dispatcher contributes parsing hints (`context.routePath`,
8+ * `context.urlEnvironmentId`), expects the resolver to set
9+ * `context.environmentId` / `context.dataDriver`, and serves from
10+ * `defaultKernel` when the resolver returns undefined or none is registered.
11+ * The framework ships no multi-tenant resolver — strategy coverage lives in
12+ * cloud `packages/objectos-runtime/src/kernel-resolver.test.ts`.
1113 */
1214
1315import { describe , it , expect , vi } from 'vitest' ;
1416import { HttpDispatcher } from './http-dispatcher.js' ;
15- import type { KernelResolver } from './http-dispatcher.js' ;
17+ import type { KernelResolver , HttpProtocolContext } from './http-dispatcher.js' ;
1618
1719/** Minimal kernel whose objectql records which instance served the request. */
1820function makeKernel ( tag : string ) {
@@ -32,27 +34,45 @@ function makeKernel(tag: string) {
3234}
3335
3436describe ( 'HttpDispatcher — ADR-0006 kernelResolver seam' , ( ) => {
35- it ( 'prefers the injected kernelResolver over kernelManager for primary routing ' , async ( ) => {
37+ it ( 'delegates kernel selection to the resolver with parsing hints attached ' , async ( ) => {
3638 const defaultKernel = makeKernel ( 'default' ) ;
3739 const envKernel = makeKernel ( 'env' ) ;
3840
39- const resolveKernel = vi . fn ( async ( ) => envKernel ) ;
40- const kernelResolver : KernelResolver = { resolveKernel } ;
41- const getOrCreate = vi . fn ( async ( ) => envKernel ) ;
42-
41+ const resolveKernel = vi . fn ( async ( ctx : HttpProtocolContext ) => {
42+ // Phase 5 contract: the resolver sets the environment context.
43+ ctx . environmentId = 'env-from-resolver' ;
44+ return envKernel ;
45+ } ) ;
4346 const dispatcher = new HttpDispatcher ( defaultKernel , undefined , {
44- kernelResolver,
45- kernelManager : { getOrCreate } as any ,
47+ kernelResolver : { resolveKernel } ,
4648 enforceProjectMembership : false ,
4749 } ) ;
4850
49- await dispatcher . dispatch ( 'GET' , '/data/widget' , undefined , { } , { request : { } } as any ) ;
51+ const context : any = { request : { headers : { host : 'tenant.example.com' } } } ;
52+ await dispatcher . dispatch ( 'GET' , '/environments/env-123/data/widget' , undefined , { } , context ) ;
5053
51- // The seam was consulted exactly once, with (context, defaultKernel)…
5254 expect ( resolveKernel ) . toHaveBeenCalledTimes ( 1 ) ;
55+ const passed = resolveKernel . mock . calls [ 0 ] [ 0 ] as HttpProtocolContext ;
56+ // Dispatcher-provided hints (pure parsing, unvalidated):
57+ expect ( passed . routePath ) . toBe ( '/environments/env-123/data/widget' ) ;
58+ expect ( passed . urlEnvironmentId ) . toBe ( 'env-123' ) ;
59+ // Resolver-set environment context survives for downstream stages:
60+ expect ( context . environmentId ) . toBe ( 'env-from-resolver' ) ;
5361 expect ( resolveKernel . mock . calls [ 0 ] [ 1 ] ) . toBe ( defaultKernel ) ;
54- // …and it WON: the legacy kernelManager path was not used for routing.
55- expect ( getOrCreate ) . not . toHaveBeenCalled ( ) ;
62+ } ) ;
63+
64+ it ( 'does not parse /cloud/environments/:id as a scoped-path candidate' , async ( ) => {
65+ const defaultKernel = makeKernel ( 'default' ) ;
66+ const resolveKernel = vi . fn ( async ( ) => undefined ) ;
67+ const dispatcher = new HttpDispatcher ( defaultKernel , undefined , {
68+ kernelResolver : { resolveKernel } ,
69+ enforceProjectMembership : false ,
70+ } ) ;
71+
72+ const context : any = { request : { } } ;
73+ await dispatcher . dispatch ( 'GET' , '/cloud/environments/env-9' , undefined , { } , context ) ;
74+ const passed = resolveKernel . mock . calls [ 0 ] [ 0 ] as HttpProtocolContext ;
75+ expect ( passed . urlEnvironmentId ) . toBeUndefined ( ) ;
5676 } ) ;
5777
5878 it ( 'falls back to defaultKernel when the resolver returns undefined' , async ( ) => {
@@ -64,28 +84,21 @@ describe('HttpDispatcher — ADR-0006 kernelResolver seam', () => {
6484 enforceProjectMembership : false ,
6585 } ) ;
6686
67- // Should not throw — undefined routes to the single defaultKernel.
6887 const result = await dispatcher . dispatch ( 'GET' , '/data/widget' , undefined , { } , { request : { } } as any ) ;
6988 expect ( resolveKernel ) . toHaveBeenCalledTimes ( 1 ) ;
7089 expect ( result ) . toBeDefined ( ) ;
7190 } ) ;
7291
73- it ( 'uses the legacy kernelManager path when no resolver is injected (back-compat) ' , async ( ) => {
92+ it ( 'serves single-environment hosts ( no resolver) from defaultKernel without env context ' , async ( ) => {
7493 const defaultKernel = makeKernel ( 'default' ) ;
75- const envKernel = makeKernel ( 'env' ) ;
76- const getOrCreate = vi . fn ( async ( ) => envKernel ) ;
77-
7894 const dispatcher = new HttpDispatcher ( defaultKernel , undefined , {
79- kernelManager : { getOrCreate } as any ,
8095 enforceProjectMembership : false ,
8196 } ) ;
8297
83- // Force a resolved environmentId so the kernelManager branch is taken.
84- await dispatcher . dispatch ( 'GET' , '/data/widget' , undefined , { } , {
85- request : { } ,
86- environmentId : 'env_123' ,
87- } as any ) ;
88-
89- expect ( getOrCreate ) . toHaveBeenCalledWith ( 'env_123' ) ;
98+ const context : any = { request : { headers : { host : 'localhost' } } } ;
99+ const result = await dispatcher . dispatch ( 'GET' , '/data/widget' , undefined , { } , context ) ;
100+ expect ( result ) . toBeDefined ( ) ;
101+ // No resolver registered → nothing resolves an environment.
102+ expect ( context . environmentId ) . toBeUndefined ( ) ;
90103 } ) ;
91104} ) ;
0 commit comments