// --------------------------------------------------------------------------- // Fixed-executor ExecutionStackMiddleware — the single-scope, boot-built // execution variant of `./execution-stack-middleware.ts`. // // The per-request `ExecutionStackMiddleware` resolves a `Principal` and then // builds a FRESH per-(user, org) executor each request via `makeExecutionStack` // -> `makeScopedExecutor`, binding `{ tenant: organizationId, subject: // accountId }`. That is the cloud / self-host model: a per-request executor // derived from identity. // // Local is structurally different: ONE executor is built once at boot over a // SINGLE tenant derived from the working directory, with // `oauthEndpointUrlPolicy: { allowHttp: true }`, and shared across every request // (and the in-process MCP). There is no per-request (user, org) binding. Forcing // local through the scoped middleware would (a) swap its cwd tenant for a // synthetic identity-derived one — orphaning existing `~/.executor` data — and // (b) silently drop `allowHttp`. // // So a host whose execution is a single boot executor supplies a // `FixedExecutionProvider` (the pre-built executor + engine) and this middleware // resolves identity to `AuthContext` exactly like the scoped variant, then // provides the FIXED executor + engine + plugin extension Services to the // handler — no per-request rebuild. The identity seam still runs (so a host can // gate or attribute requests), but the executor is constant. This is local's // genuine model expressed as a first-class `make()` execution mode, not a // special case bolted onto the scoped path. // --------------------------------------------------------------------------- import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; import { Context, Effect } from "effect"; import type * as Cause from "effect/Cause"; import type { AnyPlugin, Executor, PluginExtensions } from "@executor-js/sdk"; import type { ExecutionEngine } from "@executor-js/execution"; import { ExecutionEngineService, ExecutorService } from "../services"; import { providePluginExtensions, type PluginExtensionServices } from "../plugin-routes"; import { authContextFromPrincipal, AuthContext, isPlatformPrincipal, ReadOnlyCredential, type Principal, type ResolvedPrincipal, } from "./identity"; import type { FailureRenderingStrategy } from "./execution-stack-middleware"; /** * The pre-built, boot-scoped execution a fixed-executor host serves on. Local * builds this ONCE (single cwd scope + `allowHttp`) and shares it across every * request and the in-process MCP. `extensions` is the plugin extension map * (`executor[pluginId]`) the handlers' `*ExtensionService` Tags read. */ export interface FixedExecution { readonly executor: Executor; readonly engine: ExecutionEngine; readonly extensions: PluginExtensions; } export class FixedExecutionProvider extends Context.Service< FixedExecutionProvider, FixedExecution >()("@executor-js/api/FixedExecutionProvider") {} export interface MakeFixedExecutionMiddlewareOptions< TPlugins extends readonly AnyPlugin[], E, RLong, RStrategy, > { /** The host's plugin tuple — drives the typed extension Services and binding. */ readonly plugins: TPlugins; /** * Resolve the inbound web `Request` to a neutral `Principal`. The credential * shape stays inside this function; local's single-user provider always * resolves the one local Principal. (The seam's type admits a platform * credential, but a fixed-executor host has no platform view — one resolving * here is refused below.) */ readonly authenticate: (request: Request) => Effect.Effect; /** Render `authenticate` failures (text for local, matching self-host). */ readonly strategy: FailureRenderingStrategy; } /** * Build the fixed-executor `ExecutionStackMiddleware`. Per request: resolve the * `Principal` (and render any failure), build the `AuthContext`, then provide * the boot-built `FixedExecutionProvider`'s executor + engine + plugin extension * Services to the wrapped handler. `RCapture` is the boot-scoped context * captured once at layer-build time (the identity provider + the fixed execution * seam); the per-request body depends only on `HttpRouter`-provided context. * * Returned as the `HttpRouter.middleware` value (NOT `.layer`) so it composes * the same way the scoped variant does. */ export const makeFixedExecutionMiddleware = < const TPlugins extends readonly AnyPlugin[], E, RLong = never, RStrategy = never, RCapture = RLong | RStrategy | FixedExecutionProvider, >( options: MakeFixedExecutionMiddlewareOptions, ) => { const provideExecutorExtensions = providePluginExtensions(options.plugins); return HttpRouter.middleware<{ provides: | AuthContext | ExecutorService | ExecutionEngineService | PluginExtensionServices; }>()( Effect.gen(function* () { const captured = yield* Effect.context(); const { executor, engine, extensions } = yield* FixedExecutionProvider.asEffect(); return (httpEffect) => Effect.gen(function* () { const request = yield* HttpServerRequest.HttpServerRequest; const webRequest = yield* HttpServerRequest.toWeb(request); const resolved = yield* options.strategy.renderFailure( options.authenticate(webRequest).pipe( // The fixed executor binds ONE subject at boot; there is no // platform view to serve an org credential, so it is refused // rather than silently acting as the boot subject. Effect.filterOrFail( (principal): principal is Principal => !isPlatformPrincipal(principal), () => new ReadOnlyCredential({ code: "read_only_credential", message: "Organization API keys are not accepted on this host", }), ), ), ); // The strategy recovered the failure into a Response — return it. if (!isPrincipal(resolved)) return resolved; const auth = AuthContext.of(authContextFromPrincipal(resolved)); return yield* httpEffect.pipe( Effect.provideService(AuthContext, auth), Effect.provideService(ExecutorService, executor), Effect.provideService(ExecutionEngineService, engine), provideExecutorExtensions(extensions as PluginExtensions), ); }).pipe(Effect.provideContext(captured as Context.Context)); }), ); }; // `renderFailure` yields either the resolved `Principal` (proceed) or an // already-built `HttpServerResponse` (the strategy recovered the failure). const isPrincipal = ( value: Principal | HttpServerResponse.HttpServerResponse, ): value is Principal => !HttpServerResponse.isHttpServerResponse(value);