@@ -52,13 +52,45 @@ export interface HttpDispatcherResult {
5252 result ?: any ; // For flexible return types or direct response objects (Response/NextResponse)
5353}
5454
55+ /**
56+ * ADR-0006 generic kernel-resolution seam.
57+ *
58+ * A host (e.g. ObjectStack Cloud) injects a resolver to own per-request
59+ * kernel selection. The framework ships NO multi-tenant implementation — all
60+ * hostname→env strategy, the per-env kernel cache, and the control plane live
61+ * in the host distribution (`@objectstack/objectos-runtime`). When no resolver
62+ * is injected the dispatcher serves every request from its single
63+ * `defaultKernel` (single-environment mode).
64+ *
65+ * Returning `undefined` routes the request to `defaultKernel` — resolvers use
66+ * this for control-plane / unscoped / single-environment requests. This is the
67+ * generic contract that, in ADR-0006 Phase 5, fully replaces the dispatcher's
68+ * built-in `kernelManager` + hostname resolution; in Phase 2 it coexists with
69+ * them (it merely takes precedence for primary request routing).
70+ */
71+ export interface KernelResolver {
72+ resolveKernel (
73+ context : HttpProtocolContext ,
74+ defaultKernel : ObjectKernel ,
75+ ) : Promise < ObjectKernel | undefined > | ObjectKernel | undefined ;
76+ }
77+
5578/**
5679 * Optional configuration passed to the dispatcher constructor. Supports the
5780 * legacy `enforceProjectMembership` toggle plus the new multi-kernel
5881 * scheduling hook required by ADR-0003's cloud runtime mode.
5982 */
6083export interface HttpDispatcherOptions {
6184 enforceProjectMembership ?: boolean ;
85+ /**
86+ * Optional generic kernel-resolution seam (ADR-0006). When present it
87+ * TAKES PRECEDENCE over `kernelManager` for primary request routing: after
88+ * the dispatcher resolves `context.environmentId`, it delegates per-request
89+ * kernel selection to the resolver. Coexists with `kernelManager` (still
90+ * used by action-route service resolution) until ADR-0006 Phase 5 retires
91+ * the in-dispatcher resolution. Falls back to `resolveService('kernel-resolver')`.
92+ */
93+ kernelResolver ?: KernelResolver ;
6294 /**
6395 * Optional {@link KernelManager}. When present, the dispatcher resolves
6496 * `context.environmentId` first and then routes the request against the
@@ -88,6 +120,7 @@ export class HttpDispatcher {
88120 private envRegistry ?: any ; // EnvironmentDriverRegistry
89121 private defaultProject ?: { environmentId : string ; orgId ?: string } ;
90122 private kernelManager ?: KernelManager ;
123+ private kernelResolver ?: KernelResolver ;
91124 private scopeManager ?: EnvironmentScopeManager ;
92125 /**
93126 * When `true`, scoped data-plane routes enforce a
@@ -119,6 +152,10 @@ export class HttpDispatcher {
119152 this . envRegistry = envRegistry ?? resolveService ( 'env-registry' ) ;
120153 this . enforceMembership = options ?. enforceProjectMembership ?? true ;
121154 this . kernelManager = options ?. kernelManager ?? resolveService ( 'kernel-manager' ) ;
155+ // ADR-0006 Phase 2 seam — host-injected kernel resolver (preferred over
156+ // kernelManager for primary routing when present). Optional service so
157+ // single-environment / legacy hosts that register neither are unchanged.
158+ this . kernelResolver = options ?. kernelResolver ?? resolveService ( 'kernel-resolver' ) ;
122159 this . scopeManager = options ?. scopeManager ?? resolveService ( 'scope-manager' ) ;
123160 // Single-project default is resolved lazily on first request — the
124161 // plugin that registers it (`createSingleEnvironmentPlugin`) may run
@@ -2806,7 +2843,14 @@ export class HttpDispatcher {
28062843 // Reserved virtual id 'platform' addresses the control plane through
28072844 // the regular project URL family — never spin up a per-project kernel
28082845 // for it (there is no projects row to look up).
2809- if ( this . kernelManager && context . environmentId && context . environmentId !== 'platform' ) {
2846+ if ( this . kernelResolver ) {
2847+ // ADR-0006 Phase 2 seam: the host owns kernel selection. The
2848+ // resolver returns `defaultKernel` (or undefined) for platform /
2849+ // unscoped / single-env requests, so this branch is
2850+ // behavior-equivalent to the kernelManager path below for the
2851+ // cloud resolver (which delegates to the same KernelManager).
2852+ this . kernel = ( await this . kernelResolver . resolveKernel ( context , this . defaultKernel ) ) ?? this . defaultKernel ;
2853+ } else if ( this . kernelManager && context . environmentId && context . environmentId !== 'platform' ) {
28102854 this . kernel = await this . kernelManager . getOrCreate ( context . environmentId ) ;
28112855 } else {
28122856 this . kernel = this . defaultKernel ;
0 commit comments