theokit 0.65.0-next.1 → 0.65.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +2 -2
- package/dist/{preview-AH57XVSY.js → preview-N5H4Z56A.js} +2 -2
- package/dist/{start-AL3MJSPH.js → start-XN6EEXO2.js} +2 -2
- package/dist/start-XN6EEXO2.js.map +1 -0
- package/package.json +5 -5
- package/dist/start-AL3MJSPH.js.map +0 -1
- /package/dist/{preview-AH57XVSY.js.map → preview-N5H4Z56A.js.map} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -32,7 +32,7 @@ cli.command("build", "Build for production").option("--target <target>", "Deploy
|
|
|
32
32
|
});
|
|
33
33
|
cli.command("start", "Start production server").option("--port <port>", "Port number").action(async (options) => {
|
|
34
34
|
try {
|
|
35
|
-
const { startCommand } = await import("../start-
|
|
35
|
+
const { startCommand } = await import("../start-XN6EEXO2.js");
|
|
36
36
|
await startCommand({ port: options.port ? Number(options.port) : void 0 });
|
|
37
37
|
} catch (err) {
|
|
38
38
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -44,7 +44,7 @@ cli.command("start", "Start production server").option("--port <port>", "Port nu
|
|
|
44
44
|
});
|
|
45
45
|
cli.command("preview", "Build for production, then serve it \u2014 one step (B-030)").option("--port <port>", "Port number").option("--target <target>", "Deploy target (node, vercel, cloudflare)").action(async (options) => {
|
|
46
46
|
try {
|
|
47
|
-
const { previewCommand } = await import("../preview-
|
|
47
|
+
const { previewCommand } = await import("../preview-N5H4Z56A.js");
|
|
48
48
|
await previewCommand({
|
|
49
49
|
port: options.port ? Number(options.port) : void 0,
|
|
50
50
|
target: options.target
|
|
@@ -5,7 +5,7 @@ import "tsx/esm";
|
|
|
5
5
|
async function defaultSteps() {
|
|
6
6
|
const [{ buildCommand }, { startCommand }] = await Promise.all([
|
|
7
7
|
import("./build-ZTJOAOF2.js"),
|
|
8
|
-
import("./start-
|
|
8
|
+
import("./start-XN6EEXO2.js")
|
|
9
9
|
]);
|
|
10
10
|
return {
|
|
11
11
|
build: (opts) => buildCommand(opts),
|
|
@@ -20,4 +20,4 @@ async function previewCommand(options = {}, steps) {
|
|
|
20
20
|
export {
|
|
21
21
|
previewCommand
|
|
22
22
|
};
|
|
23
|
-
//# sourceMappingURL=preview-
|
|
23
|
+
//# sourceMappingURL=preview-N5H4Z56A.js.map
|
|
@@ -329,7 +329,7 @@ import { readFileSync } from "fs";
|
|
|
329
329
|
import { createRequire } from "module";
|
|
330
330
|
|
|
331
331
|
// src/server/agent/sdk-compat.ts
|
|
332
|
-
var SUPPORTED_SDK_RANGE = "^4.0.1";
|
|
332
|
+
var SUPPORTED_SDK_RANGE = "^4.0.1 || ^5.0.0";
|
|
333
333
|
var SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)(?:-([a-z]+)\.(\d+))?$/;
|
|
334
334
|
function parseSemver(value) {
|
|
335
335
|
const m = SEMVER_RE.exec(value);
|
|
@@ -1405,4 +1405,4 @@ export {
|
|
|
1405
1405
|
resolveSsrEntry,
|
|
1406
1406
|
startCommand
|
|
1407
1407
|
};
|
|
1408
|
-
//# sourceMappingURL=start-
|
|
1408
|
+
//# sourceMappingURL=start-XN6EEXO2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/start/index.ts","../src/server/cron/cron-runtime-node.ts","../src/server/define/health-route.ts","../src/server/rate-limit/client-ip.ts","../src/server/rate-limit/rate-limit-per-route.ts","../src/cli/commands/start/assert-sdk-compatible.ts","../src/server/agent/sdk-compat.ts","../src/cli/commands/start/bootstrap-stages.ts","../src/cli/commands/start/cron-bootstrap.ts","../src/cli/commands/start/graceful-shutdown.ts","../src/cli/commands/start/manifest-loader.ts","../src/cli/commands/start/public-exposure-gate.ts","../src/cli/commands/start/handlers.ts","../src/server/http/static.ts","../src/cli/commands/start/ssr-setup.ts","../src/cli/commands/start/request-handler.ts","../src/cli/commands/start/resolve-listen-host.ts","../src/cli/commands/start/websocket-handler.ts"],"sourcesContent":["/**\n * theokit start — production server orchestration spine.\n *\n * T4.2 (architecture-cleanup, ADR-0017): stages extracted to sibling modules.\n * - start-bootstrap-stages.ts — config/registry/storage bootstrap + resolveSsrEntry\n * - start-manifest-loader.ts — manifest.json or scan fallback\n * - start-ssr-setup.ts — SSR entry-server + HTML template split\n * - start-handlers.ts — branch handlers (action/route/static/404)\n * - start-request-handler.ts — request lifecycle wiring\n * - start-websocket-handler.ts — WS upgrade (opt-in)\n * - start-graceful-shutdown.ts — SIGTERM/SIGINT drain\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { createServer } from 'node:http'\nimport { join, resolve } from 'node:path'\n\nimport { loadConfig } from '../../../config/load-config.js'\nimport { loadEnv } from '../../../config/load-env.js'\nimport { resolvePluginSpecifiers } from '../../../config/resolve-plugin-specifiers.js'\nimport { initCacheEngineFromConfig } from '../../../server/cache-bootstrap.js'\nimport { createCronScheduler } from '../../../server/cron/cron-runtime-node.js'\nimport { defineHealthRoute } from '../../../server/define/health-route.js'\nimport { createCorsHandler } from '../../../server/http/cors.js'\nimport { createObservabilityPluginFromConfig } from '../../../server/observability-bootstrap.js'\nimport { createPluginRunnerFromConfig } from '../../../server/plugins/load-plugins.js'\nimport { createRouteRateLimiter } from '../../../server/rate-limit/rate-limit-per-route.js'\nimport { createProductionLoader } from '../../../server/scan/module-loader.js'\nimport { resolveTransformer } from '../../../server/transformer.js'\nimport { preflightNodeAndBindings } from '../../preflight-node-version.js'\nimport { CONTROLLER_MANIFEST_FILE } from '../build/emit-controllers.js'\n\nimport { assertSdkCompatible } from './assert-sdk-compatible.js'\nimport {\n configureAgentRegistryFromConfig,\n configureStorageManagerFromConfig,\n} from './bootstrap-stages.js'\nimport { loadCronDefinitions } from './cron-bootstrap.js'\nimport { installGracefulShutdown } from './graceful-shutdown.js'\nimport type { RequestHandlerCtx } from './handlers.js'\nimport { loadRoutesAndActions } from './manifest-loader.js'\nimport { assessPublicExposure } from './public-exposure-gate.js'\nimport { createRequestHandler } from './request-handler.js'\nimport { describeListenTarget, resolveListenTarget } from './resolve-listen-host.js'\nimport { setupSsr } from './ssr-setup.js'\nimport { attachWebSocketHandler } from './websocket-handler.js'\n\n// Backwards-compat: external test fixtures may import resolveSsrEntry from here.\nexport { resolveSsrEntry } from './bootstrap-stages.js'\n\ninterface StartOptions {\n port?: number\n}\n\nexport async function startCommand(options: StartOptions): Promise<void> {\n const cwd = process.cwd()\n // Preflight (FIRST — BEFORE anything that touches native bindings).\n preflightNodeAndBindings(cwd)\n loadEnv({ cwd, mode: 'production' })\n const config = await loadConfig(cwd)\n\n // M48 — fail fast if the installed @theokit/sdk is present but incompatible (before serving any\n // request). Absent SDK stays silent here (an api-only app is valid); the request path guards it lazily.\n assertSdkCompatible()\n\n await configureAgentRegistryFromConfig(config.agents?.registry)\n await configureStorageManagerFromConfig(config.storage)\n // #352 — without this, `revalidateTag` / `revalidatePath` / `updateTag` throw\n // in every application: they resolve the engine from a singleton nothing\n // initialized.\n await initCacheEngineFromConfig(config.cache)\n\n const distDir = resolve(cwd, '.theokit')\n const clientDir = resolve(distDir, 'client')\n // theokit#123 — compiled controllers, when `theokit build` emitted any.\n //\n // Keyed on the MANIFEST rather than on the directory existing: a stale `dist/controllers` left by\n // an earlier build whose sources were since deleted would otherwise keep serving routes the app\n // no longer declares. `theokit build` writes the manifest only when it compiles something, and\n // `cleanOutDir` removes both, so the manifest is the authoritative \"this build has controllers\".\n const controllersDistDir = existsSync(resolve(distDir, CONTROLLER_MANIFEST_FILE))\n ? resolve(distDir, 'controllers')\n : undefined\n // #95 — honor config `serverDir` (default \"server\") in production start, matching dev.\n const serverDir = resolve(cwd, config.serverDir)\n\n if (!existsSync(clientDir)) {\n throw new Error('No build found. Run `theo build` first.')\n }\n\n const indexHtml = readFileSync(join(clientDir, 'index.html'), 'utf-8')\n const loadModule = createProductionLoader()\n // `PORT` is what every container platform injects, and `theo start` read only\n // the config — so an image told to listen on the platform's port listened on\n // 3000 instead, and the platform's health check found nothing\n // (usetheokit/theokit#402). Explicit flag beats environment beats config: the\n // flag is a person typing now, the environment is where the process was put.\n const envPort = Number.parseInt(process.env.PORT ?? '', 10)\n const port = options.port ?? (Number.isInteger(envPort) ? envPort : undefined) ?? config.port\n // #353 — observability is registered FIRST when configured, so its span brackets\n // the user's own hooks. The honest cost of one ordered list: its `onResponse`\n // also runs first, so the span closes just before the tail of the chain. Head\n // coverage matters more — auth and rate-limit hooks live there.\n const observabilityPlugin = createObservabilityPluginFromConfig(config.observability, process.env)\n // #425 — a `plugins` entry MAY be a module specifier, so the same declaration the build bakes\n // into a deployed entry is the one this server registers. Constructed plugins pass through.\n const declaredPlugins = await resolvePluginSpecifiers(config.plugins ?? [], cwd)\n const pluginRunner = await createPluginRunnerFromConfig(\n observabilityPlugin === undefined ? declaredPlugins : [observabilityPlugin, ...declaredPlugins],\n )\n const transformer = resolveTransformer(config.serialization)\n\n const custom404Path = join(clientDir, '404.html')\n const custom500Path = join(clientDir, '500.html')\n const custom404Html = existsSync(custom404Path) ? readFileSync(custom404Path, 'utf-8') : null\n const custom500Html = existsSync(custom500Path) ? readFileSync(custom500Path, 'utf-8') : null\n\n const {\n routes: cachedRoutes,\n actions: cachedActions,\n wsRoutes: cachedWsRoutes,\n agents: cachedAgents,\n } = loadRoutesAndActions(distDir, serverDir, config.agentsDir)\n\n // `createRouteRateLimiter` accepts BOTH config shapes — it detects the legacy flat form and\n // treats it as the default bucket — so one call covers everything the schema allows.\n //\n // The previous code built a limiter only for the flat shape, on the belief that the per-route\n // variant was handled by an api-middleware path. No such path runs under `theokit start`, so a\n // per-route config produced `null` here and `handlers.ts` skipped limiting on every request. The\n // app booted clean, the config validated, and nothing was ever limited — see\n // usetheokit/theokit#321. A config that validates and then does nothing is worse than one that\n // fails loudly, because the operator has no reason to look.\n const rateLimiter = config.rateLimit ? createRouteRateLimiter(config.rateLimit) : null\n\n const ssr = await setupSsr({\n distDir,\n indexHtml,\n ssrConfigEnabled: config.ssr,\n ssrStreamingConfig: config.ssrStreaming,\n })\n\n const server = createServer(\n createRequestHandler({\n buildCtx: (req, res, requestId, startTime): RequestHandlerCtx => ({\n req,\n res,\n url: req.url ?? '/',\n requestId,\n startTime,\n clientDir,\n custom404Html,\n cachedRoutes,\n cachedActions,\n cachedAgents,\n loadModule,\n serverDir,\n projectRoot: cwd,\n controllersDistDir,\n pluginRunner,\n transformer,\n csrfMode: config.security?.csrf ?? 'strict',\n disallowed: config.security?.disallowed,\n rateLimiter,\n }),\n securityHeadersConfig: config.security?.headers ?? {},\n // #409 — built once at startup, like the security headers beside it. Declaring `cors` and\n // being served by this command used to mean no CORS at all, which reads in a browser as a\n // blocked fetch and in the config as a setting that is present and validated.\n corsHandler: config.security?.cors ? createCorsHandler(config.security.cors) : null,\n ssrRender: ssr.render,\n ssrRenderStreaming: ssr.renderStreaming,\n ssrStreamingEnabled: ssr.streamingEnabled,\n htmlHead: ssr.htmlHead,\n htmlTail: ssr.htmlTail,\n indexHtml,\n custom500Html,\n // M7-2: serve a built-in liveness route on the Node listener. Readiness\n // probe wiring from theo.config.ts is a documented follow-up (see the M7\n // implementation summary § Scope note).\n reservedRoutes: { health: defineHealthRoute() },\n }),\n )\n\n await attachWebSocketHandler(server, cachedWsRoutes, loadModule)\n\n // theokit#324: `theokit build --target node` announces an in-process\n // scheduler here. Drive it, or the announcement is false.\n const cronDefinitions = await loadCronDefinitions(resolve(distDir, 'crons.json'), cwd, loadModule)\n if (cronDefinitions.length > 0) {\n createCronScheduler(cronDefinitions).start()\n }\n\n // `config.host` was never passed here, and `listen(port)` with no address binds\n // every interface — so the server listened wider than its own configuration,\n // whose default says `localhost`. Passing it broke containers, where `localhost`\n // means nobody, so `HOST` now gets a say (usetheokit/theokit#402).\n const listenTarget = resolveListenTarget(config.host)\n\n // Deciding WHERE to listen settled reachability; this settles consequence. The refusal happens\n // BEFORE `listen` because a server that binds and then complains has already accepted the first\n // request — the log entry would arrive after the exposure it describes.\n const exposure = assessPublicExposure({\n routes: cachedRoutes,\n target: listenTarget,\n allowUnauthenticatedWrites: config.security?.allowUnauthenticatedWrites ?? false,\n // `cachedRoutes` comes from the file-route scan and never describes controllers. Without this\n // the gate reads an empty table as \"nothing is exposed\" and binds a public interface in silence,\n // while a controller marked `theokit:public` on a POST sits on it (theokit#543).\n hasControllers: controllersDistDir !== undefined,\n })\n if (exposure.kind === 'refused') {\n console.error(`\\n ${exposure.message}\\n`)\n // Non-zero: an orchestrator restarting this container must see a failure, not a clean exit that\n // reads as \"the process decided to stop\" (docs/adr/0002 — an abnormal ending is never reported\n // as normal).\n process.exitCode = 1\n return\n }\n if (exposure.kind === 'unverified') {\n console.warn(`\\n ${exposure.message}\\n`)\n }\n\n server.listen(port, listenTarget.host, () => {\n console.log(`\\n Theo production server`)\n // The line states the bound address, because it used to print `localhost`\n // either way — so a container serving everyone and one serving nobody were\n // indistinguishable in the log.\n console.log(`${describeListenTarget(listenTarget, port)}\\n`)\n if (exposure.kind === 'allowed-by-override') {\n // The override permits the exposure; it does not make it quiet. Each start names what is\n // open, so `allowUnauthenticatedWrites: true` cannot be forgotten in a config nobody reopens.\n const n = exposure.exposures.length\n console.warn(\n ` security.allowUnauthenticatedWrites is on — ${String(n)} unauthenticated write ${n === 1 ? 'route is' : 'routes are'} reachable:`,\n )\n for (const e of exposure.exposures) console.warn(` ${e.method} ${e.routePath}`)\n console.warn('')\n }\n if (cronDefinitions.length > 0) {\n console.log(` Crons: ${String(cronDefinitions.length)} scheduled in-process\\n`)\n }\n })\n\n installGracefulShutdown(server)\n}\n","import { CronExpressionParser } from 'cron-parser'\n\nimport { generateNewTraceContext } from '../observability/trace-context-propagation.js'\n\nimport type { CronContext, CronDefinition } from './cron-types.js'\n\n/**\n * In-memory cron scheduler for `theokit dev` (T1.4).\n *\n * Algorithm:\n * - For each cron, compute `nextFireAt = cron-parser.next()`.\n * - Schedule a `setTimeout(handler, nextFireAt - now)`.\n * - After handler invocation (sync return or Promise scheduled), recompute\n * next fire from CURRENT time (drift-free vs scheduled time).\n *\n * Per-cron isolation (EC-109):\n * - Each cron's handler invocation is fire-and-forget (`void` scheduled).\n * A hanging handler does NOT block the scheduler loop nor other crons.\n * - `concurrency: 'forbid'` (default) tracks an in-flight flag per-cron;\n * subsequent ticks skip + warn while the in-flight flag is set.\n * - `concurrency: 'allow'` runs handlers concurrently — caller's responsibility.\n *\n * Production deploys use platform-native triggers (T1.5 adapter translators);\n * this scheduler exists only for local dev iteration.\n */\n\nexport interface CronScheduler {\n start(): void\n stop(): void\n}\n\ninterface CronJobState {\n readonly def: CronDefinition\n inFlight: boolean\n timer: NodeJS.Timeout | null\n abortController: AbortController | null\n}\n\nexport function createCronScheduler(definitions: readonly CronDefinition[]): CronScheduler {\n const states: CronJobState[] = definitions.map((def) => ({\n def,\n inFlight: false,\n timer: null,\n abortController: null,\n }))\n\n let started = false\n\n const fireAndReschedule = (state: CronJobState, scheduledAt: Date): void => {\n state.timer = null\n\n if (state.inFlight && state.def.concurrency === 'forbid') {\n console.warn(\n `[theokit:cron] \"${state.def.name}\" skipped tick at ${scheduledAt.toISOString()} ` +\n '— previous handler still running (concurrency: forbid).',\n )\n scheduleNext(state)\n return\n }\n\n state.inFlight = true\n state.abortController = new AbortController()\n const traceCtx = generateNewTraceContext()\n const ctx: CronContext = {\n traceId: traceCtx.trace_id,\n scheduledAt,\n signal: state.abortController.signal,\n }\n\n // Fire-and-forget: EC-109 — never await here so a hanging handler\n // can't block the scheduler loop or other crons.\n void Promise.resolve()\n .then(() => state.def.handler(ctx))\n .catch((err: unknown) => {\n console.error(\n `[theokit:cron] \"${state.def.name}\" handler error:`,\n err instanceof Error ? err.message : err,\n )\n })\n .finally(() => {\n state.inFlight = false\n })\n\n scheduleNext(state)\n }\n\n const scheduleNext = (state: CronJobState): void => {\n if (!started) return\n const interval = CronExpressionParser.parse(state.def.schedule, {\n tz: 'UTC',\n currentDate: new Date(),\n })\n const next = interval.next().toDate()\n const delayMs = Math.max(0, next.getTime() - Date.now())\n state.timer = setTimeout(() => {\n fireAndReschedule(state, next)\n }, delayMs)\n // setTimeout returns a Timeout object; in Node, .unref() is available but\n // we DO want this to keep the event loop alive in dev — explicit no-unref.\n }\n\n return {\n start(): void {\n if (started) return\n started = true\n for (const state of states) {\n scheduleNext(state)\n }\n },\n stop(): void {\n started = false\n for (const state of states) {\n if (state.timer) {\n clearTimeout(state.timer)\n state.timer = null\n }\n state.abortController?.abort()\n state.abortController = null\n }\n },\n }\n}\n","/**\n * M7-2 — health/ready reserved routes for the convention/filesystem-route\n * server. Liveness (`/__theo/health`, always 200) and readiness\n * (`/__theo/ready`, 200/503 from a probe) are registered on a reserved\n * namespace BEFORE the user-route catch-all + 404 branch — mirroring nitro's\n * `/_nitro/*` reserved-namespace pattern (knowledge-base/references/nitro/src/\n * runtime/internal/routes/dev-tasks.ts). Liveness and readiness are kept\n * separate by design: liveness says \"the process is up\", readiness says\n * \"dependencies are up\".\n *\n * @public\n */\n\n/** Reserved path for the liveness endpoint. */\nexport const HEALTH_PATH = '/__theo/health'\n/** Reserved path for the readiness endpoint. */\nexport const READY_PATH = '/__theo/ready'\n\n/** Config produced by {@link defineHealthRoute}. */\nexport interface HealthRouteConfig {\n readonly kind: 'health'\n /** Returns the liveness body (auto-serialized to JSON). Default: `{ status: 'ok' }`. */\n readonly handler: () => unknown\n}\n\n/** Config produced by {@link defineReadyRoute}. */\nexport interface ReadyRouteConfig {\n readonly kind: 'ready'\n /** Resolves `true` when dependencies are ready. A throw/reject is treated as not-ready (503). */\n readonly probe: () => boolean | Promise<boolean>\n}\n\n/** The reserved-route registry consulted by {@link serveReservedRoute}. */\nexport interface ReservedRoutes {\n readonly health?: HealthRouteConfig\n readonly ready?: ReadyRouteConfig\n}\n\n/** A reserved-route response: an HTTP status + a JSON-serializable body. */\nexport interface ReservedResponse {\n readonly status: number\n readonly body: unknown\n}\n\n/**\n * Define the liveness route. The default handler returns `{ status: 'ok' }`.\n * Liveness is always 200 — it asserts the process is up, not that dependencies are.\n */\nexport function defineHealthRoute(\n handler: () => unknown = () => ({ status: 'ok' }),\n): HealthRouteConfig {\n return { kind: 'health', handler }\n}\n\n/**\n * Define the readiness route. The `probe` decides 200 (ready) vs 503 (not-ready).\n * A probe that throws/rejects is treated as not-ready (503) — never a 500 crash.\n */\nexport function defineReadyRoute(probe: () => boolean | Promise<boolean>): ReadyRouteConfig {\n return { kind: 'ready', probe }\n}\n\n/**\n * Pure dispatcher: map a request pathname to a reserved-route response, or\n * `null` when the path is not reserved (so the user catch-all + 404 take over).\n * Liveness defaults to 200 `{status:'ok'}`; readiness without a probe is\n * trivially ready (200). Registered BEFORE the user catch-all by the caller.\n */\nexport async function serveReservedRoute(\n pathname: string,\n routes: ReservedRoutes = {},\n): Promise<ReservedResponse | null> {\n if (pathname === HEALTH_PATH) {\n const handler = routes.health?.handler ?? (() => ({ status: 'ok' }))\n return { status: 200, body: handler() }\n }\n if (pathname === READY_PATH) {\n const probe = routes.ready?.probe\n if (probe === undefined) {\n return { status: 200, body: { status: 'ready' } }\n }\n try {\n const ready = await probe()\n return ready\n ? { status: 200, body: { status: 'ready' } }\n : { status: 503, body: { status: 'not-ready' } }\n } catch (err) {\n // 503 (the dependency is not ready) but NEVER swallow the cause — a\n // readiness probe failing silently is invisible to ops (project rule:\n // \"NEVER swallow exceptions\"). Log the cause, keep the 503 contract.\n console.error('[theo] readiness probe threw — treating as not-ready', err)\n return { status: 503, body: { status: 'not-ready' } }\n }\n }\n return null\n}\n","import type { IncomingMessage } from 'node:http'\n\n/**\n * Resolves the address a rate-limit bucket should be keyed on.\n *\n * ## Why this is not just `req.socket.remoteAddress`\n *\n * Behind any reverse proxy — Caddy, nginx, a load balancer, an ingress controller — the socket\n * address is the PROXY's, identical for every visitor on the internet. Keying buckets on it gives\n * the whole world one shared budget, so the first few requests each minute exhaust it and everyone\n * else is refused. That is worse than no limiting: a limit meant to stop one abusive client becomes\n * a denial of service any single client can trigger for everybody.\n *\n * ## Why it is opt-in\n *\n * `x-forwarded-for` is a request header, and a request header is whatever the client typed. Reading\n * it without being told to would let anyone bypass the limiter by rotating a forged value — a\n * one-line `curl -H`. So the default is to trust nothing and use the socket.\n *\n * ## Why the RIGHTMOST entry\n *\n * Each proxy APPENDS the address that connected to it. A client that sends `x-forwarded-for: 1.2.3.4`\n * through one proxy produces `1.2.3.4, <real client>` — the forgery lands on the left, and the entry\n * the trusted proxy wrote is last. Counting from the right therefore skips exactly the hops the\n * operator vouched for, and everything a client can influence stays to the left of where we look.\n *\n * This mirrors the `'trusted-proxy'` policy in `adapters/web-shim.ts`, which documents the same rule\n * for the Web-Request runtimes.\n */\n\n/**\n * How many proxies sit in front of the app.\n *\n * `false` (the default) trusts nothing and uses the socket address. `true` means one trusted proxy.\n * A number names the hop count for a longer chain — a CDN in front of your own proxy is `2`.\n */\nexport type TrustProxy = boolean | number\n\n/** `true` is shorthand for a single proxy; `false` for none. A number passes through. */\nfunction hopCount(trustProxy: TrustProxy): number {\n if (trustProxy === true) return 1\n if (trustProxy === false) return 0\n return trustProxy\n}\n\n/** Reads a header that Node may hand back as an array. */\nfunction headerValue(req: IncomingMessage, name: string): string | undefined {\n const raw = req.headers[name]\n if (Array.isArray(raw)) return raw[raw.length - 1]\n return raw\n}\n\n/**\n * The forwarded-header half of the decision, with no runtime in it.\n *\n * Extracted (usetheokit/theokit#612) so the Node path and the Web path cannot drift: a `Request`\n * has the same headers and no socket, and the arithmetic on those headers is the part that is easy\n * to get subtly wrong twice. What the two runtimes genuinely disagree about — where the fallback\n * address comes from — stays with each caller.\n *\n * @param header the raw `x-forwarded-for` value, if present\n * @param realIpHeader the raw `x-real-ip` value, if present\n * @param trustProxy how many proxies the operator vouched for\n * @returns the address to key on, or `undefined` when no trusted entry could be read\n *\n * Module-private: the two exported resolvers below are the whole surface, and a caller holding\n * the raw header arithmetic would be re-deciding the part that is easy to get wrong.\n */\nfunction addressFromForwardedHeaders(\n header: string | undefined,\n realIpHeader: string | undefined,\n trustProxy: TrustProxy,\n): string | undefined {\n const hops = hopCount(trustProxy)\n if (hops < 1) return undefined\n\n if (header) {\n const entries = header\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean)\n\n // Count in from the right by the number of hops the operator vouched for. A chain shorter than\n // configured means a request did NOT come through the expected proxies — a direct hit on the\n // app's port, say — so we report nothing rather than reading an entry the client could have\n // written.\n const index = entries.length - hops\n if (index >= 0 && index < entries.length) return entries[index]\n }\n\n // `x-real-ip` carries a single address and is written by the proxy, not appended to, so there is\n // no hop arithmetic to do. Only consulted when a proxy is trusted at all.\n const realIp = realIpHeader?.trim()\n return realIp === undefined || realIp === '' ? undefined : realIp\n}\n\nexport function resolveClientIp(req: IncomingMessage, trustProxy: TrustProxy = false): string {\n // `req.socket` is typed as always-present in Node typings; the optional chain keeps the fallback\n // reachable for test doubles built as plain object literals.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive for test doubles\n const socketAddress = req.socket?.remoteAddress ?? 'unknown'\n\n return (\n addressFromForwardedHeaders(\n headerValue(req, 'x-forwarded-for'),\n headerValue(req, 'x-real-ip'),\n trustProxy,\n ) ?? socketAddress\n )\n}\n\n/**\n * The Web-`Request` twin of {@link resolveClientIp}.\n *\n * There is no socket to fall back to, so the answer is `undefined` when no trusted proxy wrote an\n * address. A caller MUST treat that as \"cannot identify\" rather than substituting a constant:\n * bucketing every visitor under one placeholder turns a rate limiter into a shared budget the first\n * caller each window exhausts for the whole internet.\n */\nexport function resolveClientIpFromRequest(\n request: Request,\n trustProxy: TrustProxy = false,\n): string | undefined {\n return addressFromForwardedHeaders(\n request.headers.get('x-forwarded-for') ?? undefined,\n request.headers.get('x-real-ip') ?? undefined,\n trustProxy,\n )\n}\n","// T5a.1d — Web Crypto migration. The last node:crypto consumer in server/.\n// `createHash('sha256')` is sync but Web Crypto's `subtle.digest('SHA-256', ...)`\n// is async. This propagates through hashFragment → deriveKey → the factory's\n// returned checker, which is why the checker is async. `theokit start` awaits it\n// (usetheokit/theokit#321 — before that fix this factory had no production consumer at all, and a\n// per-route config silently disabled rate limiting outright). IncomingMessage stays as a type-only\n// import (TS-erased; runtime-clean).\nimport type { IncomingMessage } from 'node:http'\n\nimport { parseCookieHeader } from '../http/cookies.js'\n\nimport { resolveClientIp, type TrustProxy } from './client-ip.js'\nimport { InMemoryStore, type RateLimitStore } from './rate-limit-store.js'\nimport type { RateLimitConfig, RateLimitResult } from './rate-limit.js'\n\n/**\n * T2.2 — Per-route + per-user rate limiting.\n *\n * Layered on top of `rate-limit-store.ts`. The route map allows\n * declarative policies (\"strict /api/login, loose everything else\")\n * driven by config, not handler-decorated. `keyBy` selects what\n * identifier the limiter buckets on.\n *\n * ADR D2: per-route via path matching, NOT per-handler decorator.\n * Operators can tune policies without touching route definitions.\n */\n\nexport type KeyByMode = 'ip' | 'session' | 'user' | ((req: IncomingMessage) => string)\n\nexport interface RouteRateLimitConfig {\n /** Fallback config used when no per-route entry matches. */\n default?: RateLimitConfig\n /** Map of path pattern → config. Exact-string keys (RegExp via API). */\n routes?: Record<string, RateLimitConfig>\n /** Same as `routes` but each entry is a [pattern, config] tuple, RegExp allowed. */\n routePatterns?: readonly [string | RegExp, RateLimitConfig][]\n /** Bucket identifier strategy. Default 'ip'. */\n keyBy?: KeyByMode\n /** Cookie name used by keyBy='session'. Defaults to 'theo_session'. */\n cookieName?: string\n /** Optional shared store (for multi-route correlation). Default per-limiter InMemoryStore. */\n store?: RateLimitStore\n /**\n * How many reverse proxies sit in front of the app, for `keyBy: 'ip'`. Default `false` — trust\n * none and key on the socket address.\n *\n * Set this whenever the app is behind Caddy, nginx, a load balancer or an ingress controller:\n * without it every visitor keys on the proxy's address and shares one bucket, so a handful of\n * requests exhausts the budget for the entire internet. With it, the client address is read from\n * `x-forwarded-for` counting in from the right, past exactly the hops declared here.\n *\n * Leaving it off by default is deliberate — `x-forwarded-for` is client-writable, and honouring it\n * uninvited turns the limiter into a one-header bypass. See `client-ip.ts`.\n */\n trustProxy?: TrustProxy\n}\n\n/**\n * Normalize a path for matching: strip query string, drop trailing slash\n * unless root. EC-5: `/api/login` and `/api/login/` collapse to the same\n * canonical form so attackers can't bypass strict limits.\n */\nfunction normalizePath(input: string): string {\n const noQuery = input.split('?')[0]\n if (noQuery.length > 1 && noQuery.endsWith('/')) return noQuery.slice(0, -1)\n return noQuery\n}\n\n/**\n * Test whether `path` matches `pattern`. String patterns are compared\n * after trailing-slash normalization (EC-5). RegExp uses `.test` after\n * resetting `lastIndex` (defensive against `/g` flag).\n */\nexport function matchRoutePattern(path: string, pattern: string | RegExp): boolean {\n const canonical = normalizePath(path)\n if (typeof pattern === 'string') {\n return canonical === normalizePath(pattern)\n }\n pattern.lastIndex = 0\n return pattern.test(canonical)\n}\n\n/**\n * Hash a string with SHA-256 and return the first 16 base64url chars.\n * Used by `keyBy='session'` so the raw cookie value never lands in a\n * rate-limit key (which may flow into audit logs).\n *\n * T5a.1d — async via Web Crypto subtle.digest (no node:crypto). The\n * base64url encoding is done manually because btoa+url-safe transform is\n * available everywhere but `digest('base64url')` is Node-only.\n */\nasync function hashFragment(input: string): Promise<string> {\n const buf = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(input))\n const bytes = new Uint8Array(buf)\n let bin = ''\n for (const b of bytes) bin += String.fromCharCode(b)\n // eslint-disable-next-line sonarjs/slow-regex -- input is fixed-length 44 chars (SHA-256 base64), trailing '=' padding ≤ 2 chars, no ReDoS surface\n const b64url = btoa(bin).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n return b64url.slice(0, 16)\n}\n\n// T3.2 DRY consolidation: cookie parsing moved to ../http/cookies.ts.\n// The canonical `parseCookieHeader` returns a Map for O(1) lookup; this\n// wrapper preserves the original `readCookie(req, name)` signature so the\n// rest of the file stays untouched.\nfunction readCookie(req: IncomingMessage, name: string): string | undefined {\n return parseCookieHeader(req.headers.cookie ?? undefined).get(name)\n}\n\n/**\n * Build the rate-limit bucket key for the request based on `keyBy`.\n *\n * EC-6: session mode reads the configured `cookieName`. With the wrong\n * cookie name (e.g., default 'theo_session' but app uses 'app_session'),\n * we fall back to IP so anonymous users still get rate-limited rather\n * than sharing an empty bucket.\n */\nexport async function deriveKey(\n req: IncomingMessage,\n keyBy: KeyByMode,\n cookieName: string,\n trustProxy: TrustProxy = false,\n): Promise<string> {\n if (typeof keyBy === 'function') return keyBy(req)\n // Behind a proxy the socket address is the proxy's, the same for every visitor. `resolveClientIp`\n // returns it unchanged unless the operator declared how many proxies to trust — see its comment\n // for why reading `x-forwarded-for` uninvited would hand out a one-header bypass.\n const ip = resolveClientIp(req, trustProxy)\n switch (keyBy) {\n case 'session': {\n const cookie = readCookie(req, cookieName)\n return cookie ? `session:${await hashFragment(cookie)}` : `ip:${ip}`\n }\n case 'user': {\n const userId = (req as unknown as { user?: { id?: string } }).user?.id\n return userId ? `user:${userId}` : `ip:${ip}`\n }\n case 'ip':\n default:\n return `ip:${ip}`\n }\n}\n\n/**\n * Per-route rate limiter factory. Returns a sync checker compatible with\n * the existing api-middleware shape.\n *\n * Backwards-compatibility (ADR D2): a flat `{ windowMs, max }` config is\n * accepted and treated as `default` (no per-route variants).\n */\nexport function createRouteRateLimiter(config: RouteRateLimitConfig | RateLimitConfig) {\n // Detect legacy flat shape\n const isFlat =\n 'windowMs' in config && 'max' in config && !('default' in config) && !('routes' in config)\n const cfg: RouteRateLimitConfig = isFlat ? { default: config } : config\n\n const store = cfg.store ?? new InMemoryStore()\n // CR-005: validate store shape ONCE at construction. The previous\n // implementation ran `instanceof InMemoryStore` on every request and\n // threw at request-time if a non-InMemoryStore was passed — which\n // turned a clear config error into a runtime 500 on the first request.\n if (!(store instanceof InMemoryStore)) {\n throw new Error(\n 'createRouteRateLimiter: async RateLimitStore implementations require a dedicated async middleware path. ' +\n 'Use the InMemoryStore default for the sync facade.',\n )\n }\n const inMemoryStore = store\n const keyBy = cfg.keyBy ?? 'ip'\n const cookieName = cfg.cookieName ?? 'theo_session'\n const trustProxy = cfg.trustProxy ?? false\n\n // Build a pre-compiled list of (pattern, config) tuples for matching.\n const patternList: [string | RegExp, RateLimitConfig][] = []\n if (cfg.routes) {\n for (const [pattern, c] of Object.entries(cfg.routes)) patternList.push([pattern, c])\n }\n if (cfg.routePatterns) {\n for (const tuple of cfg.routePatterns) patternList.push(tuple)\n }\n\n return async function checkRouteRateLimit(req: IncomingMessage): Promise<RateLimitResult> {\n const url = req.url ?? ''\n let matched: RateLimitConfig | undefined\n for (const [pattern, c] of patternList) {\n if (matchRoutePattern(url, pattern)) {\n matched = c\n break\n }\n }\n const effective = matched ?? cfg.default\n if (!effective) {\n // No route match + no default → not limited.\n return { limited: false, headers: {} }\n }\n\n // Bucket key includes normalized path so /api/login and /api/login/\n // collapse to the same bucket (EC-5).\n const bucketSuffix = typeof matched === 'undefined' ? '*default*' : normalizePath(url)\n const key = `${await deriveKey(req, keyBy, cookieName, trustProxy)}|${bucketSuffix}`\n const state = inMemoryStore.incrSync(key, effective.windowMs)\n\n if (state.count > effective.max) {\n const retryAfter = Math.ceil((state.resetAt - Date.now()) / 1000)\n return {\n limited: true,\n headers: {\n 'X-RateLimit-Limit': String(effective.max),\n 'X-RateLimit-Remaining': '0',\n 'Retry-After': String(retryAfter),\n },\n }\n }\n return {\n limited: false,\n headers: {\n 'X-RateLimit-Limit': String(effective.max),\n 'X-RateLimit-Remaining': String(Math.max(0, effective.max - state.count)),\n },\n }\n }\n}\n\n/**\n * T5a.2 Phase D slice 1/3 — Web-Standards rate-limiter inputs context.\n *\n * Web `Request` has no equivalent of `req.socket.remoteAddress` (Node\n * runtime concept) or `req.user` (set by upstream middleware). The\n * Web-shaped rate-limiter requires the caller to pass these explicitly:\n *\n * - `clientIp` — resolved per-runtime (Node: `socket.remoteAddress`;\n * CF Workers: `request.headers.get('cf-connecting-ip')`; Vercel:\n * `x-forwarded-for` first hop; Bun/Deno: adapter-specific).\n * - `userId` — resolved by auth middleware (Phase D slice 3/3 ships\n * the Web-shaped session helper).\n *\n * Defaults: `clientIp = 'unknown'`, `userId = undefined` (matches the\n * IncomingMessage path's fallback semantics).\n */\nexport interface DeriveKeyRequestContext {\n clientIp?: string\n userId?: string\n}\n\n/**\n * T5a.2 Phase D slice 1/3 — Web-Standards-shaped key derivation.\n *\n * Mirror of `deriveKey(req: IncomingMessage, keyBy, cookieName)` for the\n * Web `Request` shape. Same `'ip' | 'session' | 'user'` enum cases (the\n * `function` callback case is IncomingMessage-only because the existing\n * `KeyByMode` callback type is Node-shaped; Web callers use the enum\n * cases or call a future `KeyByModeWeb` shape — out of T5a.2 Phase D scope).\n *\n * Uses `getCookieFromRequest` (Phase B slice 6/6) for session-mode cookie\n * lookup. Cookie parsing has the same CR-009 percent-encoding safety.\n */\nexport async function deriveKeyFromRequest(\n request: Request,\n keyBy: Exclude<KeyByMode, (req: IncomingMessage) => string>,\n cookieName: string,\n ctx: DeriveKeyRequestContext = {},\n): Promise<string> {\n const ip = ctx.clientIp ?? 'unknown'\n switch (keyBy) {\n case 'session': {\n // Reuse the Web cookie helper extracted in Phase B slice 6/6 so\n // CR-009 percent-encoding sanity stays consistent across paths.\n const { getCookieFromRequest } = await import('../http/cookies.js')\n const cookie = getCookieFromRequest(request, cookieName)\n return cookie ? `session:${await hashFragment(cookie)}` : `ip:${ip}`\n }\n case 'user': {\n return ctx.userId ? `user:${ctx.userId}` : `ip:${ip}`\n }\n case 'ip':\n default:\n return `ip:${ip}`\n }\n}\n\n/**\n * T5a.2 Phase D slice 1/3 — Web-Standards rate-limiter factory.\n *\n * Mirror of `createRouteRateLimiter(config)` returning a checker that\n * accepts `(request: Request, ctx?: DeriveKeyRequestContext)` instead of\n * `(req: IncomingMessage)`. Same `RouteRateLimitConfig` accepted; same\n * `keyBy` enum cases; same `InMemoryStore` constraint (CR-005 guard).\n *\n * Same returned `RateLimitResult` shape (headers + limited boolean).\n *\n * Web `Request` has no `req.url` path-only property — uses\n * `new URL(request.url).pathname + search` to derive the URL the way the\n * IncomingMessage path's `req.url ?? ''` would.\n */\nexport function createRouteRateLimiterWeb(config: RouteRateLimitConfig | RateLimitConfig) {\n const isFlat =\n 'windowMs' in config && 'max' in config && !('default' in config) && !('routes' in config)\n const cfg: RouteRateLimitConfig = isFlat ? { default: config } : config\n\n const store = cfg.store ?? new InMemoryStore()\n if (!(store instanceof InMemoryStore)) {\n throw new Error(\n 'createRouteRateLimiterWeb: async RateLimitStore implementations require a dedicated async middleware path. ' +\n 'Use the InMemoryStore default for the Web facade.',\n )\n }\n const inMemoryStore = store\n const keyBy = (cfg.keyBy ?? 'ip') as Exclude<KeyByMode, (req: IncomingMessage) => string>\n const cookieName = cfg.cookieName ?? 'theo_session'\n\n const patternList: [string | RegExp, RateLimitConfig][] = []\n if (cfg.routes) {\n for (const [pattern, c] of Object.entries(cfg.routes)) patternList.push([pattern, c])\n }\n if (cfg.routePatterns) {\n for (const tuple of cfg.routePatterns) patternList.push(tuple)\n }\n\n return async function checkRouteRateLimitWeb(\n request: Request,\n ctx: DeriveKeyRequestContext = {},\n ): Promise<RateLimitResult> {\n // Web Request guarantees absolute URL — extract path+query for pattern\n // matching (mirror of IncomingMessage's `req.url ?? ''`).\n const parsed = new URL(request.url)\n const url = `${parsed.pathname}${parsed.search}`\n let matched: RateLimitConfig | undefined\n for (const [pattern, c] of patternList) {\n if (matchRoutePattern(url, pattern)) {\n matched = c\n break\n }\n }\n const effective = matched ?? cfg.default\n if (!effective) {\n return { limited: false, headers: {} }\n }\n\n const bucketSuffix = typeof matched === 'undefined' ? '*default*' : normalizePath(url)\n const key = `${await deriveKeyFromRequest(request, keyBy, cookieName, ctx)}|${bucketSuffix}`\n const state = inMemoryStore.incrSync(key, effective.windowMs)\n\n if (state.count > effective.max) {\n const retryAfter = Math.ceil((state.resetAt - Date.now()) / 1000)\n return {\n limited: true,\n headers: {\n 'X-RateLimit-Limit': String(effective.max),\n 'X-RateLimit-Remaining': '0',\n 'Retry-After': String(retryAfter),\n },\n }\n }\n return {\n limited: false,\n headers: {\n 'X-RateLimit-Limit': String(effective.max),\n 'X-RateLimit-Remaining': String(Math.max(0, effective.max - state.count)),\n },\n }\n }\n}\n","/**\n * M48 (ecosystem-integration-guarantee) T3.2 — boot-time SDK compatibility fail-fast.\n *\n * The agent runtime is `@theokit/sdk` (G2). Today a missing / mis-versioned SDK is invisible until\n * the FIRST agent request, where `sdk-adapter.ts` yields a lazy `SDK_NOT_INSTALLED` stream event.\n * This runs at `theokit start` boot instead: if the installed SDK is present but outside the\n * supported range it throws a typed error naming found-vs-required; if it is absent (a legitimately\n * api-only app — the SDK is an OPTIONAL peer) it stays silent unless the caller marks it required.\n *\n * CLI/adapter layer → `node:*` is allowed here (unlike `server/`). The version range check itself is\n * the pure `server/agent/sdk-compat.ts` helper (one source of truth with the contract-test drift guard).\n */\nimport { readFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\n\nimport { SUPPORTED_SDK_RANGE, satisfiesSdkRange } from '../../../server/agent/sdk-compat.js'\n\n/** Typed error surfaced at boot when the installed `@theokit/sdk` is incompatible or absent-but-required. */\nexport class SdkIncompatibleError extends Error {\n readonly code = 'SDK_INCOMPATIBLE' as const\n readonly found: string | undefined\n readonly required: string\n constructor(found: string | undefined, required: string) {\n super(\n found === undefined\n ? `@theokit/sdk is required but not installed — run: pnpm add @theokit/sdk@${required}`\n : `@theokit/sdk ${found} does not satisfy the required range ${required} — run: pnpm add @theokit/sdk@${required}`,\n )\n this.name = 'SdkIncompatibleError'\n this.found = found\n this.required = required\n }\n}\n\n/** Resolve the version of the ACTUALLY-installed `@theokit/sdk`, or `undefined` when absent. */\nfunction defaultResolveSdkVersion(): string | undefined {\n try {\n const require = createRequire(import.meta.url)\n const pkgPath = require.resolve('@theokit/sdk/package.json')\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as { version?: string }\n return typeof pkg.version === 'string' ? pkg.version : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Fail fast at boot when the installed `@theokit/sdk` is incompatible.\n *\n * - present + in range → returns (silent)\n * - present + out of range → throws {@link SdkIncompatibleError} (found + required)\n * - absent + `required !== true` → returns (api-only app; the request path still guards lazily)\n * - absent + `required === true` → throws {@link SdkIncompatibleError}\n *\n * `resolveVersion` is injectable for tests (DIP); production uses {@link defaultResolveSdkVersion}.\n */\nexport function assertSdkCompatible(opts?: {\n required?: boolean\n resolveVersion?: () => string | undefined\n}): void {\n const version = (opts?.resolveVersion ?? defaultResolveSdkVersion)()\n if (version === undefined) {\n if (opts?.required === true) throw new SdkIncompatibleError(undefined, SUPPORTED_SDK_RANGE)\n return\n }\n if (!satisfiesSdkRange(version, SUPPORTED_SDK_RANGE)) {\n throw new SdkIncompatibleError(version, SUPPORTED_SDK_RANGE)\n }\n}\n","/**\n * M48 (ecosystem-integration-guarantee) — the supported `@theokit/sdk` version range + a pure,\n * dependency-free `||`-aware caret semver check. Shared by the contract test's version-drift guard\n * (`tests/integration/contract-sdk-seam.test.ts`) and the boot-time fail-fast\n * (`cli/commands/start/assert-sdk-compatible.ts`) — one source of truth for \"which SDK theokit runs on\".\n *\n * Web-Standards discipline (G8/R3a): `server/` code uses no `node:*`. This is pure string logic.\n * Rule 9 (Don't Reinvent) trade-off (ADR D1): a small inline caret checker instead of a `semver`\n * dependency, matching the theo-ui seam's existing precedent (`contract-usetheo-ui-vite-plugin.test.ts`).\n */\n\n/** The single source of truth for the supported SDK range. Mirrors the `package.json` peer floor. */\nexport const SUPPORTED_SDK_RANGE = '^4.0.1 || ^5.0.0'\n\n// Bounded semver: three `\\d+` tuples + an optional `-tag.N` prerelease. No nested quantifiers →\n// no catastrophic backtracking; the security/detect-unsafe-regex heuristic false-positives here.\n// eslint-disable-next-line security/detect-unsafe-regex -- bounded \\d+ groups anchored by `.`/`$`, no backtracking\nconst SEMVER_RE = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([a-z]+)\\.(\\d+))?$/\n\ninterface Semver {\n major: string\n minor: string\n patch: string\n tag: string | undefined\n pre: string | undefined\n}\n\nfunction parseSemver(value: string): Semver | null {\n const m = SEMVER_RE.exec(value)\n if (!m) return null\n return { major: m[1], minor: m[2], patch: m[3], tag: m[4], pre: m[5] }\n}\n\n/**\n * Return `true` when `version` satisfies `range`, where `range` is a `||`-joined series of caret\n * pins (e.g. `^4.0.1` or `^0.14.0 || ^1.0.0`). A version satisfies the range when it satisfies ANY\n * clause. Covers the semver subset the monorepo uses (caret pins, optional `-tag.N` prerelease);\n * no range-sets or build metadata.\n */\nexport function satisfiesSdkRange(version: string, range: string): boolean {\n return range\n .split('||')\n .map((clause) => clause.trim())\n .some((clause) => satisfiesSingleCaret(version, clause))\n}\n\nfunction satisfiesSingleCaret(version: string, range: string): boolean {\n if (!range.startsWith('^')) return false\n const pin = parseSemver(range.slice(1))\n const ver = parseSemver(version)\n if (!pin || !ver) return false\n if (pin.tag !== undefined) return satisfiesPrereleaseCaret(ver, pin)\n if (ver.tag !== undefined) return false // a prerelease never satisfies a stable caret pin\n return satisfiesStableCaret(ver, pin)\n}\n\nfunction satisfiesPrereleaseCaret(ver: Semver, pin: Semver): boolean {\n // Prerelease pin: same X.Y.Z + same tag + prerelease number >= pin's.\n if (pin.major !== ver.major || pin.minor !== ver.minor || pin.patch !== ver.patch) return false\n if (ver.tag !== pin.tag) return false\n return Number(ver.pre ?? '0') >= Number(pin.pre ?? '0')\n}\n\nfunction satisfiesStableCaret(ver: Semver, pin: Semver): boolean {\n if (pin.major !== ver.major) return false\n if (pin.major === '0') {\n // 0.X.Y caret: minor must match exactly; patch can be >=.\n if (pin.minor !== ver.minor) return false\n return Number(ver.patch) >= Number(pin.patch)\n }\n // >= 1.0.0 caret: minor can be >=, patch anything within the same/greater minor.\n if (Number(ver.minor) > Number(pin.minor)) return true\n if (Number(ver.minor) === Number(pin.minor)) return Number(ver.patch) >= Number(pin.patch)\n return false\n}\n","/**\n * Bootstrap stages extracted from `start.ts` per T4.2 (architecture-cleanup, ADR-0017).\n *\n * The full goal of T4.2 is a ≤30-LOC `startCommand` spine + 6-8 stage files. This\n * file ships the first batch: the configure-from-config bootstrap helpers + the\n * SSR entry resolver. They are stand-alone, side-effect-free at module load, and\n * already individually testable.\n *\n * Remaining stages (request-handler extraction, graceful-shutdown extraction,\n * signal-handlers extraction) are deferred to a follow-up sprint — see plan\n * ks.\n */\n\nimport { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport { warnOnce } from '../../../server/observability/logger.js'\n\ninterface SdkAgentRegistry {\n configure?: (opts: { maxAgents?: number; idleTimeoutMs?: number }) => void\n}\ninterface SdkModule {\n Agent?: { registry?: SdkAgentRegistry }\n}\n\n/**\n * Configure SDK's Agent.registry from `theo.config.ts > agents.registry`.\n * Lazy at boot; EC-3 sync flag flip prevents race under concurrent boot.\n * Silent no-op when registry config is absent or SDK is uninstalled.\n */\nexport async function configureAgentRegistryFromConfig(\n registryConfig: { maxAgents: number; idleTimeoutMs: number } | undefined,\n): Promise<void> {\n if (registryConfig === undefined) return\n try {\n const sdk = (await import('@theokit/sdk').catch(() => null)) as SdkModule | null\n const sdkConfigure = sdk?.Agent?.registry?.configure\n if (sdkConfigure === undefined) return\n const { configureAgentRegistryOnce } =\n await import('../../../server/agent/configure-agent-registry.js')\n configureAgentRegistryOnce(\n {\n configure: (opts) => {\n sdkConfigure(opts)\n },\n },\n registryConfig,\n )\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n warnOnce('bootstrap.agent_registry_skip', {\n event: 'bootstrap.agent_registry_skip',\n message: msg,\n })\n }\n}\n\n/**\n * Configure the StorageManager from `theo.config.ts > storage` (ADR-0007).\n * Manager enforces configure-once internally (D3); this helper bridges the\n * config to the singleton with actionable error handling.\n */\nexport async function configureStorageManagerFromConfig(storageConfig: unknown): Promise<void> {\n if (storageConfig === undefined || storageConfig === null) return\n try {\n const { getStorageManager } = await import('../../../server/storage/storage-manager.js')\n const { storageSchema } = await import('../../../config/schema.js')\n // Re-validate at boot so a malformed config from a non-Zod source\n // (test fixtures, dynamic configs) surfaces a clear error early.\n const parsed = storageSchema.parse(storageConfig)\n getStorageManager().configure(parsed)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n warnOnce('bootstrap.storage_skip', {\n event: 'bootstrap.storage_skip',\n message: msg,\n })\n }\n}\n\nconst SSR_EXTENSIONS = ['.mjs', '.js'] as const\n\n/**\n * Resolve the SSR entry-server module path. tsup may emit `.mjs` or `.js`\n * depending on output format. Try `.mjs` first (modern default) then fall\n * back to `.js`. Returns null when neither exists — SSR stays disabled.\n *\n * Exported so unit tests can pin the resolution order without booting the\n * full CLI.\n */\nexport function resolveSsrEntry(distDir: string): string | null {\n for (const ext of SSR_EXTENSIONS) {\n const path = resolve(distDir, `server/entry-server${ext}`)\n\n if (existsSync(path)) return path\n }\n return null\n}\n","import { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport type { CronManifest } from '../../../server/cron/cron-manifest.js'\nimport type { CronDefinition } from '../../../server/cron/cron-types.js'\n\n/**\n * Reads the cron manifest `theokit build` writes and re-loads each handler so\n * `theokit start` can drive the in-process scheduler.\n *\n * The build prints \"Cron → in-process scheduler (theokit start)\" for\n * `target: node`, but nothing on the serving side ever read `dist/crons.json`\n * — the crons were declared, validated, written, and then never ran\n * (theokit#324).\n *\n * The manifest names WHICH files hold a cron; the definition itself is taken\n * from the module, which is the source `defineCron` produced. Trusting the\n * manifest's copy of the schedule would let a stale build silently run a cron\n * on the wrong cadence.\n */\nexport async function loadCronDefinitions(\n manifestPath: string,\n projectRoot: string,\n loadModule: (filePath: string) => unknown,\n): Promise<CronDefinition[]> {\n if (!existsSync(manifestPath)) return []\n\n const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8')) as CronManifest\n const definitions: CronDefinition[] = []\n\n for (const entry of manifest.crons) {\n const filePath = resolve(projectRoot, entry.filePath)\n const mod = (await loadModule(filePath)) as { default?: unknown }\n const exported = mod.default\n\n if (!isCronDefinition(exported)) {\n throw new Error(\n `Cron \"${entry.name}\" declared in \"${entry.filePath}\" is missing a valid default export. ` +\n 'Expected `export default defineCron(name, { schedule, handler })`. ' +\n 'Re-run `theokit build` if the file changed since the last build.',\n )\n }\n\n definitions.push(exported)\n }\n\n return definitions\n}\n\nfunction isCronDefinition(value: unknown): value is CronDefinition {\n if (typeof value !== 'object' || value === null) return false\n const candidate = value as Record<string, unknown>\n return (\n typeof candidate.name === 'string' &&\n typeof candidate.schedule === 'string' &&\n typeof candidate.handler === 'function'\n )\n}\n","/**\n * Graceful shutdown stage for `theokit start` (T4.2 architecture-cleanup,\n * ADR-0007 D6 — SIGTERM evicts agents + drains StorageManager).\n *\n * EC-13: SIGTERM evicts agents IMMEDIATELY (no per-request drain). In-flight\n * requests get aborted mid-stream — acceptable because the platform LB\n * removed this pod from rotation BEFORE sending SIGTERM (K8s preStop hook +\n * terminationGracePeriodSeconds; same on Vercel/CF/Render).\n *\n * Re-entry guard: multiple SIGTERMs in quick succession run shutdown ONCE.\n */\n\nimport type { Server as HttpServer } from 'node:http'\n\nimport { warnOnce } from '../../../server/observability/logger.js'\n\nexport function installGracefulShutdown(server: HttpServer): void {\n let shuttingDown = false\n const shutdown = (signal: NodeJS.Signals): void => {\n if (shuttingDown) return\n shuttingDown = true\n console.log(`\\n [theokit] ${signal} received — evicting agents`)\n void (async () => {\n // Lazy-import SDK only at shutdown time to avoid forcing the dep on\n // apps that don't use agents at all.\n try {\n const sdk = (await import('@theokit/sdk').catch(() => null)) as {\n Agent?: { registry?: { evictAll?: () => Promise<void> } }\n } | null\n if (sdk?.Agent?.registry?.evictAll !== undefined) {\n await sdk.Agent.registry.evictAll()\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n warnOnce('shutdown.evict_error', {\n event: 'shutdown.evict_error',\n message: msg,\n })\n }\n // T3.1 — drain the StorageManager AFTER agent eviction. Order matters:\n // agents may still hold open pool refs while evicting; closing pools\n // first would break in-flight queries.\n try {\n const { getStorageManager } = await import('../../../server/storage/storage-manager.js')\n const manager = getStorageManager()\n await manager.dispose()\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n warnOnce('shutdown.dispose_error', {\n event: 'shutdown.dispose_error',\n message: msg,\n })\n }\n // #353 — flush telemetry LAST, so the spans covering eviction and the\n // storage drain are in the batch that leaves. Without this the exporter's\n // final buffer dies with the process, and the most interesting spans a\n // deploy produces — the ones from the shutdown itself — are the ones\n // guaranteed never to arrive.\n try {\n const { getObservabilityAdapter } =\n await import('../../../server/observability-bootstrap.js')\n await getObservabilityAdapter()?.shutdown()\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n warnOnce('shutdown.observability_error', {\n event: 'shutdown.observability_error',\n message: msg,\n })\n }\n console.log(` [theokit] shutdown complete`)\n server.close(() => {\n process.exit(0)\n })\n setTimeout(() => {\n warnOnce('shutdown.forced_exit', {\n event: 'shutdown.forced_exit',\n message: 'forced exit after 25s timeout',\n })\n process.exit(0)\n }, 25_000).unref()\n })()\n }\n process.on('SIGTERM', () => {\n shutdown('SIGTERM')\n })\n process.on('SIGINT', () => {\n shutdown('SIGINT')\n })\n}\n","/**\n * Manifest loading stage for `theokit start` (T4.2 architecture-cleanup).\n *\n * Loads pre-built manifest from `.theokit/manifest.json` if present; otherwise\n * scans server/ for routes/actions/ws at startup with a structured warn.\n */\n\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { dirname } from 'node:path'\n\nimport { warnOnce } from '../../../server/observability/logger.js'\nimport type { ActionNode } from '../../../server/scan/action-scan.js'\nimport { scanServerActions } from '../../../server/scan/action-scan.js'\nimport type { AgentNode } from '../../../server/scan/agent-scan.js'\nimport { scanAgents } from '../../../server/scan/agent-scan.js'\nimport { loadManifest } from '../../../server/scan/manifest.js'\nimport type { ServerRouteNode } from '../../../server/scan/match.js'\nimport { scanServerRoutes } from '../../../server/scan/scan.js'\nimport type { WebSocketRouteNode } from '../../../server/scan/ws-scan.js'\nimport { scanWebSocketRoutes } from '../../../server/scan/ws-scan.js'\n\ninterface LoadedRoutes {\n routes: ServerRouteNode[]\n actions: ActionNode[]\n wsRoutes: WebSocketRouteNode[]\n agents: AgentNode[]\n}\n\nexport function loadRoutesAndActions(\n distDir: string,\n serverDir: string,\n // #95 follow-up — agents dir name (config `agentsDir`, default \"agents\") for the live-scan fallback.\n agentsDir = 'agents',\n): LoadedRoutes {\n const manifestPath = join(distDir, 'manifest.json')\n\n if (existsSync(manifestPath)) {\n const manifest = loadManifest(distDir, serverDir)\n return {\n routes: manifest.routes,\n actions: manifest.actions,\n wsRoutes: manifest.websockets,\n agents: manifest.agents,\n }\n }\n warnOnce('bootstrap.manifest_not_found', {\n event: 'bootstrap.manifest_not_found',\n message:\n 'No manifest found, scanning routes at startup. Run \"theo build\" to generate manifest.',\n serverDir,\n })\n return {\n routes: scanServerRoutes(serverDir),\n actions: scanServerActions(serverDir),\n wsRoutes: scanWebSocketRoutes(serverDir),\n // Agents live at <projectRoot>/<agentsDir>; projectRoot = serverDir's parent for the canonical layout.\n agents: scanAgents(dirname(serverDir), agentsDir),\n }\n}\n","/**\n * Refuse to put unauthenticated write routes on a public network interface.\n *\n * `resolve-listen-host.ts` settled WHICH address gets bound and made the log say so. This settles\n * whether that address should be bound at all, given what is behind it. The two are deliberately\n * separate: one is about reachability, this one is about consequence.\n *\n * The gap it closes has a shape worth naming. ADR 0001 made every route declare who may call it and\n * stopped absence from meaning open — a real improvement, and an incomplete one, because `'public'`\n * is a declaration too. A route table where every entry says `policy('public')` passes the build\n * gate perfectly and is, in substance, a table nobody protected. Nothing downstream could tell the\n * two apart, since the policy value never left the module; `detectRoutePolicyKinds` is what made it\n * legible at scan time, and this is the first thing to act on it.\n *\n * ## What it refuses, and what it does not\n *\n * Refused: a non-loopback bind while at least one POST / PUT / PATCH / DELETE declares the literal\n * `'public'`. Those are the requests that spend money, mutate state, or send mail on the operator's\n * behalf, and an unauthenticated one reachable from a network is the shape of an open relay.\n *\n * NOT refused: public GET / HEAD / OPTIONS. Public read endpoints are ordinary — health checks,\n * catalogues, landing APIs — and a gate that fired on them would be switched off within a day. A\n * gate with a stated edge is worth more than a gate nobody runs. That means this does NOT protect\n * against a public GET that leaks data; that is authorization work the policy function must do, and\n * saying so here is cheaper than letting an operator infer a guarantee that was never offered.\n *\n * ## Why absence is not safety\n *\n * A manifest built before this existed carries no `publicMethods`, and reading that silence as\n * \"nothing is public\" would be the same defect the gate exists to prevent, one field over. The\n * verdict is `unverified`: the server still starts (an upgrade must not break a running deploy),\n * and the operator is told plainly that the check did not run and what to do about it.\n */\nimport type { ServerRouteNode } from '../../../server/scan/match.js'\n\nimport type { ListenTarget } from './resolve-listen-host.js'\n\n/**\n * Methods that change something. The rest are readable requests, out of scope by the reasoning in\n * the module docblock.\n */\nconst MUTATING_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\n/** Addresses that reach only this machine. */\nconst LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1'])\n\n/** One unauthenticated write, named so the operator can go and look at it. */\nexport interface Exposure {\n readonly routePath: string\n readonly method: string\n}\n\nexport type ExposureVerdict =\n /** Bound to the loopback — nothing outside this machine can reach it, so there is nothing to judge. */\n | { readonly kind: 'not-exposed' }\n /** Public bind, and every mutating route is guarded. */\n | { readonly kind: 'allowed' }\n /** Public bind with unauthenticated writes, permitted by an explicit written decision. */\n | { readonly kind: 'allowed-by-override'; readonly exposures: readonly Exposure[] }\n /** Public bind with unauthenticated writes and no override. The server must not start. */\n | { readonly kind: 'refused'; readonly exposures: readonly Exposure[]; readonly message: string }\n /** Public bind, and the route table cannot answer the question. Starts, says so. */\n | { readonly kind: 'unverified'; readonly message: string }\n\nexport interface ExposureAssessment {\n readonly routes: readonly ServerRouteNode[]\n readonly target: ListenTarget\n /** `security.allowUnauthenticatedWrites` from theo.config.ts — an explicit, written decision. */\n readonly allowUnauthenticatedWrites: boolean\n /**\n * Whether this build emitted compiled controllers, which `routes` does not describe.\n *\n * The caller already knows: `start` resolves `dist/controllers.json` to decide whether to serve\n * them at all. Passing the fact in is what lets an empty `routes` mean two different things —\n * \"this app serves nothing\", which is safe to bind, and \"this app serves through controllers the\n * manifest does not list\", which is not judged here at all.\n *\n * `undefined` is read as unknown, not as false. Distinguishing them is the point of the field, and\n * a caller that has not been updated should not be answered with confidence it did not supply.\n */\n readonly hasControllers?: boolean\n}\n\n/** Does this address reach anything beyond this machine? */\nfunction isPubliclyBound(host: string): boolean {\n return !LOOPBACK_HOSTS.has(host.toLowerCase())\n}\n\nfunction unauthenticatedWrites(routes: readonly ServerRouteNode[]): Exposure[] {\n const found: Exposure[] = []\n for (const route of routes) {\n for (const method of route.publicMethods ?? []) {\n if (MUTATING_METHODS.has(method)) found.push({ routePath: route.routePath, method })\n }\n }\n return found\n}\n\n/**\n * Why the gate cannot judge this route table, or `null` when it can.\n *\n * Two different absences reach here, and telling them apart matters because the operator's next\n * move differs. Returning a REASON rather than a boolean is what keeps the message honest: a\n * warning that prescribes `theo build` to someone whose build is fine teaches them to ignore it.\n *\n * - `stale-manifest` — routes exist and declare mutating methods, but none carries `publicMethods`.\n * The signature of a manifest built before this check existed. Regenerating it answers the\n * question.\n *\n * - `empty-table` — the manifest describes no routes at all. Regenerating it changes nothing: the\n * scan behind that array reads `server/routes/`, and an app serving through controllers has none\n * (usetheokit/theokit#543). Measured on a nine-controller app: sixteen routes served, `routes: []`\n * written.\n *\n * This one was silently `allowed` until 2026-08-28. `.some()` on an empty array is false for both\n * questions above, so the gate concluded there was nothing to expose and bound a public interface\n * without a word — while a controller declaring `@SetMetadata('theokit:public', true)` on a POST\n * sat on it. Absence of a description is not absence of exposure, which is the sentence this whole\n * file is written around.\n */\nfunction whyCannotAnswer(\n routes: readonly ServerRouteNode[],\n hasControllers: boolean | undefined,\n): 'stale-manifest' | 'empty-table' | null {\n if (routes.length === 0) {\n // An app that serves nothing is safe to bind anywhere, and warning about it is the kind of\n // noise that gets a gate switched off. Only an empty table that is NOT the whole story warrants\n // a word — controllers present, or a caller that did not say.\n return hasControllers === false ? null : 'empty-table'\n }\n if (routes.some((r) => r.publicMethods !== undefined)) return null\n return routes.some((r) => (r.methods ?? []).some((m) => MUTATING_METHODS.has(m)))\n ? 'stale-manifest'\n : null\n}\n\nfunction unverifiedMessage(reason: 'stale-manifest' | 'empty-table', host: string): string {\n const head = `Binding ${host} without checking whether its write routes are authenticated.`\n return reason === 'stale-manifest'\n ? [\n head,\n ' This manifest predates the check and records no policy kinds. Run `theo build` to',\n ' regenerate it; until then the server starts, and this warning is the whole of what',\n ' is known about the exposure.',\n ].join('\\n')\n : [\n head,\n ' The manifest describes no routes, so there is nothing here to judge. If this app serves',\n ' through controllers, that is expected and rebuilding will not change it — the route scan',\n ' reads `server/routes/` only (usetheokit/theokit#543). Whatever your controllers mark',\n ' `theokit:public` on a POST/PUT/PATCH/DELETE is reachable from this address, and this',\n ' warning is the whole of what is known about it.',\n ].join('\\n')\n}\n\nfunction refusalMessage(exposures: readonly Exposure[], host: string): string {\n const list = exposures.map((e) => ` ${e.method} ${e.routePath}`).join('\\n')\n return [\n exposures.length === 1\n ? `Refusing to bind ${host}: 1 write route accepts unauthenticated requests.`\n : `Refusing to bind ${host}: ${String(exposures.length)} write routes accept unauthenticated requests.`,\n '',\n list,\n '',\n \" Each declares `policy('public')`, so anyone who can reach this address can call it.\",\n ' On a loopback bind that is a demo; on this one it is an open endpoint.',\n '',\n ' Resolve it one of two ways:',\n ' • give each route a real policy — `policy(({ subject }) => subject !== null)`, or',\n ' `requireOwner(subject, record.ownerId)` from `theokit/server/define`. A plugin hook',\n ' establishes `ctx.subject`; the policy reads it.',\n ' • decide otherwise, in writing: `security: { allowUnauthenticatedWrites: true }` in',\n ' theo.config.ts. The routes stay open and the startup log keeps saying so.',\n ].join('\\n')\n}\n\n/**\n * Judge a route table against the address about to be bound.\n *\n * Pure — it reads, it decides, it returns. The caller prints and exits, which keeps this testable\n * without a process and keeps the exit policy in one place.\n */\nexport function assessPublicExposure(input: ExposureAssessment): ExposureVerdict {\n if (!isPubliclyBound(input.target.host)) return { kind: 'not-exposed' }\n\n const blind = whyCannotAnswer(input.routes, input.hasControllers)\n if (blind !== null) {\n return { kind: 'unverified', message: unverifiedMessage(blind, input.target.host) }\n }\n\n const exposures = unauthenticatedWrites(input.routes)\n if (exposures.length === 0) return { kind: 'allowed' }\n if (input.allowUnauthenticatedWrites) return { kind: 'allowed-by-override', exposures }\n\n return { kind: 'refused', exposures, message: refusalMessage(exposures, input.target.host) }\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\nimport { extname } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\nimport type { RouteSubject } from '../../../core/contracts/route-policy.js'\nimport { readAgentPolicy } from '../../../server/agent/agent-access.js'\nimport { getApprovalRegistry } from '../../../server/agent/approval-registry.js'\nimport {\n handleAgentApproval,\n isApprovalPath,\n parseApprovalAgentName,\n} from '../../../server/agent/approve-agent.js'\nimport { mountAgent } from '../../../server/agent/mount-agent.js'\nimport { resolveProvider } from '../../../server/agent/provider-resolver.js'\nimport { matchAgentAuxRoute, serveMatchedAuxRoute } from '../../../server/agent/serve-aux-routes.js'\nimport { executeAction } from '../../../server/http/action-execute.js'\nimport { dispatchControllerRequest } from '../../../server/http/controller-dispatch.js'\nimport { executeRoute } from '../../../server/http/execute.js'\nimport { createWebRequestSource } from '../../../server/http/node-request.js'\nimport { writeWebResponseToServerResponse } from '../../../server/http/node-web-adapter.js'\nimport { serveThroughPluginLifecycle } from '../../../server/http/plugin-lifecycle.js'\nimport { createAgentSubjectResolver } from '../../../server/http/resolve-agent-subject.js'\nimport { sendError } from '../../../server/http/send-response.js'\nimport { serveStaticFile } from '../../../server/http/static.js'\nimport { logRequest } from '../../../server/observability/logger.js'\nimport { findSuggestion } from '../../../server/observability/suggest.js'\nimport type { PluginRunner } from '../../../server/plugins/plugin-runner.js'\nimport type { ActionNode } from '../../../server/scan/action-scan.js'\nimport type { AgentNode } from '../../../server/scan/agent-scan.js'\nimport { matchRoute } from '../../../server/scan/match.js'\nimport type { ServerRouteNode } from '../../../server/scan/match.js'\nimport type { LoadModule } from '../../../server/scan/module-loader.js'\nimport type { CsrfMode, DisallowedConfig } from '../../../server/security/csrf.js'\nimport type { TheoTransformer } from '../../../server/transformer.js'\n\n/**\n * Load a controller module that `theokit build` already compiled — theokit#123.\n *\n * A plain dynamic `import()`, deliberately: production must not need `@swc/core`. That peer is a\n * native binary the app would otherwise carry solely to re-do work the build already did, and a\n * missing optional peer would degrade into a runtime 404 instead of a build failure.\n */\nasync function loadCompiledController(absPath: string): Promise<Record<string, unknown>> {\n return (await import(pathToFileURL(absPath).href)) as Record<string, unknown>\n}\n\n/** Response header carrying the per-request correlation id. */\nconst X_REQUEST_ID = 'x-request-id'\n\n/**\n * T6.1 (PV-7 SRP): start.ts request orchestrator decomposed into 5 focused\n * per-branch handlers. Each handler returns `true` if it handled the\n * request (response sent) so the orchestrator can stop iterating.\n *\n * The original 455-LOC monolith closed over 14+ locals; the shared shape\n * `RequestHandlerCtx` makes the dependencies explicit + reviewable.\n */\nexport interface RequestHandlerCtx {\n req: IncomingMessage\n res: ServerResponse\n url: string\n requestId: string\n startTime: number\n // Pre-loaded build artifacts\n clientDir: string\n custom404Html: string | null\n // Manifest-resolved tables\n cachedRoutes: ServerRouteNode[]\n cachedActions: ActionNode[]\n cachedAgents: AgentNode[]\n // Runtime infra\n loadModule: LoadModule\n serverDir: string\n /** App root (= `process.cwd()` at `theokit start`); mountAgent points `.theokit/` discovery here. */\n projectRoot: string\n /**\n * theokit#123 — absolute path to the COMPILED controllers emitted by `theokit build`\n * (`<distDir>/controllers`), or `undefined` when the build produced none.\n *\n * `undefined` is the routes-only app, and it must stay free: no scan, no import, no cost.\n */\n controllersDistDir: string | undefined\n pluginRunner: PluginRunner | undefined\n transformer: TheoTransformer | undefined\n csrfMode: CsrfMode\n disallowed: DisallowedConfig | undefined\n /**\n * Async because the per-route limiter hashes the session cookie with Web Crypto when\n * `keyBy: 'session'`, and `subtle.digest` is promise-based.\n */\n rateLimiter:\n | ((req: IncomingMessage) => Promise<{ limited: boolean; headers: Record<string, string> }>)\n | null\n}\n\n/**\n * The caller's identity, from the application's own `server/context.ts` — the seam every\n * `route()` already reads and no agent URL ever reached (usetheokit/theokit#365).\n *\n * Memoized per request by `createAgentSubjectResolver` and invoked only on a path this process is\n * about to answer, and only when that path's agent declares a policy.\n */\nfunction agentSubjectResolver(c: RequestHandlerCtx): () => Promise<RouteSubject | null> {\n return createAgentSubjectResolver({\n req: c.req,\n res: c.res,\n loadModule: c.loadModule,\n serverDir: c.serverDir,\n pluginRunner: c.pluginRunner,\n })\n}\n\n/** Apply rate limit; return true if request was limited (response sent). */\nasync function applyRateLimit(c: RequestHandlerCtx, method: string): Promise<boolean> {\n if (!c.rateLimiter) return false\n const check = await c.rateLimiter(c.req)\n for (const [k, v] of Object.entries(check.headers)) c.res.setHeader(k, v)\n if (check.limited) {\n sendError(c.res, 'RATE_LIMITED', 'Too many requests', 429, undefined, c.requestId)\n logRequest({\n method,\n url: c.url,\n status: 429,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n return false\n}\n\n/** Branch 1: action routes (`/api/__actions/{file}/{exportName}`). */\nexport async function tryServeAction(c: RequestHandlerCtx): Promise<boolean> {\n if (!c.url.startsWith('/api/__actions/')) return false\n c.res.setHeader(X_REQUEST_ID, c.requestId)\n\n if (await applyRateLimit(c, c.req.method ?? 'POST')) return true\n\n const pathAfterPrefix = c.url.slice('/api/__actions/'.length).split('?')[0]\n const segments = pathAfterPrefix.split('/').filter(Boolean)\n if (segments.length < 2) {\n sendError(\n c.res,\n 'BAD_REQUEST',\n 'Action URL must be /api/__actions/{file}/{exportName}',\n 400,\n undefined,\n c.requestId,\n )\n logRequest({\n method: c.req.method ?? 'POST',\n url: c.url,\n status: 400,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n const exportName = segments[segments.length - 1]\n const actionPath = segments.slice(0, -1).join('/')\n const action = c.cachedActions.find((a) => a.actionPath === actionPath)\n if (!action) {\n const actionPaths = c.cachedActions.map((a) => a.actionPath)\n const suggestion = findSuggestion(actionPath, actionPaths)\n const msg = suggestion\n ? `Action \"${actionPath}\" not found. Did you mean: ${suggestion}?`\n : `Action \"${actionPath}\" not found`\n sendError(c.res, 'NOT_FOUND', msg, 404, undefined, c.requestId)\n logRequest({\n method: c.req.method ?? 'POST',\n url: c.url,\n status: 404,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n await executeAction(\n action.filePath,\n exportName,\n c.req,\n c.res,\n c.loadModule,\n c.serverDir,\n c.requestId,\n c.pluginRunner,\n c.csrfMode,\n c.disallowed,\n )\n logRequest({\n method: c.req.method ?? 'POST',\n url: c.url,\n status: c.res.statusCode,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n}\n\n/**\n * Branch 1.4: agent AUXILIARY routes served identically in dev + prod (M15/M16 follow-up) — agent\n * cards (`/.well-known/<name>/agent-card.json`), MCP (`/api/agents/<name>/mcp`), the pending-approvals\n * listing, the durable run stream and the two thread routes. Before this, they were dev-only, so a\n * built/deployed app 404'd them.\n *\n * theokit#400 — this branch runs for EVERY url, so it must decide ownership without converting the\n * request: `incomingMessageToWebRequest` drains the Node body stream, and a POST with a JSON body to\n * an ordinary `/api` file route then reached `parseJsonBody` with a readable that had already ended\n * and waited forever for an `'end'` that had already fired — no status, no timeout, no response.\n * `matchAgentAuxRoute` is handed a method and a url and cannot convert anything, so the ordering is\n * now a property of the signature rather than of this comment.\n *\n * usetheokit/theokit#405 — and having a match separate from the answer is what lets the plugin\n * lifecycle run here at all. It did not, in either surface: six endpoints answered without\n * `onRequest`/`onResponse`/`onError`, so an app embedding TheoKit could not observe them and the\n * observability plugin emitted no `http.request` span for the two that spend tokens. The same\n * bracket the plain agent turn uses now wraps this one, from the same function.\n */\nexport async function tryServeAgentAux(c: RequestHandlerCtx): Promise<boolean> {\n const urlPath = c.url.split('?')[0]\n const deps = {\n agents: c.cachedAgents,\n loadModule: c.loadModule,\n baseUrl: `http://${c.req.headers.host ?? 'localhost'}`,\n // M34 (#97) — the MCP aux route drives the agent (spends tokens); enforce CSRF like the run route.\n csrfMode: c.csrfMode,\n // M39 — the thread follow-up route drives the agent; resolve the key on demand.\n // theokit#328 — the thread route drives an agent, so its key follows the model too.\n resolveApiKey: (model: string | undefined, plugins?: readonly unknown[]) =>\n resolveProvider(model, { plugins }).apiKey,\n // usetheokit/theokit#365 — who is asking. Memoized and LAZY: built here, invoked only inside\n // `serveMatchedAuxRoute` and only when the matched agent declares a policy, so the application's\n // `createContext` never runs for a url this branch declines.\n resolveSubject: agentSubjectResolver(c),\n }\n\n const method = (c.req.method ?? 'GET').toUpperCase()\n const route = await matchAgentAuxRoute(method, urlPath, deps)\n if (route === null) return false\n\n c.res.setHeader(X_REQUEST_ID, c.requestId)\n await serveThroughPluginLifecycle(\n {\n source: createWebRequestSource(c.req),\n res: c.res,\n requestId: c.requestId,\n pluginRunner: c.pluginRunner,\n failureMessage: 'Agent aux handler failed',\n },\n async (request) => {\n await writeWebResponseToServerResponse(\n await serveMatchedAuxRoute(route, request, deps),\n c.res,\n )\n },\n )\n logRequest({\n method,\n url: c.url,\n status: c.res.statusCode,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n}\n\n/**\n * Branch 1.5: agent convention routes (`/api/agents/<name>`, M2). Runs BEFORE the generic `/api/*`\n * branch so a scanned `agents/<name>.ts` owns its path (parity with dev). Loads the module, resolves\n * the provider apiKey (fail-fast), and streams the M0/M1 UIMessageStream via `mountAgent`.\n */\nexport async function tryServeAgent(c: RequestHandlerCtx): Promise<boolean> {\n if (!c.url.startsWith('/api/agents/')) return false\n const urlPath = c.url.split('?')[0]\n\n // HITL approve route (`/api/agents/<name>/approve/<id>`, M4) — resolve the pending approval.\n // Handled BEFORE the agent-path exact match (the approve path never equals an `agentPath`).\n if (isApprovalPath(urlPath)) {\n c.res.setHeader(X_REQUEST_ID, c.requestId)\n if (await applyRateLimit(c, c.req.method ?? 'POST')) return true\n const method = (c.req.method ?? 'POST').toUpperCase()\n if (method !== 'POST') {\n sendError(\n c.res,\n 'METHOD_NOT_ALLOWED',\n 'Approve endpoints accept POST',\n 405,\n undefined,\n c.requestId,\n )\n logRequest({\n method,\n url: c.url,\n status: 405,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n // usetheokit/theokit#405 — the approve route settles a human decision and answered with no\n // plugin lifecycle at all, so a hook that fires for every other route never fired for this one\n // and no `http.request` span was emitted for it. Same bracket as the turn below.\n await serveThroughPluginLifecycle(\n {\n source: createWebRequestSource(c.req),\n res: c.res,\n requestId: c.requestId,\n pluginRunner: c.pluginRunner,\n failureMessage: 'Approve handler failed',\n },\n async (request) => {\n // usetheokit/theokit#365 — the approve route settles a paused tool, so it answers to the\n // named agent's declared policy. The resolver is memoized and lazy: `handleAgentApproval`\n // invokes it only when the agent declares one.\n const approveAgent = c.cachedAgents.find((a) => a.name === parseApprovalAgentName(urlPath))\n const policy =\n approveAgent === undefined\n ? undefined\n : readAgentPolicy(await c.loadModule(approveAgent.filePath), approveAgent.filePath)\n const response = await handleAgentApproval(\n request,\n urlPath,\n getApprovalRegistry(),\n c.csrfMode,\n { policy, resolveSubject: agentSubjectResolver(c) },\n )\n await writeWebResponseToServerResponse(response, c.res)\n },\n )\n logRequest({\n method,\n url: c.url,\n status: c.res.statusCode,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n\n const agent = c.cachedAgents.find((a) => a.agentPath === urlPath)\n if (!agent) return false // fall through to the generic /api/* branch (may 404 there)\n\n c.res.setHeader(X_REQUEST_ID, c.requestId)\n if (await applyRateLimit(c, c.req.method ?? 'POST')) return true\n\n const method = (c.req.method ?? 'POST').toUpperCase()\n if (method !== 'POST') {\n sendError(\n c.res,\n 'METHOD_NOT_ALLOWED',\n 'Agent endpoints accept POST',\n 405,\n undefined,\n c.requestId,\n )\n logRequest({\n method,\n url: c.url,\n status: 405,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n\n await serveAgentTurn(c, agent, method)\n return true\n}\n\n/**\n * Serves one agent turn through the plugin lifecycle.\n *\n * Extracted from `tryServeAgent` so that function stays what its name says — a router over the\n * agent paths — while the turn itself, which is what grew a lifecycle, reads in one piece.\n */\nasync function serveAgentTurn(\n c: RequestHandlerCtx,\n agent: { filePath: string; name: string },\n method: string,\n): Promise<void> {\n // theokit#324 — the plugin lifecycle runs here too.\n //\n // This branch used to mount the agent without ever consulting the runner, so `onRequest`,\n // `onResponse` and `onError` fired for every OTHER route and never for an agent turn — leaving an\n // app embedding TheoKit with no supported place to observe or bound agent state. Reported with a repro\n // by a consumer who found it by instrumenting a hook and watching it stay silent — the failure is\n // invisible by construction, since a hook that never runs looks exactly like one with nothing to\n // say.\n //\n // The bracket itself moved to `serveThroughPluginLifecycle` when the aux and approve branches\n // needed the same one (usetheokit/theokit#405): this shape was copied twice and drifted five\n // ways, which is the argument for having one of it.\n const resolveSubject = agentSubjectResolver(c)\n\n await serveThroughPluginLifecycle(\n {\n source: createWebRequestSource(c.req),\n res: c.res,\n requestId: c.requestId,\n pluginRunner: c.pluginRunner,\n failureMessage: 'Agent handler failed',\n },\n async (request) => {\n const mod = await c.loadModule(agent.filePath)\n // theokit#326 — resolve against the model the agent declares, not by env priority.\n const apiKey = (model: string | undefined, plugins?: readonly unknown[]): string =>\n resolveProvider(model, { plugins }).apiKey\n const response = await mountAgent(mod, request, apiKey, {\n source: agent.filePath,\n csrfMode: c.csrfMode,\n projectRoot: c.projectRoot,\n // usetheokit/theokit#365 — the policy is read off `mod` inside `mountAgent`; what this\n // caller owes is the identity to judge it against. usetheokit/theokit#406 — and it is the\n // same value the run's spans are labelled with, so `source` above never reaches telemetry.\n agentName: agent.name,\n resolveSubject,\n })\n await writeWebResponseToServerResponse(response, c.res)\n },\n )\n logRequest({\n method,\n url: c.url,\n status: c.res.statusCode,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n}\n\n/** Branch 2: API routes (`/api/*` excluding actions). */\nexport async function tryServeApiRoute(c: RequestHandlerCtx): Promise<boolean> {\n if (!c.url.startsWith('/api/')) return false\n c.res.setHeader(X_REQUEST_ID, c.requestId)\n\n if (await applyRateLimit(c, c.req.method ?? 'GET')) return true\n\n const match = matchRoute(c.url, c.cachedRoutes)\n if (!match) {\n // theokit#123 — controller fall-through, mirroring the dev `api-middleware` arm.\n //\n // Before this, `theokit dev` served a decorator controller and `theokit start` 404'd it: the\n // production path has no Vite/swc transform, so an uncompiled `.controller.ts` could not load.\n // `theokit build` now emits compiled modules and this branch serves them, so the SAME app\n // answers the same routes in both. Reached only after a file-route miss, so file routes keep\n // precedence exactly as in dev.\n if (c.controllersDistDir !== undefined) {\n const handled = await dispatchControllerRequest({\n controllersDir: c.controllersDistDir,\n loadModule: loadCompiledController,\n req: c.req,\n res: c.res,\n csrfMode: c.csrfMode,\n disallowed: c.disallowed,\n requestId: c.requestId,\n // #607 — the runner this branch never received. Without it a `@Controller` route ran no\n // plugin hook at all in production: not `onRequest`, not `preHandler`, not `onResponse`,\n // not `onError`. The file-route branch below has always passed it.\n pluginRunner: c.pluginRunner,\n })\n if (handled) return true\n }\n const urlPath = c.url.split('?')[0]\n const routePaths = c.cachedRoutes.map((r) => r.routePath)\n const suggestion = findSuggestion(urlPath, routePaths)\n const msg = suggestion\n ? `API route not found: ${urlPath}. Did you mean: ${suggestion}?`\n : 'API route not found'\n sendError(c.res, 'NOT_FOUND', msg, 404, undefined, c.requestId)\n logRequest({\n method: c.req.method ?? 'GET',\n url: c.url,\n status: 404,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n const method = (c.req.method ?? 'GET').toUpperCase()\n // T3.1 (ADR-0016) — context object replaces 12 positional args\n await executeRoute({\n route: match.route,\n method,\n params: match.params,\n req: c.req,\n res: c.res,\n loadModule: c.loadModule,\n serverDir: c.serverDir,\n requestId: c.requestId,\n pluginRunner: c.pluginRunner,\n transformer: c.transformer,\n csrfMode: c.csrfMode,\n disallowed: c.disallowed,\n })\n logRequest({\n method,\n url: c.url,\n status: c.res.statusCode,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n}\n\n/** Branch 3: static files (returns true if a static asset was served). */\nexport function tryServeStatic(c: RequestHandlerCtx): boolean {\n return serveStaticFile(c.req, c.res, c.clientDir)\n}\n\n/** Branch 4: custom 404 for URLs that look like missing assets. */\nexport function tryServeCustom404(c: RequestHandlerCtx): boolean {\n const urlPath = c.url.split('?')[0]\n if (c.custom404Html && extname(urlPath)) {\n c.res.writeHead(404, { 'Content-Type': 'text/html' })\n c.res.end(c.custom404Html)\n return true\n }\n return false\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Static-file server. The URL path IS user-controlled, so every fs call here is guarded twice\n * before it runs: a string check that rejects a URL walking out with `..` (403), and then a\n * `realpath` check that asks the filesystem whether the target actually lives under `clientDir`.\n *\n * The second guard is not decoration. This header used to claim the string check alone was\n * authoritative, and #428 disproved it: `path.resolve` never touches the disk, so a symlink inside\n * `clientDir` sailed through it and the server returned a file from anywhere on the host. Both\n * guards are now required for that claim to hold.\n */\nimport { closeSync, fstatSync, openSync, readFileSync, realpathSync } from 'node:fs'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { resolve, extname, sep } from 'node:path'\n\nconst MIME_TYPES: Record<string, string> = {\n '.html': 'text/html',\n '.js': 'application/javascript',\n '.mjs': 'application/javascript',\n '.css': 'text/css',\n '.json': 'application/json',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n '.txt': 'text/plain',\n '.map': 'application/json',\n}\n\nexport function serveStaticFile(\n req: IncomingMessage,\n res: ServerResponse,\n clientDir: string,\n): boolean {\n const urlPath = (req.url ?? '/').split('?')[0]\n\n // Path traversal prevention (EC-1) — rejects a URL that walks out with `..`.\n // `sep` matters: without it `/srv/client-backup` passes as \"inside\" `/srv/client`.\n const filePath = resolve(clientDir, '.' + urlPath)\n if (filePath !== clientDir && !filePath.startsWith(clientDir + sep)) {\n res.writeHead(403)\n res.end('Forbidden')\n return true\n }\n\n // #428 — the check above is string arithmetic; `resolve` never touches the disk, so it cannot\n // see that an entry inside `clientDir` IS a file somewhere else. Ask the filesystem instead.\n // Symlinks are not banned — one that stays inside the served tree is ordinary and still served;\n // only the ones that leave are refused, and they are refused as \"not here\" rather than 403 so\n // the response does not confirm what lies outside.\n let realPath: string\n let realRoot: string\n try {\n realPath = realpathSync(filePath)\n realRoot = realpathSync(clientDir)\n } catch {\n return false // missing, unreadable, or a broken link — all \"not served\"\n }\n if (realPath !== realRoot && !realPath.startsWith(realRoot + sep)) return false\n\n // One descriptor for the type check and the bytes: re-opening by path between them is what\n // lets the file that was checked differ from the file that is served (CodeQL js/file-system-race).\n let fd: number\n try {\n fd = openSync(realPath, 'r')\n } catch {\n return false\n }\n try {\n if (!fstatSync(fd).isFile()) return false\n\n const ext = extname(filePath)\n const contentType = MIME_TYPES[ext] ?? 'application/octet-stream'\n const content = readFileSync(fd)\n\n res.writeHead(200, {\n 'Content-Type': contentType,\n 'Content-Length': content.length,\n })\n res.end(content)\n return true\n } finally {\n closeSync(fd)\n }\n}\n","/**\n * SSR setup stage for `theokit start` (T4.2 architecture-cleanup).\n *\n * Loads the SSR entry-server module if configured + builds template split\n * around the React root div. Returns null renderers when SSR is disabled.\n */\n\nimport type { ServerResponse } from 'node:http'\n\nimport { findRootDiv } from '../../../core/contracts/find-root-div.js'\n\nimport { resolveSsrEntry } from './bootstrap-stages.js'\n\nexport interface SsrRenderResult {\n html: string\n hydrationData: {\n loaderData?: unknown\n actionData?: unknown\n errors?: unknown\n }\n}\n\nexport type RenderStreamingResult = { redirect: Response } | { streaming: true } | undefined\n\nexport type SsrRender = (\n url: string,\n options?: { nonce?: string },\n) => Promise<SsrRenderResult | { redirect: Response } | string>\n\nexport type SsrRenderStreaming = (\n url: string,\n response: ServerResponse,\n options?: {\n signal?: AbortSignal\n nonce?: string\n /**\n * The template up to and including `<div id=\"root\">`, written before React\n * produces a byte. Without it the streamed response is React's output alone\n * — no `<html>`, no `<head>` (usetheokit/theokit#343).\n */\n htmlHead?: string\n /** The template after `</div>`, written after the hydration data script. */\n htmlTail?: string\n },\n) => Promise<RenderStreamingResult>\n\ninterface SsrSetupResult {\n enabled: boolean\n streamingEnabled: boolean\n render: SsrRender | null\n renderStreaming: SsrRenderStreaming | null\n htmlHead: string\n htmlTail: string\n}\n\nexport function isSsrRenderResult(value: unknown): value is SsrRenderResult {\n if (typeof value !== 'object' || value === null) return false\n if (!('html' in value)) return false\n const html = (value as Record<string, unknown>).html\n if (typeof html !== 'string') return false\n return true\n}\n\ninterface SsrEntryServer {\n render: SsrRender\n renderStreaming?: SsrRenderStreaming\n}\n\nexport async function setupSsr(opts: {\n distDir: string\n indexHtml: string\n ssrConfigEnabled: boolean\n ssrStreamingConfig: boolean | undefined\n}): Promise<SsrSetupResult> {\n const ssrServerPath: string | null = opts.ssrConfigEnabled ? resolveSsrEntry(opts.distDir) : null\n const enabled = ssrServerPath !== null\n const streamingEnabled = enabled && Boolean(opts.ssrStreamingConfig)\n\n if (ssrServerPath === null) {\n return {\n enabled: false,\n streamingEnabled: false,\n render: null,\n renderStreaming: null,\n htmlHead: '',\n htmlTail: '',\n }\n }\n\n const mod = (await import(ssrServerPath)) as SsrEntryServer\n const render = mod.render\n const renderStreaming = typeof mod.renderStreaming === 'function' ? mod.renderStreaming : null\n\n // Split HTML template on root div\n let htmlHead = ''\n let htmlTail = ''\n const rootDiv = findRootDiv(opts.indexHtml)\n if (rootDiv) {\n htmlHead = opts.indexHtml.slice(0, rootDiv.insertAt)\n htmlTail = opts.indexHtml.slice(rootDiv.insertAt)\n }\n\n return { enabled, streamingEnabled, render, renderStreaming, htmlHead, htmlTail }\n}\n","/**\n * Inline request handler for `theokit start` (T4.2 architecture-cleanup).\n *\n * Wires the per-request flow: security headers → action/route/static branches\n * → SSR fallback → CSR fallback → 500 page.\n *\n * Refactored: CC reduced from 33 to ≤10 per function\n * (architecture-remediation T2.1, 2026-06-12).\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\nimport { generateNonce } from '../../../server/auth/nonce.js'\nimport { type ReservedRoutes, serveReservedRoute } from '../../../server/define/health-route.js'\nimport type { CorsHandler } from '../../../server/http/cors.js'\nimport { sendError } from '../../../server/http/send-response.js'\nimport { TRACE_HEADER, extractTraceId } from '../../../server/http/trace-context.js'\nimport { buildSecurityHeaders } from '../../../server/security/security-headers.js'\nimport { extractHeadTags, injectIntoHead } from '../../../vite-plugin/hoist-head-tags.js'\nimport { applyNonceToInlineScripts } from '../../../vite-plugin/ssr-dev-middleware.js'\n\nimport {\n tryServeAction,\n tryServeAgent,\n tryServeAgentAux,\n tryServeApiRoute,\n tryServeCustom404,\n tryServeStatic,\n type RequestHandlerCtx,\n} from './handlers.js'\nimport {\n isSsrRenderResult,\n type SsrRender,\n type SsrRenderResult,\n type SsrRenderStreaming,\n} from './ssr-setup.js'\n\ninterface RequestHandlerContext {\n buildCtx: (\n req: IncomingMessage,\n res: ServerResponse,\n requestId: string,\n startTime: number,\n ) => RequestHandlerCtx\n securityHeadersConfig: Parameters<typeof buildSecurityHeaders>[0]\n /**\n * #409 — `security.cors` had exactly one consumer, Vite's `configureServer` hook, so an app that\n * worked cross-origin under `theokit dev` stopped working the moment this command served it:\n * same config, same code, no error and no warning. `null` when the app declared no `cors` block,\n * which is what it meant before and still means — no headers, not permissive ones.\n */\n corsHandler: CorsHandler | null\n ssrRender: SsrRender | null\n ssrRenderStreaming: SsrRenderStreaming | null\n ssrStreamingEnabled: boolean\n htmlHead: string\n htmlTail: string\n indexHtml: string\n custom500Html: string | null\n /** M7-2: reserved health/ready routes served before the user catch-all. */\n reservedRoutes?: ReservedRoutes\n}\n\n/**\n * M7-2: serve a reserved `/__theo/*` route (health/ready) before any user\n * branch. Returns true when the request was handled.\n */\nasync function tryServeReserved(\n ctx: RequestHandlerContext,\n url: string,\n res: ServerResponse,\n): Promise<boolean> {\n const pathname = new URL(url, 'http://localhost').pathname\n const reserved = await serveReservedRoute(pathname, ctx.reservedRoutes ?? {})\n if (reserved === null) return false\n const payload = JSON.stringify(reserved.body)\n res.writeHead(reserved.status, {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(payload),\n })\n res.end(payload)\n return true\n}\n\nfunction asSsrRenderResult(value: SsrRenderResult): SsrRenderResult {\n return value\n}\n\nfunction isRedirectResult(result: unknown): result is { redirect: Response } {\n return result !== null && typeof result === 'object' && 'redirect' in result\n}\n\nfunction sendRedirect(res: ServerResponse, result: { redirect: Response }): void {\n res.writeHead(302, { Location: result.redirect.headers.get('location') ?? '/' })\n res.end()\n}\n\nfunction send500(res: ServerResponse, custom500Html: string | null): void {\n if (!res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'text/html' })\n }\n if (!res.writableEnded) {\n res.end(custom500Html ?? '<h1>500 — Server Error</h1>')\n }\n}\n\n/**\n * Moves the route's `<title>`/`<meta>`/`<link>` from the rendered body into the head.\n *\n * `ctx.htmlHead` is the template up to and including `<div id=\"root\">`, so it still contains the\n * `</head>` this injects before. Without it, a route's metadata ships inside the body and only\n * reaches the head after hydration — which never happens for a crawler that does not run\n * JavaScript, and those are exactly the ones that render social cards.\n *\n * The dev middleware does the same thing; both paths have to, or previews work in one and not the\n * other, which is worse than neither.\n */\nexport function withHoistedHead(\n htmlHead: string,\n ssrHtml: string,\n nonce: string,\n): { head: string; body: string } {\n const { html, headTags } = extractHeadTags(ssrHtml)\n\n // The nonce is stamped onto the template's own inline scripts here, per request, because the\n // nonce differs per request while `ctx.htmlHead` is computed once at startup. Without it, a CSP\n // with a nonce blocks anything the template inlines — including the theme-init script that\n // applications put in `<head>` specifically to avoid a flash of the wrong theme on load.\n const head = applyNonceToInlineScripts(injectIntoHead(htmlHead, headTags), nonce)\n return { head, body: html }\n}\n\nfunction buildSsrHtml(\n ctx: RequestHandlerContext,\n result: string | SsrRenderResult,\n nonce: string,\n): string {\n if (typeof result === 'string') {\n const { head, body } = withHoistedHead(ctx.htmlHead, result, nonce)\n return head + body + ctx.htmlTail\n }\n if (isSsrRenderResult(result)) {\n const rendered = asSsrRenderResult(result)\n const dataJson = JSON.stringify(rendered.hydrationData).replace(/</g, '\\\\u003c')\n const hydrationScript = `<script${\n nonce ? ` nonce=\"${nonce}\"` : ''\n }>window.__staticRouterHydrationData=${dataJson}</script>`\n const { head, body } = withHoistedHead(ctx.htmlHead, rendered.html, nonce)\n return head + body + hydrationScript + ctx.htmlTail\n }\n return applyNonceToInlineScripts(ctx.htmlHead, nonce) + ctx.htmlTail\n}\n\nasync function handleSsrStreaming(\n ctx: RequestHandlerContext,\n req: IncomingMessage,\n res: ServerResponse,\n url: string,\n nonce: string,\n): Promise<boolean> {\n if (!ctx.ssrStreamingEnabled || !ctx.ssrRenderStreaming) return false\n\n const controller = new AbortController()\n const onClose = (): void => {\n controller.abort()\n }\n req.on('close', onClose)\n try {\n const result = await ctx.ssrRenderStreaming(url, res, {\n signal: controller.signal,\n nonce,\n // #343 — the streamed response carried neither of these, so `ssrStreaming: true`\n // served a bare React tree: no `<head>`, no client entry, no hydration data.\n //\n // `applyNonceToInlineScripts` and not `withHoistedHead`: hoisting reads the\n // RENDERED body for head elements, and nothing is rendered yet when the head\n // has to flush. Metadata hoisting under streaming is the same defect on a\n // different surface, and it is M9's, not this one's.\n htmlHead: applyNonceToInlineScripts(ctx.htmlHead, nonce),\n htmlTail: ctx.htmlTail,\n })\n if (isRedirectResult(result)) sendRedirect(res, result)\n return true\n } catch (streamErr) {\n console.error('[SSR Stream Error]', (streamErr as Error).message)\n send500(res, ctx.custom500Html)\n return true\n } finally {\n req.removeListener('close', onClose)\n }\n}\n\nasync function handleSsrSync(\n ctx: RequestHandlerContext,\n res: ServerResponse,\n url: string,\n nonce: string,\n): Promise<boolean> {\n if (!ctx.ssrRender) return false\n\n try {\n const result = await ctx.ssrRender(url, { nonce })\n if (isRedirectResult(result)) {\n sendRedirect(res, result)\n return true\n }\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(buildSsrHtml(ctx, result, nonce))\n return true\n } catch (ssrErr) {\n console.error('[SSR Error] Falling back to CSR:', (ssrErr as Error).message)\n return false\n }\n}\n\nfunction handleFatalError(ctx: RequestHandlerContext, res: ServerResponse, err: unknown): void {\n if (ctx.custom500Html && !res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'text/html' })\n res.end(ctx.custom500Html)\n } else if (!res.headersSent) {\n sendError(res, 'INTERNAL_ERROR', (err as Error).message, 500)\n } else {\n res.end()\n }\n}\n\nexport function createRequestHandler(\n ctx: RequestHandlerContext,\n): (req: IncomingMessage, res: ServerResponse) => void {\n return (req: IncomingMessage, res: ServerResponse) => {\n void (async () => {\n const url = req.url ?? '/'\n // #353 — production minted a fresh UUID on every request and discarded the\n // incoming W3C `traceparent`, so a trace crossing into this server started\n // over and no span could continue it. `theo dev` has honoured the header on\n // `/api/*` since Phase 7 (`vite-plugin/api-middleware.ts:368`); this is the\n // same resolution on the path a deploy actually serves.\n //\n // The tier-2 fallback (`x-request-id`) is caller-controlled and validated\n // inside `extractTraceId` before it is trusted — it ends up in the logs.\n const requestId = extractTraceId(req)\n const start = Date.now()\n // Echoed under both names, matching dev: `x-request-id` is what existing\n // consumers read, `x-trace-id` is the canonical one.\n res.setHeader('x-request-id', requestId)\n res.setHeader(TRACE_HEADER, requestId)\n\n // Preflight before anything else, and it answers rather than routing — the same order the\n // dev middleware uses (`vite-plugin/api-middleware.ts`), because an OPTIONS the router\n // handles is an OPTIONS the browser never gets a CORS answer to.\n if (ctx.corsHandler?.handlePreflight(req, res) === true) return\n ctx.corsHandler?.applyHeaders(req, res)\n\n const nonce = generateNonce()\n const securityHeaders = buildSecurityHeaders(\n ctx.securityHeadersConfig,\n { production: true },\n { nonce },\n )\n for (const [k, v] of Object.entries(securityHeaders)) {\n res.setHeader(k, v)\n }\n\n const handlerCtx = ctx.buildCtx(req, res, requestId, start)\n\n try {\n if (await tryServeReserved(ctx, url, res)) return\n if (await tryServeAction(handlerCtx)) return\n if (await tryServeAgentAux(handlerCtx)) return\n if (await tryServeAgent(handlerCtx)) return\n if (await tryServeApiRoute(handlerCtx)) return\n if (tryServeStatic(handlerCtx)) return\n if (tryServeCustom404(handlerCtx)) return\n\n if (await handleSsrStreaming(ctx, req, res, url, nonce)) return\n if (await handleSsrSync(ctx, res, url, nonce)) return\n\n // CSR fallback\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(ctx.indexHtml)\n } catch (err) {\n handleFatalError(ctx, res, err)\n }\n })()\n }\n}\n","/**\n * Resolve the address `server.listen` should bind, and say which it is.\n *\n * `config.host` was declared, defaulted to `'localhost'`, documented as the way to\n * open a server to the LAN, and never passed to `listen`. Node with no address\n * binds EVERY interface, so the production server listened wider than its\n * configuration said — and its default said the narrow thing.\n *\n * Fixing that broke containers, which is the second half of this story\n * (usetheokit/theokit#402). Inside a container `localhost` means *nobody*: the\n * image starts, prints a URL, and refuses every request including its own. So the\n * environment gets a say. `HOST` is the variable every container platform already\n * sets or expects to set, and honouring it costs the operator nothing to discover.\n *\n * Precedence, narrowest authority last: explicit config beats `HOST`, because a\n * value written into the project is a decision and an environment variable is a\n * deployment detail. Absent both, the loopback — binding every interface is a\n * decision someone should have to write down.\n *\n * The second export exists because the log lied. `theo start` printed `localhost`\n * whether it bound the loopback or every interface, so a container that serves\n * everyone and a container that serves nobody produced byte-identical output.\n * That is `docs/adr/0002-an-abnormal-ending-is-never-reported-as-normal.md` in the\n * startup path: the observable state must distinguish the two.\n */\n\nexport interface ListenTarget {\n /** The address handed to `server.listen`. */\n readonly host: string\n /** Where the value came from, so the log can say so rather than guess. */\n readonly source: 'config' | 'env' | 'default'\n}\n\nexport function resolveListenTarget(\n host: string | boolean | undefined,\n env: string | undefined = process.env.HOST,\n): ListenTarget {\n if (host === true) return { host: '0.0.0.0', source: 'config' }\n if (typeof host === 'string' && host !== '') return { host, source: 'config' }\n // `host: false` is an explicit \"do not open me up\" and outranks the environment.\n if (host !== false && env !== undefined && env !== '') return { host: env, source: 'env' }\n return { host: 'localhost', source: 'default' }\n}\n\n/**\n * The line `theo start` prints, given what it actually bound.\n *\n * `0.0.0.0` is not a URL a human can open, so the loopback is offered — but the\n * bound address is stated beside it, because \"every interface\" and \"this machine\n * only\" are the two states an operator most needs to tell apart, and they were\n * indistinguishable.\n */\nexport function describeListenTarget(target: ListenTarget, port: number): string {\n const url = target.host === '0.0.0.0' || target.host === '::' ? 'localhost' : target.host\n const bound =\n target.host === '0.0.0.0' || target.host === '::'\n ? `bound to ${target.host} (every interface)`\n : `bound to ${target.host} only`\n return ` → http://${url}:${String(port)} [${bound}${provenance(target.source)}]`\n}\n\n/** Where the address came from, and — when nobody chose it — what to write to change it. */\nfunction provenance(source: ListenTarget['source']): string {\n if (source === 'env') return ' from HOST'\n if (source === 'config') return ''\n return ' — set `host: true` in theo.config.ts or HOST=0.0.0.0 to reach it from outside this machine'\n}\n","/**\n * WebSocket upgrade handler for `theokit start` (T4.2 architecture-cleanup).\n *\n * Wires `server.on('upgrade')` for declared WS routes. Opt-in: only attached\n * when `wsRoutes.length > 0`. Lazy-imports `ws` package — throws an actionable\n * error when wsRoutes declared but `ws` not installed.\n */\n\nimport type { Server as HttpServer } from 'node:http'\n\nimport type * as WsLib from 'ws'\n\nimport type { WebSocketHandler } from '../../../server/define/define-websocket.js'\nimport type { LoadModule } from '../../../server/scan/module-loader.js'\nimport type { WebSocketRouteNode } from '../../../server/scan/ws-scan.js'\n\nexport async function attachWebSocketHandler(\n server: HttpServer,\n wsRoutes: WebSocketRouteNode[],\n loadModule: LoadModule,\n): Promise<void> {\n if (wsRoutes.length === 0) return\n\n let WebSocketServerCtor: typeof WsLib.WebSocketServer\n try {\n const wsModule = await import('ws')\n WebSocketServerCtor = wsModule.WebSocketServer\n } catch {\n throw new Error('WebSocket routes found but \"ws\" package is not installed. Run: npm install ws')\n }\n\n const wss = new WebSocketServerCtor({ noServer: true })\n\n server.on('upgrade', (request, socket, head) => {\n void (async () => {\n const url = request.url ?? '/'\n if (!url.startsWith('/ws/')) {\n socket.destroy()\n return\n }\n\n const wsPath = url.split('?')[0]\n const match = wsRoutes.find((r) => r.wsPath === wsPath)\n if (!match) {\n socket.destroy()\n return\n }\n\n try {\n const mod = await loadModule(match.filePath)\n const handler = ((mod as { default?: unknown }).default ?? mod) as WebSocketHandler\n\n wss.handleUpgrade(request, socket, head, (ws) => {\n handler.onOpen?.(ws, request)\n ws.on('message', (data: Buffer) => {\n handler.onMessage?.(ws, data.toString())\n })\n ws.on('close', (code: number, reason: Buffer) => {\n handler.onClose?.(ws, code, reason)\n })\n ws.on('error', (err: Error) => {\n handler.onError?.(ws, err)\n })\n })\n } catch {\n socket.destroy()\n }\n })()\n })\n}\n"],"mappings":"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","names":["existsSync","readFileSync","join","resolve","require","existsSync","readFileSync","resolve","existsSync","existsSync","extname","readFileSync","resolve","method","extname","resolve","existsSync","readFileSync","join"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "theokit",
|
|
3
|
-
"version": "0.65.0-next.
|
|
3
|
+
"version": "0.65.0-next.2",
|
|
4
4
|
"description": "The TheoKit web framework — file-based routing, typed server surfaces, the Vite plugin, the CLI and the deploy adapters, around agents served from agents/*.ts.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -150,12 +150,12 @@
|
|
|
150
150
|
"tsx": "^4.22.4",
|
|
151
151
|
"typescript": "^5.9.3",
|
|
152
152
|
"vite": "^7.0.0",
|
|
153
|
+
"@theokit/agents": "^13.0.0-next.1",
|
|
153
154
|
"@theokit/http": "^2.1.0-next.0",
|
|
154
|
-
"@theokit/
|
|
155
|
-
"@theokit/presenter": "^0.8.0"
|
|
155
|
+
"@theokit/presenter": "^0.9.0-next.0"
|
|
156
156
|
},
|
|
157
157
|
"peerDependencies": {
|
|
158
|
-
"@theokit/sdk": "^4.52.1",
|
|
158
|
+
"@theokit/sdk": "^4.52.1 || ^5.0.0",
|
|
159
159
|
"@theokit/ui": "^1.1.0",
|
|
160
160
|
"db0": "^0.3.0",
|
|
161
161
|
"react": "^19.0.0",
|
|
@@ -183,7 +183,7 @@
|
|
|
183
183
|
}
|
|
184
184
|
},
|
|
185
185
|
"devDependencies": {
|
|
186
|
-
"@theokit/sdk": "^4.52.1",
|
|
186
|
+
"@theokit/sdk": "^4.52.1 || ^5.0.0",
|
|
187
187
|
"@types/busboy": "^1.5.4",
|
|
188
188
|
"@types/picomatch": "^4.0.3",
|
|
189
189
|
"better-sqlite3": "^12.11.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/commands/start/index.ts","../src/server/cron/cron-runtime-node.ts","../src/server/define/health-route.ts","../src/server/rate-limit/client-ip.ts","../src/server/rate-limit/rate-limit-per-route.ts","../src/cli/commands/start/assert-sdk-compatible.ts","../src/server/agent/sdk-compat.ts","../src/cli/commands/start/bootstrap-stages.ts","../src/cli/commands/start/cron-bootstrap.ts","../src/cli/commands/start/graceful-shutdown.ts","../src/cli/commands/start/manifest-loader.ts","../src/cli/commands/start/public-exposure-gate.ts","../src/cli/commands/start/handlers.ts","../src/server/http/static.ts","../src/cli/commands/start/ssr-setup.ts","../src/cli/commands/start/request-handler.ts","../src/cli/commands/start/resolve-listen-host.ts","../src/cli/commands/start/websocket-handler.ts"],"sourcesContent":["/**\n * theokit start — production server orchestration spine.\n *\n * T4.2 (architecture-cleanup, ADR-0017): stages extracted to sibling modules.\n * - start-bootstrap-stages.ts — config/registry/storage bootstrap + resolveSsrEntry\n * - start-manifest-loader.ts — manifest.json or scan fallback\n * - start-ssr-setup.ts — SSR entry-server + HTML template split\n * - start-handlers.ts — branch handlers (action/route/static/404)\n * - start-request-handler.ts — request lifecycle wiring\n * - start-websocket-handler.ts — WS upgrade (opt-in)\n * - start-graceful-shutdown.ts — SIGTERM/SIGINT drain\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { createServer } from 'node:http'\nimport { join, resolve } from 'node:path'\n\nimport { loadConfig } from '../../../config/load-config.js'\nimport { loadEnv } from '../../../config/load-env.js'\nimport { resolvePluginSpecifiers } from '../../../config/resolve-plugin-specifiers.js'\nimport { initCacheEngineFromConfig } from '../../../server/cache-bootstrap.js'\nimport { createCronScheduler } from '../../../server/cron/cron-runtime-node.js'\nimport { defineHealthRoute } from '../../../server/define/health-route.js'\nimport { createCorsHandler } from '../../../server/http/cors.js'\nimport { createObservabilityPluginFromConfig } from '../../../server/observability-bootstrap.js'\nimport { createPluginRunnerFromConfig } from '../../../server/plugins/load-plugins.js'\nimport { createRouteRateLimiter } from '../../../server/rate-limit/rate-limit-per-route.js'\nimport { createProductionLoader } from '../../../server/scan/module-loader.js'\nimport { resolveTransformer } from '../../../server/transformer.js'\nimport { preflightNodeAndBindings } from '../../preflight-node-version.js'\nimport { CONTROLLER_MANIFEST_FILE } from '../build/emit-controllers.js'\n\nimport { assertSdkCompatible } from './assert-sdk-compatible.js'\nimport {\n configureAgentRegistryFromConfig,\n configureStorageManagerFromConfig,\n} from './bootstrap-stages.js'\nimport { loadCronDefinitions } from './cron-bootstrap.js'\nimport { installGracefulShutdown } from './graceful-shutdown.js'\nimport type { RequestHandlerCtx } from './handlers.js'\nimport { loadRoutesAndActions } from './manifest-loader.js'\nimport { assessPublicExposure } from './public-exposure-gate.js'\nimport { createRequestHandler } from './request-handler.js'\nimport { describeListenTarget, resolveListenTarget } from './resolve-listen-host.js'\nimport { setupSsr } from './ssr-setup.js'\nimport { attachWebSocketHandler } from './websocket-handler.js'\n\n// Backwards-compat: external test fixtures may import resolveSsrEntry from here.\nexport { resolveSsrEntry } from './bootstrap-stages.js'\n\ninterface StartOptions {\n port?: number\n}\n\nexport async function startCommand(options: StartOptions): Promise<void> {\n const cwd = process.cwd()\n // Preflight (FIRST — BEFORE anything that touches native bindings).\n preflightNodeAndBindings(cwd)\n loadEnv({ cwd, mode: 'production' })\n const config = await loadConfig(cwd)\n\n // M48 — fail fast if the installed @theokit/sdk is present but incompatible (before serving any\n // request). Absent SDK stays silent here (an api-only app is valid); the request path guards it lazily.\n assertSdkCompatible()\n\n await configureAgentRegistryFromConfig(config.agents?.registry)\n await configureStorageManagerFromConfig(config.storage)\n // #352 — without this, `revalidateTag` / `revalidatePath` / `updateTag` throw\n // in every application: they resolve the engine from a singleton nothing\n // initialized.\n await initCacheEngineFromConfig(config.cache)\n\n const distDir = resolve(cwd, '.theokit')\n const clientDir = resolve(distDir, 'client')\n // theokit#123 — compiled controllers, when `theokit build` emitted any.\n //\n // Keyed on the MANIFEST rather than on the directory existing: a stale `dist/controllers` left by\n // an earlier build whose sources were since deleted would otherwise keep serving routes the app\n // no longer declares. `theokit build` writes the manifest only when it compiles something, and\n // `cleanOutDir` removes both, so the manifest is the authoritative \"this build has controllers\".\n const controllersDistDir = existsSync(resolve(distDir, CONTROLLER_MANIFEST_FILE))\n ? resolve(distDir, 'controllers')\n : undefined\n // #95 — honor config `serverDir` (default \"server\") in production start, matching dev.\n const serverDir = resolve(cwd, config.serverDir)\n\n if (!existsSync(clientDir)) {\n throw new Error('No build found. Run `theo build` first.')\n }\n\n const indexHtml = readFileSync(join(clientDir, 'index.html'), 'utf-8')\n const loadModule = createProductionLoader()\n // `PORT` is what every container platform injects, and `theo start` read only\n // the config — so an image told to listen on the platform's port listened on\n // 3000 instead, and the platform's health check found nothing\n // (usetheokit/theokit#402). Explicit flag beats environment beats config: the\n // flag is a person typing now, the environment is where the process was put.\n const envPort = Number.parseInt(process.env.PORT ?? '', 10)\n const port = options.port ?? (Number.isInteger(envPort) ? envPort : undefined) ?? config.port\n // #353 — observability is registered FIRST when configured, so its span brackets\n // the user's own hooks. The honest cost of one ordered list: its `onResponse`\n // also runs first, so the span closes just before the tail of the chain. Head\n // coverage matters more — auth and rate-limit hooks live there.\n const observabilityPlugin = createObservabilityPluginFromConfig(config.observability, process.env)\n // #425 — a `plugins` entry MAY be a module specifier, so the same declaration the build bakes\n // into a deployed entry is the one this server registers. Constructed plugins pass through.\n const declaredPlugins = await resolvePluginSpecifiers(config.plugins ?? [], cwd)\n const pluginRunner = await createPluginRunnerFromConfig(\n observabilityPlugin === undefined ? declaredPlugins : [observabilityPlugin, ...declaredPlugins],\n )\n const transformer = resolveTransformer(config.serialization)\n\n const custom404Path = join(clientDir, '404.html')\n const custom500Path = join(clientDir, '500.html')\n const custom404Html = existsSync(custom404Path) ? readFileSync(custom404Path, 'utf-8') : null\n const custom500Html = existsSync(custom500Path) ? readFileSync(custom500Path, 'utf-8') : null\n\n const {\n routes: cachedRoutes,\n actions: cachedActions,\n wsRoutes: cachedWsRoutes,\n agents: cachedAgents,\n } = loadRoutesAndActions(distDir, serverDir, config.agentsDir)\n\n // `createRouteRateLimiter` accepts BOTH config shapes — it detects the legacy flat form and\n // treats it as the default bucket — so one call covers everything the schema allows.\n //\n // The previous code built a limiter only for the flat shape, on the belief that the per-route\n // variant was handled by an api-middleware path. No such path runs under `theokit start`, so a\n // per-route config produced `null` here and `handlers.ts` skipped limiting on every request. The\n // app booted clean, the config validated, and nothing was ever limited — see\n // usetheokit/theokit#321. A config that validates and then does nothing is worse than one that\n // fails loudly, because the operator has no reason to look.\n const rateLimiter = config.rateLimit ? createRouteRateLimiter(config.rateLimit) : null\n\n const ssr = await setupSsr({\n distDir,\n indexHtml,\n ssrConfigEnabled: config.ssr,\n ssrStreamingConfig: config.ssrStreaming,\n })\n\n const server = createServer(\n createRequestHandler({\n buildCtx: (req, res, requestId, startTime): RequestHandlerCtx => ({\n req,\n res,\n url: req.url ?? '/',\n requestId,\n startTime,\n clientDir,\n custom404Html,\n cachedRoutes,\n cachedActions,\n cachedAgents,\n loadModule,\n serverDir,\n projectRoot: cwd,\n controllersDistDir,\n pluginRunner,\n transformer,\n csrfMode: config.security?.csrf ?? 'strict',\n disallowed: config.security?.disallowed,\n rateLimiter,\n }),\n securityHeadersConfig: config.security?.headers ?? {},\n // #409 — built once at startup, like the security headers beside it. Declaring `cors` and\n // being served by this command used to mean no CORS at all, which reads in a browser as a\n // blocked fetch and in the config as a setting that is present and validated.\n corsHandler: config.security?.cors ? createCorsHandler(config.security.cors) : null,\n ssrRender: ssr.render,\n ssrRenderStreaming: ssr.renderStreaming,\n ssrStreamingEnabled: ssr.streamingEnabled,\n htmlHead: ssr.htmlHead,\n htmlTail: ssr.htmlTail,\n indexHtml,\n custom500Html,\n // M7-2: serve a built-in liveness route on the Node listener. Readiness\n // probe wiring from theo.config.ts is a documented follow-up (see the M7\n // implementation summary § Scope note).\n reservedRoutes: { health: defineHealthRoute() },\n }),\n )\n\n await attachWebSocketHandler(server, cachedWsRoutes, loadModule)\n\n // theokit#324: `theokit build --target node` announces an in-process\n // scheduler here. Drive it, or the announcement is false.\n const cronDefinitions = await loadCronDefinitions(resolve(distDir, 'crons.json'), cwd, loadModule)\n if (cronDefinitions.length > 0) {\n createCronScheduler(cronDefinitions).start()\n }\n\n // `config.host` was never passed here, and `listen(port)` with no address binds\n // every interface — so the server listened wider than its own configuration,\n // whose default says `localhost`. Passing it broke containers, where `localhost`\n // means nobody, so `HOST` now gets a say (usetheokit/theokit#402).\n const listenTarget = resolveListenTarget(config.host)\n\n // Deciding WHERE to listen settled reachability; this settles consequence. The refusal happens\n // BEFORE `listen` because a server that binds and then complains has already accepted the first\n // request — the log entry would arrive after the exposure it describes.\n const exposure = assessPublicExposure({\n routes: cachedRoutes,\n target: listenTarget,\n allowUnauthenticatedWrites: config.security?.allowUnauthenticatedWrites ?? false,\n // `cachedRoutes` comes from the file-route scan and never describes controllers. Without this\n // the gate reads an empty table as \"nothing is exposed\" and binds a public interface in silence,\n // while a controller marked `theokit:public` on a POST sits on it (theokit#543).\n hasControllers: controllersDistDir !== undefined,\n })\n if (exposure.kind === 'refused') {\n console.error(`\\n ${exposure.message}\\n`)\n // Non-zero: an orchestrator restarting this container must see a failure, not a clean exit that\n // reads as \"the process decided to stop\" (docs/adr/0002 — an abnormal ending is never reported\n // as normal).\n process.exitCode = 1\n return\n }\n if (exposure.kind === 'unverified') {\n console.warn(`\\n ${exposure.message}\\n`)\n }\n\n server.listen(port, listenTarget.host, () => {\n console.log(`\\n Theo production server`)\n // The line states the bound address, because it used to print `localhost`\n // either way — so a container serving everyone and one serving nobody were\n // indistinguishable in the log.\n console.log(`${describeListenTarget(listenTarget, port)}\\n`)\n if (exposure.kind === 'allowed-by-override') {\n // The override permits the exposure; it does not make it quiet. Each start names what is\n // open, so `allowUnauthenticatedWrites: true` cannot be forgotten in a config nobody reopens.\n const n = exposure.exposures.length\n console.warn(\n ` security.allowUnauthenticatedWrites is on — ${String(n)} unauthenticated write ${n === 1 ? 'route is' : 'routes are'} reachable:`,\n )\n for (const e of exposure.exposures) console.warn(` ${e.method} ${e.routePath}`)\n console.warn('')\n }\n if (cronDefinitions.length > 0) {\n console.log(` Crons: ${String(cronDefinitions.length)} scheduled in-process\\n`)\n }\n })\n\n installGracefulShutdown(server)\n}\n","import { CronExpressionParser } from 'cron-parser'\n\nimport { generateNewTraceContext } from '../observability/trace-context-propagation.js'\n\nimport type { CronContext, CronDefinition } from './cron-types.js'\n\n/**\n * In-memory cron scheduler for `theokit dev` (T1.4).\n *\n * Algorithm:\n * - For each cron, compute `nextFireAt = cron-parser.next()`.\n * - Schedule a `setTimeout(handler, nextFireAt - now)`.\n * - After handler invocation (sync return or Promise scheduled), recompute\n * next fire from CURRENT time (drift-free vs scheduled time).\n *\n * Per-cron isolation (EC-109):\n * - Each cron's handler invocation is fire-and-forget (`void` scheduled).\n * A hanging handler does NOT block the scheduler loop nor other crons.\n * - `concurrency: 'forbid'` (default) tracks an in-flight flag per-cron;\n * subsequent ticks skip + warn while the in-flight flag is set.\n * - `concurrency: 'allow'` runs handlers concurrently — caller's responsibility.\n *\n * Production deploys use platform-native triggers (T1.5 adapter translators);\n * this scheduler exists only for local dev iteration.\n */\n\nexport interface CronScheduler {\n start(): void\n stop(): void\n}\n\ninterface CronJobState {\n readonly def: CronDefinition\n inFlight: boolean\n timer: NodeJS.Timeout | null\n abortController: AbortController | null\n}\n\nexport function createCronScheduler(definitions: readonly CronDefinition[]): CronScheduler {\n const states: CronJobState[] = definitions.map((def) => ({\n def,\n inFlight: false,\n timer: null,\n abortController: null,\n }))\n\n let started = false\n\n const fireAndReschedule = (state: CronJobState, scheduledAt: Date): void => {\n state.timer = null\n\n if (state.inFlight && state.def.concurrency === 'forbid') {\n console.warn(\n `[theokit:cron] \"${state.def.name}\" skipped tick at ${scheduledAt.toISOString()} ` +\n '— previous handler still running (concurrency: forbid).',\n )\n scheduleNext(state)\n return\n }\n\n state.inFlight = true\n state.abortController = new AbortController()\n const traceCtx = generateNewTraceContext()\n const ctx: CronContext = {\n traceId: traceCtx.trace_id,\n scheduledAt,\n signal: state.abortController.signal,\n }\n\n // Fire-and-forget: EC-109 — never await here so a hanging handler\n // can't block the scheduler loop or other crons.\n void Promise.resolve()\n .then(() => state.def.handler(ctx))\n .catch((err: unknown) => {\n console.error(\n `[theokit:cron] \"${state.def.name}\" handler error:`,\n err instanceof Error ? err.message : err,\n )\n })\n .finally(() => {\n state.inFlight = false\n })\n\n scheduleNext(state)\n }\n\n const scheduleNext = (state: CronJobState): void => {\n if (!started) return\n const interval = CronExpressionParser.parse(state.def.schedule, {\n tz: 'UTC',\n currentDate: new Date(),\n })\n const next = interval.next().toDate()\n const delayMs = Math.max(0, next.getTime() - Date.now())\n state.timer = setTimeout(() => {\n fireAndReschedule(state, next)\n }, delayMs)\n // setTimeout returns a Timeout object; in Node, .unref() is available but\n // we DO want this to keep the event loop alive in dev — explicit no-unref.\n }\n\n return {\n start(): void {\n if (started) return\n started = true\n for (const state of states) {\n scheduleNext(state)\n }\n },\n stop(): void {\n started = false\n for (const state of states) {\n if (state.timer) {\n clearTimeout(state.timer)\n state.timer = null\n }\n state.abortController?.abort()\n state.abortController = null\n }\n },\n }\n}\n","/**\n * M7-2 — health/ready reserved routes for the convention/filesystem-route\n * server. Liveness (`/__theo/health`, always 200) and readiness\n * (`/__theo/ready`, 200/503 from a probe) are registered on a reserved\n * namespace BEFORE the user-route catch-all + 404 branch — mirroring nitro's\n * `/_nitro/*` reserved-namespace pattern (knowledge-base/references/nitro/src/\n * runtime/internal/routes/dev-tasks.ts). Liveness and readiness are kept\n * separate by design: liveness says \"the process is up\", readiness says\n * \"dependencies are up\".\n *\n * @public\n */\n\n/** Reserved path for the liveness endpoint. */\nexport const HEALTH_PATH = '/__theo/health'\n/** Reserved path for the readiness endpoint. */\nexport const READY_PATH = '/__theo/ready'\n\n/** Config produced by {@link defineHealthRoute}. */\nexport interface HealthRouteConfig {\n readonly kind: 'health'\n /** Returns the liveness body (auto-serialized to JSON). Default: `{ status: 'ok' }`. */\n readonly handler: () => unknown\n}\n\n/** Config produced by {@link defineReadyRoute}. */\nexport interface ReadyRouteConfig {\n readonly kind: 'ready'\n /** Resolves `true` when dependencies are ready. A throw/reject is treated as not-ready (503). */\n readonly probe: () => boolean | Promise<boolean>\n}\n\n/** The reserved-route registry consulted by {@link serveReservedRoute}. */\nexport interface ReservedRoutes {\n readonly health?: HealthRouteConfig\n readonly ready?: ReadyRouteConfig\n}\n\n/** A reserved-route response: an HTTP status + a JSON-serializable body. */\nexport interface ReservedResponse {\n readonly status: number\n readonly body: unknown\n}\n\n/**\n * Define the liveness route. The default handler returns `{ status: 'ok' }`.\n * Liveness is always 200 — it asserts the process is up, not that dependencies are.\n */\nexport function defineHealthRoute(\n handler: () => unknown = () => ({ status: 'ok' }),\n): HealthRouteConfig {\n return { kind: 'health', handler }\n}\n\n/**\n * Define the readiness route. The `probe` decides 200 (ready) vs 503 (not-ready).\n * A probe that throws/rejects is treated as not-ready (503) — never a 500 crash.\n */\nexport function defineReadyRoute(probe: () => boolean | Promise<boolean>): ReadyRouteConfig {\n return { kind: 'ready', probe }\n}\n\n/**\n * Pure dispatcher: map a request pathname to a reserved-route response, or\n * `null` when the path is not reserved (so the user catch-all + 404 take over).\n * Liveness defaults to 200 `{status:'ok'}`; readiness without a probe is\n * trivially ready (200). Registered BEFORE the user catch-all by the caller.\n */\nexport async function serveReservedRoute(\n pathname: string,\n routes: ReservedRoutes = {},\n): Promise<ReservedResponse | null> {\n if (pathname === HEALTH_PATH) {\n const handler = routes.health?.handler ?? (() => ({ status: 'ok' }))\n return { status: 200, body: handler() }\n }\n if (pathname === READY_PATH) {\n const probe = routes.ready?.probe\n if (probe === undefined) {\n return { status: 200, body: { status: 'ready' } }\n }\n try {\n const ready = await probe()\n return ready\n ? { status: 200, body: { status: 'ready' } }\n : { status: 503, body: { status: 'not-ready' } }\n } catch (err) {\n // 503 (the dependency is not ready) but NEVER swallow the cause — a\n // readiness probe failing silently is invisible to ops (project rule:\n // \"NEVER swallow exceptions\"). Log the cause, keep the 503 contract.\n console.error('[theo] readiness probe threw — treating as not-ready', err)\n return { status: 503, body: { status: 'not-ready' } }\n }\n }\n return null\n}\n","import type { IncomingMessage } from 'node:http'\n\n/**\n * Resolves the address a rate-limit bucket should be keyed on.\n *\n * ## Why this is not just `req.socket.remoteAddress`\n *\n * Behind any reverse proxy — Caddy, nginx, a load balancer, an ingress controller — the socket\n * address is the PROXY's, identical for every visitor on the internet. Keying buckets on it gives\n * the whole world one shared budget, so the first few requests each minute exhaust it and everyone\n * else is refused. That is worse than no limiting: a limit meant to stop one abusive client becomes\n * a denial of service any single client can trigger for everybody.\n *\n * ## Why it is opt-in\n *\n * `x-forwarded-for` is a request header, and a request header is whatever the client typed. Reading\n * it without being told to would let anyone bypass the limiter by rotating a forged value — a\n * one-line `curl -H`. So the default is to trust nothing and use the socket.\n *\n * ## Why the RIGHTMOST entry\n *\n * Each proxy APPENDS the address that connected to it. A client that sends `x-forwarded-for: 1.2.3.4`\n * through one proxy produces `1.2.3.4, <real client>` — the forgery lands on the left, and the entry\n * the trusted proxy wrote is last. Counting from the right therefore skips exactly the hops the\n * operator vouched for, and everything a client can influence stays to the left of where we look.\n *\n * This mirrors the `'trusted-proxy'` policy in `adapters/web-shim.ts`, which documents the same rule\n * for the Web-Request runtimes.\n */\n\n/**\n * How many proxies sit in front of the app.\n *\n * `false` (the default) trusts nothing and uses the socket address. `true` means one trusted proxy.\n * A number names the hop count for a longer chain — a CDN in front of your own proxy is `2`.\n */\nexport type TrustProxy = boolean | number\n\n/** `true` is shorthand for a single proxy; `false` for none. A number passes through. */\nfunction hopCount(trustProxy: TrustProxy): number {\n if (trustProxy === true) return 1\n if (trustProxy === false) return 0\n return trustProxy\n}\n\n/** Reads a header that Node may hand back as an array. */\nfunction headerValue(req: IncomingMessage, name: string): string | undefined {\n const raw = req.headers[name]\n if (Array.isArray(raw)) return raw[raw.length - 1]\n return raw\n}\n\n/**\n * The forwarded-header half of the decision, with no runtime in it.\n *\n * Extracted (usetheokit/theokit#612) so the Node path and the Web path cannot drift: a `Request`\n * has the same headers and no socket, and the arithmetic on those headers is the part that is easy\n * to get subtly wrong twice. What the two runtimes genuinely disagree about — where the fallback\n * address comes from — stays with each caller.\n *\n * @param header the raw `x-forwarded-for` value, if present\n * @param realIpHeader the raw `x-real-ip` value, if present\n * @param trustProxy how many proxies the operator vouched for\n * @returns the address to key on, or `undefined` when no trusted entry could be read\n *\n * Module-private: the two exported resolvers below are the whole surface, and a caller holding\n * the raw header arithmetic would be re-deciding the part that is easy to get wrong.\n */\nfunction addressFromForwardedHeaders(\n header: string | undefined,\n realIpHeader: string | undefined,\n trustProxy: TrustProxy,\n): string | undefined {\n const hops = hopCount(trustProxy)\n if (hops < 1) return undefined\n\n if (header) {\n const entries = header\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean)\n\n // Count in from the right by the number of hops the operator vouched for. A chain shorter than\n // configured means a request did NOT come through the expected proxies — a direct hit on the\n // app's port, say — so we report nothing rather than reading an entry the client could have\n // written.\n const index = entries.length - hops\n if (index >= 0 && index < entries.length) return entries[index]\n }\n\n // `x-real-ip` carries a single address and is written by the proxy, not appended to, so there is\n // no hop arithmetic to do. Only consulted when a proxy is trusted at all.\n const realIp = realIpHeader?.trim()\n return realIp === undefined || realIp === '' ? undefined : realIp\n}\n\nexport function resolveClientIp(req: IncomingMessage, trustProxy: TrustProxy = false): string {\n // `req.socket` is typed as always-present in Node typings; the optional chain keeps the fallback\n // reachable for test doubles built as plain object literals.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive for test doubles\n const socketAddress = req.socket?.remoteAddress ?? 'unknown'\n\n return (\n addressFromForwardedHeaders(\n headerValue(req, 'x-forwarded-for'),\n headerValue(req, 'x-real-ip'),\n trustProxy,\n ) ?? socketAddress\n )\n}\n\n/**\n * The Web-`Request` twin of {@link resolveClientIp}.\n *\n * There is no socket to fall back to, so the answer is `undefined` when no trusted proxy wrote an\n * address. A caller MUST treat that as \"cannot identify\" rather than substituting a constant:\n * bucketing every visitor under one placeholder turns a rate limiter into a shared budget the first\n * caller each window exhausts for the whole internet.\n */\nexport function resolveClientIpFromRequest(\n request: Request,\n trustProxy: TrustProxy = false,\n): string | undefined {\n return addressFromForwardedHeaders(\n request.headers.get('x-forwarded-for') ?? undefined,\n request.headers.get('x-real-ip') ?? undefined,\n trustProxy,\n )\n}\n","// T5a.1d — Web Crypto migration. The last node:crypto consumer in server/.\n// `createHash('sha256')` is sync but Web Crypto's `subtle.digest('SHA-256', ...)`\n// is async. This propagates through hashFragment → deriveKey → the factory's\n// returned checker, which is why the checker is async. `theokit start` awaits it\n// (usetheokit/theokit#321 — before that fix this factory had no production consumer at all, and a\n// per-route config silently disabled rate limiting outright). IncomingMessage stays as a type-only\n// import (TS-erased; runtime-clean).\nimport type { IncomingMessage } from 'node:http'\n\nimport { parseCookieHeader } from '../http/cookies.js'\n\nimport { resolveClientIp, type TrustProxy } from './client-ip.js'\nimport { InMemoryStore, type RateLimitStore } from './rate-limit-store.js'\nimport type { RateLimitConfig, RateLimitResult } from './rate-limit.js'\n\n/**\n * T2.2 — Per-route + per-user rate limiting.\n *\n * Layered on top of `rate-limit-store.ts`. The route map allows\n * declarative policies (\"strict /api/login, loose everything else\")\n * driven by config, not handler-decorated. `keyBy` selects what\n * identifier the limiter buckets on.\n *\n * ADR D2: per-route via path matching, NOT per-handler decorator.\n * Operators can tune policies without touching route definitions.\n */\n\nexport type KeyByMode = 'ip' | 'session' | 'user' | ((req: IncomingMessage) => string)\n\nexport interface RouteRateLimitConfig {\n /** Fallback config used when no per-route entry matches. */\n default?: RateLimitConfig\n /** Map of path pattern → config. Exact-string keys (RegExp via API). */\n routes?: Record<string, RateLimitConfig>\n /** Same as `routes` but each entry is a [pattern, config] tuple, RegExp allowed. */\n routePatterns?: readonly [string | RegExp, RateLimitConfig][]\n /** Bucket identifier strategy. Default 'ip'. */\n keyBy?: KeyByMode\n /** Cookie name used by keyBy='session'. Defaults to 'theo_session'. */\n cookieName?: string\n /** Optional shared store (for multi-route correlation). Default per-limiter InMemoryStore. */\n store?: RateLimitStore\n /**\n * How many reverse proxies sit in front of the app, for `keyBy: 'ip'`. Default `false` — trust\n * none and key on the socket address.\n *\n * Set this whenever the app is behind Caddy, nginx, a load balancer or an ingress controller:\n * without it every visitor keys on the proxy's address and shares one bucket, so a handful of\n * requests exhausts the budget for the entire internet. With it, the client address is read from\n * `x-forwarded-for` counting in from the right, past exactly the hops declared here.\n *\n * Leaving it off by default is deliberate — `x-forwarded-for` is client-writable, and honouring it\n * uninvited turns the limiter into a one-header bypass. See `client-ip.ts`.\n */\n trustProxy?: TrustProxy\n}\n\n/**\n * Normalize a path for matching: strip query string, drop trailing slash\n * unless root. EC-5: `/api/login` and `/api/login/` collapse to the same\n * canonical form so attackers can't bypass strict limits.\n */\nfunction normalizePath(input: string): string {\n const noQuery = input.split('?')[0]\n if (noQuery.length > 1 && noQuery.endsWith('/')) return noQuery.slice(0, -1)\n return noQuery\n}\n\n/**\n * Test whether `path` matches `pattern`. String patterns are compared\n * after trailing-slash normalization (EC-5). RegExp uses `.test` after\n * resetting `lastIndex` (defensive against `/g` flag).\n */\nexport function matchRoutePattern(path: string, pattern: string | RegExp): boolean {\n const canonical = normalizePath(path)\n if (typeof pattern === 'string') {\n return canonical === normalizePath(pattern)\n }\n pattern.lastIndex = 0\n return pattern.test(canonical)\n}\n\n/**\n * Hash a string with SHA-256 and return the first 16 base64url chars.\n * Used by `keyBy='session'` so the raw cookie value never lands in a\n * rate-limit key (which may flow into audit logs).\n *\n * T5a.1d — async via Web Crypto subtle.digest (no node:crypto). The\n * base64url encoding is done manually because btoa+url-safe transform is\n * available everywhere but `digest('base64url')` is Node-only.\n */\nasync function hashFragment(input: string): Promise<string> {\n const buf = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(input))\n const bytes = new Uint8Array(buf)\n let bin = ''\n for (const b of bytes) bin += String.fromCharCode(b)\n // eslint-disable-next-line sonarjs/slow-regex -- input is fixed-length 44 chars (SHA-256 base64), trailing '=' padding ≤ 2 chars, no ReDoS surface\n const b64url = btoa(bin).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n return b64url.slice(0, 16)\n}\n\n// T3.2 DRY consolidation: cookie parsing moved to ../http/cookies.ts.\n// The canonical `parseCookieHeader` returns a Map for O(1) lookup; this\n// wrapper preserves the original `readCookie(req, name)` signature so the\n// rest of the file stays untouched.\nfunction readCookie(req: IncomingMessage, name: string): string | undefined {\n return parseCookieHeader(req.headers.cookie ?? undefined).get(name)\n}\n\n/**\n * Build the rate-limit bucket key for the request based on `keyBy`.\n *\n * EC-6: session mode reads the configured `cookieName`. With the wrong\n * cookie name (e.g., default 'theo_session' but app uses 'app_session'),\n * we fall back to IP so anonymous users still get rate-limited rather\n * than sharing an empty bucket.\n */\nexport async function deriveKey(\n req: IncomingMessage,\n keyBy: KeyByMode,\n cookieName: string,\n trustProxy: TrustProxy = false,\n): Promise<string> {\n if (typeof keyBy === 'function') return keyBy(req)\n // Behind a proxy the socket address is the proxy's, the same for every visitor. `resolveClientIp`\n // returns it unchanged unless the operator declared how many proxies to trust — see its comment\n // for why reading `x-forwarded-for` uninvited would hand out a one-header bypass.\n const ip = resolveClientIp(req, trustProxy)\n switch (keyBy) {\n case 'session': {\n const cookie = readCookie(req, cookieName)\n return cookie ? `session:${await hashFragment(cookie)}` : `ip:${ip}`\n }\n case 'user': {\n const userId = (req as unknown as { user?: { id?: string } }).user?.id\n return userId ? `user:${userId}` : `ip:${ip}`\n }\n case 'ip':\n default:\n return `ip:${ip}`\n }\n}\n\n/**\n * Per-route rate limiter factory. Returns a sync checker compatible with\n * the existing api-middleware shape.\n *\n * Backwards-compatibility (ADR D2): a flat `{ windowMs, max }` config is\n * accepted and treated as `default` (no per-route variants).\n */\nexport function createRouteRateLimiter(config: RouteRateLimitConfig | RateLimitConfig) {\n // Detect legacy flat shape\n const isFlat =\n 'windowMs' in config && 'max' in config && !('default' in config) && !('routes' in config)\n const cfg: RouteRateLimitConfig = isFlat ? { default: config } : config\n\n const store = cfg.store ?? new InMemoryStore()\n // CR-005: validate store shape ONCE at construction. The previous\n // implementation ran `instanceof InMemoryStore` on every request and\n // threw at request-time if a non-InMemoryStore was passed — which\n // turned a clear config error into a runtime 500 on the first request.\n if (!(store instanceof InMemoryStore)) {\n throw new Error(\n 'createRouteRateLimiter: async RateLimitStore implementations require a dedicated async middleware path. ' +\n 'Use the InMemoryStore default for the sync facade.',\n )\n }\n const inMemoryStore = store\n const keyBy = cfg.keyBy ?? 'ip'\n const cookieName = cfg.cookieName ?? 'theo_session'\n const trustProxy = cfg.trustProxy ?? false\n\n // Build a pre-compiled list of (pattern, config) tuples for matching.\n const patternList: [string | RegExp, RateLimitConfig][] = []\n if (cfg.routes) {\n for (const [pattern, c] of Object.entries(cfg.routes)) patternList.push([pattern, c])\n }\n if (cfg.routePatterns) {\n for (const tuple of cfg.routePatterns) patternList.push(tuple)\n }\n\n return async function checkRouteRateLimit(req: IncomingMessage): Promise<RateLimitResult> {\n const url = req.url ?? ''\n let matched: RateLimitConfig | undefined\n for (const [pattern, c] of patternList) {\n if (matchRoutePattern(url, pattern)) {\n matched = c\n break\n }\n }\n const effective = matched ?? cfg.default\n if (!effective) {\n // No route match + no default → not limited.\n return { limited: false, headers: {} }\n }\n\n // Bucket key includes normalized path so /api/login and /api/login/\n // collapse to the same bucket (EC-5).\n const bucketSuffix = typeof matched === 'undefined' ? '*default*' : normalizePath(url)\n const key = `${await deriveKey(req, keyBy, cookieName, trustProxy)}|${bucketSuffix}`\n const state = inMemoryStore.incrSync(key, effective.windowMs)\n\n if (state.count > effective.max) {\n const retryAfter = Math.ceil((state.resetAt - Date.now()) / 1000)\n return {\n limited: true,\n headers: {\n 'X-RateLimit-Limit': String(effective.max),\n 'X-RateLimit-Remaining': '0',\n 'Retry-After': String(retryAfter),\n },\n }\n }\n return {\n limited: false,\n headers: {\n 'X-RateLimit-Limit': String(effective.max),\n 'X-RateLimit-Remaining': String(Math.max(0, effective.max - state.count)),\n },\n }\n }\n}\n\n/**\n * T5a.2 Phase D slice 1/3 — Web-Standards rate-limiter inputs context.\n *\n * Web `Request` has no equivalent of `req.socket.remoteAddress` (Node\n * runtime concept) or `req.user` (set by upstream middleware). The\n * Web-shaped rate-limiter requires the caller to pass these explicitly:\n *\n * - `clientIp` — resolved per-runtime (Node: `socket.remoteAddress`;\n * CF Workers: `request.headers.get('cf-connecting-ip')`; Vercel:\n * `x-forwarded-for` first hop; Bun/Deno: adapter-specific).\n * - `userId` — resolved by auth middleware (Phase D slice 3/3 ships\n * the Web-shaped session helper).\n *\n * Defaults: `clientIp = 'unknown'`, `userId = undefined` (matches the\n * IncomingMessage path's fallback semantics).\n */\nexport interface DeriveKeyRequestContext {\n clientIp?: string\n userId?: string\n}\n\n/**\n * T5a.2 Phase D slice 1/3 — Web-Standards-shaped key derivation.\n *\n * Mirror of `deriveKey(req: IncomingMessage, keyBy, cookieName)` for the\n * Web `Request` shape. Same `'ip' | 'session' | 'user'` enum cases (the\n * `function` callback case is IncomingMessage-only because the existing\n * `KeyByMode` callback type is Node-shaped; Web callers use the enum\n * cases or call a future `KeyByModeWeb` shape — out of T5a.2 Phase D scope).\n *\n * Uses `getCookieFromRequest` (Phase B slice 6/6) for session-mode cookie\n * lookup. Cookie parsing has the same CR-009 percent-encoding safety.\n */\nexport async function deriveKeyFromRequest(\n request: Request,\n keyBy: Exclude<KeyByMode, (req: IncomingMessage) => string>,\n cookieName: string,\n ctx: DeriveKeyRequestContext = {},\n): Promise<string> {\n const ip = ctx.clientIp ?? 'unknown'\n switch (keyBy) {\n case 'session': {\n // Reuse the Web cookie helper extracted in Phase B slice 6/6 so\n // CR-009 percent-encoding sanity stays consistent across paths.\n const { getCookieFromRequest } = await import('../http/cookies.js')\n const cookie = getCookieFromRequest(request, cookieName)\n return cookie ? `session:${await hashFragment(cookie)}` : `ip:${ip}`\n }\n case 'user': {\n return ctx.userId ? `user:${ctx.userId}` : `ip:${ip}`\n }\n case 'ip':\n default:\n return `ip:${ip}`\n }\n}\n\n/**\n * T5a.2 Phase D slice 1/3 — Web-Standards rate-limiter factory.\n *\n * Mirror of `createRouteRateLimiter(config)` returning a checker that\n * accepts `(request: Request, ctx?: DeriveKeyRequestContext)` instead of\n * `(req: IncomingMessage)`. Same `RouteRateLimitConfig` accepted; same\n * `keyBy` enum cases; same `InMemoryStore` constraint (CR-005 guard).\n *\n * Same returned `RateLimitResult` shape (headers + limited boolean).\n *\n * Web `Request` has no `req.url` path-only property — uses\n * `new URL(request.url).pathname + search` to derive the URL the way the\n * IncomingMessage path's `req.url ?? ''` would.\n */\nexport function createRouteRateLimiterWeb(config: RouteRateLimitConfig | RateLimitConfig) {\n const isFlat =\n 'windowMs' in config && 'max' in config && !('default' in config) && !('routes' in config)\n const cfg: RouteRateLimitConfig = isFlat ? { default: config } : config\n\n const store = cfg.store ?? new InMemoryStore()\n if (!(store instanceof InMemoryStore)) {\n throw new Error(\n 'createRouteRateLimiterWeb: async RateLimitStore implementations require a dedicated async middleware path. ' +\n 'Use the InMemoryStore default for the Web facade.',\n )\n }\n const inMemoryStore = store\n const keyBy = (cfg.keyBy ?? 'ip') as Exclude<KeyByMode, (req: IncomingMessage) => string>\n const cookieName = cfg.cookieName ?? 'theo_session'\n\n const patternList: [string | RegExp, RateLimitConfig][] = []\n if (cfg.routes) {\n for (const [pattern, c] of Object.entries(cfg.routes)) patternList.push([pattern, c])\n }\n if (cfg.routePatterns) {\n for (const tuple of cfg.routePatterns) patternList.push(tuple)\n }\n\n return async function checkRouteRateLimitWeb(\n request: Request,\n ctx: DeriveKeyRequestContext = {},\n ): Promise<RateLimitResult> {\n // Web Request guarantees absolute URL — extract path+query for pattern\n // matching (mirror of IncomingMessage's `req.url ?? ''`).\n const parsed = new URL(request.url)\n const url = `${parsed.pathname}${parsed.search}`\n let matched: RateLimitConfig | undefined\n for (const [pattern, c] of patternList) {\n if (matchRoutePattern(url, pattern)) {\n matched = c\n break\n }\n }\n const effective = matched ?? cfg.default\n if (!effective) {\n return { limited: false, headers: {} }\n }\n\n const bucketSuffix = typeof matched === 'undefined' ? '*default*' : normalizePath(url)\n const key = `${await deriveKeyFromRequest(request, keyBy, cookieName, ctx)}|${bucketSuffix}`\n const state = inMemoryStore.incrSync(key, effective.windowMs)\n\n if (state.count > effective.max) {\n const retryAfter = Math.ceil((state.resetAt - Date.now()) / 1000)\n return {\n limited: true,\n headers: {\n 'X-RateLimit-Limit': String(effective.max),\n 'X-RateLimit-Remaining': '0',\n 'Retry-After': String(retryAfter),\n },\n }\n }\n return {\n limited: false,\n headers: {\n 'X-RateLimit-Limit': String(effective.max),\n 'X-RateLimit-Remaining': String(Math.max(0, effective.max - state.count)),\n },\n }\n }\n}\n","/**\n * M48 (ecosystem-integration-guarantee) T3.2 — boot-time SDK compatibility fail-fast.\n *\n * The agent runtime is `@theokit/sdk` (G2). Today a missing / mis-versioned SDK is invisible until\n * the FIRST agent request, where `sdk-adapter.ts` yields a lazy `SDK_NOT_INSTALLED` stream event.\n * This runs at `theokit start` boot instead: if the installed SDK is present but outside the\n * supported range it throws a typed error naming found-vs-required; if it is absent (a legitimately\n * api-only app — the SDK is an OPTIONAL peer) it stays silent unless the caller marks it required.\n *\n * CLI/adapter layer → `node:*` is allowed here (unlike `server/`). The version range check itself is\n * the pure `server/agent/sdk-compat.ts` helper (one source of truth with the contract-test drift guard).\n */\nimport { readFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\n\nimport { SUPPORTED_SDK_RANGE, satisfiesSdkRange } from '../../../server/agent/sdk-compat.js'\n\n/** Typed error surfaced at boot when the installed `@theokit/sdk` is incompatible or absent-but-required. */\nexport class SdkIncompatibleError extends Error {\n readonly code = 'SDK_INCOMPATIBLE' as const\n readonly found: string | undefined\n readonly required: string\n constructor(found: string | undefined, required: string) {\n super(\n found === undefined\n ? `@theokit/sdk is required but not installed — run: pnpm add @theokit/sdk@${required}`\n : `@theokit/sdk ${found} does not satisfy the required range ${required} — run: pnpm add @theokit/sdk@${required}`,\n )\n this.name = 'SdkIncompatibleError'\n this.found = found\n this.required = required\n }\n}\n\n/** Resolve the version of the ACTUALLY-installed `@theokit/sdk`, or `undefined` when absent. */\nfunction defaultResolveSdkVersion(): string | undefined {\n try {\n const require = createRequire(import.meta.url)\n const pkgPath = require.resolve('@theokit/sdk/package.json')\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as { version?: string }\n return typeof pkg.version === 'string' ? pkg.version : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Fail fast at boot when the installed `@theokit/sdk` is incompatible.\n *\n * - present + in range → returns (silent)\n * - present + out of range → throws {@link SdkIncompatibleError} (found + required)\n * - absent + `required !== true` → returns (api-only app; the request path still guards lazily)\n * - absent + `required === true` → throws {@link SdkIncompatibleError}\n *\n * `resolveVersion` is injectable for tests (DIP); production uses {@link defaultResolveSdkVersion}.\n */\nexport function assertSdkCompatible(opts?: {\n required?: boolean\n resolveVersion?: () => string | undefined\n}): void {\n const version = (opts?.resolveVersion ?? defaultResolveSdkVersion)()\n if (version === undefined) {\n if (opts?.required === true) throw new SdkIncompatibleError(undefined, SUPPORTED_SDK_RANGE)\n return\n }\n if (!satisfiesSdkRange(version, SUPPORTED_SDK_RANGE)) {\n throw new SdkIncompatibleError(version, SUPPORTED_SDK_RANGE)\n }\n}\n","/**\n * M48 (ecosystem-integration-guarantee) — the supported `@theokit/sdk` version range + a pure,\n * dependency-free `||`-aware caret semver check. Shared by the contract test's version-drift guard\n * (`tests/integration/contract-sdk-seam.test.ts`) and the boot-time fail-fast\n * (`cli/commands/start/assert-sdk-compatible.ts`) — one source of truth for \"which SDK theokit runs on\".\n *\n * Web-Standards discipline (G8/R3a): `server/` code uses no `node:*`. This is pure string logic.\n * Rule 9 (Don't Reinvent) trade-off (ADR D1): a small inline caret checker instead of a `semver`\n * dependency, matching the theo-ui seam's existing precedent (`contract-usetheo-ui-vite-plugin.test.ts`).\n */\n\n/** The single source of truth for the supported SDK range. Mirrors the `package.json` peer floor. */\nexport const SUPPORTED_SDK_RANGE = '^4.0.1'\n\n// Bounded semver: three `\\d+` tuples + an optional `-tag.N` prerelease. No nested quantifiers →\n// no catastrophic backtracking; the security/detect-unsafe-regex heuristic false-positives here.\n// eslint-disable-next-line security/detect-unsafe-regex -- bounded \\d+ groups anchored by `.`/`$`, no backtracking\nconst SEMVER_RE = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([a-z]+)\\.(\\d+))?$/\n\ninterface Semver {\n major: string\n minor: string\n patch: string\n tag: string | undefined\n pre: string | undefined\n}\n\nfunction parseSemver(value: string): Semver | null {\n const m = SEMVER_RE.exec(value)\n if (!m) return null\n return { major: m[1], minor: m[2], patch: m[3], tag: m[4], pre: m[5] }\n}\n\n/**\n * Return `true` when `version` satisfies `range`, where `range` is a `||`-joined series of caret\n * pins (e.g. `^4.0.1` or `^0.14.0 || ^1.0.0`). A version satisfies the range when it satisfies ANY\n * clause. Covers the semver subset the monorepo uses (caret pins, optional `-tag.N` prerelease);\n * no range-sets or build metadata.\n */\nexport function satisfiesSdkRange(version: string, range: string): boolean {\n return range\n .split('||')\n .map((clause) => clause.trim())\n .some((clause) => satisfiesSingleCaret(version, clause))\n}\n\nfunction satisfiesSingleCaret(version: string, range: string): boolean {\n if (!range.startsWith('^')) return false\n const pin = parseSemver(range.slice(1))\n const ver = parseSemver(version)\n if (!pin || !ver) return false\n if (pin.tag !== undefined) return satisfiesPrereleaseCaret(ver, pin)\n if (ver.tag !== undefined) return false // a prerelease never satisfies a stable caret pin\n return satisfiesStableCaret(ver, pin)\n}\n\nfunction satisfiesPrereleaseCaret(ver: Semver, pin: Semver): boolean {\n // Prerelease pin: same X.Y.Z + same tag + prerelease number >= pin's.\n if (pin.major !== ver.major || pin.minor !== ver.minor || pin.patch !== ver.patch) return false\n if (ver.tag !== pin.tag) return false\n return Number(ver.pre ?? '0') >= Number(pin.pre ?? '0')\n}\n\nfunction satisfiesStableCaret(ver: Semver, pin: Semver): boolean {\n if (pin.major !== ver.major) return false\n if (pin.major === '0') {\n // 0.X.Y caret: minor must match exactly; patch can be >=.\n if (pin.minor !== ver.minor) return false\n return Number(ver.patch) >= Number(pin.patch)\n }\n // >= 1.0.0 caret: minor can be >=, patch anything within the same/greater minor.\n if (Number(ver.minor) > Number(pin.minor)) return true\n if (Number(ver.minor) === Number(pin.minor)) return Number(ver.patch) >= Number(pin.patch)\n return false\n}\n","/**\n * Bootstrap stages extracted from `start.ts` per T4.2 (architecture-cleanup, ADR-0017).\n *\n * The full goal of T4.2 is a ≤30-LOC `startCommand` spine + 6-8 stage files. This\n * file ships the first batch: the configure-from-config bootstrap helpers + the\n * SSR entry resolver. They are stand-alone, side-effect-free at module load, and\n * already individually testable.\n *\n * Remaining stages (request-handler extraction, graceful-shutdown extraction,\n * signal-handlers extraction) are deferred to a follow-up sprint — see plan\n * ks.\n */\n\nimport { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport { warnOnce } from '../../../server/observability/logger.js'\n\ninterface SdkAgentRegistry {\n configure?: (opts: { maxAgents?: number; idleTimeoutMs?: number }) => void\n}\ninterface SdkModule {\n Agent?: { registry?: SdkAgentRegistry }\n}\n\n/**\n * Configure SDK's Agent.registry from `theo.config.ts > agents.registry`.\n * Lazy at boot; EC-3 sync flag flip prevents race under concurrent boot.\n * Silent no-op when registry config is absent or SDK is uninstalled.\n */\nexport async function configureAgentRegistryFromConfig(\n registryConfig: { maxAgents: number; idleTimeoutMs: number } | undefined,\n): Promise<void> {\n if (registryConfig === undefined) return\n try {\n const sdk = (await import('@theokit/sdk').catch(() => null)) as SdkModule | null\n const sdkConfigure = sdk?.Agent?.registry?.configure\n if (sdkConfigure === undefined) return\n const { configureAgentRegistryOnce } =\n await import('../../../server/agent/configure-agent-registry.js')\n configureAgentRegistryOnce(\n {\n configure: (opts) => {\n sdkConfigure(opts)\n },\n },\n registryConfig,\n )\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n warnOnce('bootstrap.agent_registry_skip', {\n event: 'bootstrap.agent_registry_skip',\n message: msg,\n })\n }\n}\n\n/**\n * Configure the StorageManager from `theo.config.ts > storage` (ADR-0007).\n * Manager enforces configure-once internally (D3); this helper bridges the\n * config to the singleton with actionable error handling.\n */\nexport async function configureStorageManagerFromConfig(storageConfig: unknown): Promise<void> {\n if (storageConfig === undefined || storageConfig === null) return\n try {\n const { getStorageManager } = await import('../../../server/storage/storage-manager.js')\n const { storageSchema } = await import('../../../config/schema.js')\n // Re-validate at boot so a malformed config from a non-Zod source\n // (test fixtures, dynamic configs) surfaces a clear error early.\n const parsed = storageSchema.parse(storageConfig)\n getStorageManager().configure(parsed)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n warnOnce('bootstrap.storage_skip', {\n event: 'bootstrap.storage_skip',\n message: msg,\n })\n }\n}\n\nconst SSR_EXTENSIONS = ['.mjs', '.js'] as const\n\n/**\n * Resolve the SSR entry-server module path. tsup may emit `.mjs` or `.js`\n * depending on output format. Try `.mjs` first (modern default) then fall\n * back to `.js`. Returns null when neither exists — SSR stays disabled.\n *\n * Exported so unit tests can pin the resolution order without booting the\n * full CLI.\n */\nexport function resolveSsrEntry(distDir: string): string | null {\n for (const ext of SSR_EXTENSIONS) {\n const path = resolve(distDir, `server/entry-server${ext}`)\n\n if (existsSync(path)) return path\n }\n return null\n}\n","import { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport type { CronManifest } from '../../../server/cron/cron-manifest.js'\nimport type { CronDefinition } from '../../../server/cron/cron-types.js'\n\n/**\n * Reads the cron manifest `theokit build` writes and re-loads each handler so\n * `theokit start` can drive the in-process scheduler.\n *\n * The build prints \"Cron → in-process scheduler (theokit start)\" for\n * `target: node`, but nothing on the serving side ever read `dist/crons.json`\n * — the crons were declared, validated, written, and then never ran\n * (theokit#324).\n *\n * The manifest names WHICH files hold a cron; the definition itself is taken\n * from the module, which is the source `defineCron` produced. Trusting the\n * manifest's copy of the schedule would let a stale build silently run a cron\n * on the wrong cadence.\n */\nexport async function loadCronDefinitions(\n manifestPath: string,\n projectRoot: string,\n loadModule: (filePath: string) => unknown,\n): Promise<CronDefinition[]> {\n if (!existsSync(manifestPath)) return []\n\n const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8')) as CronManifest\n const definitions: CronDefinition[] = []\n\n for (const entry of manifest.crons) {\n const filePath = resolve(projectRoot, entry.filePath)\n const mod = (await loadModule(filePath)) as { default?: unknown }\n const exported = mod.default\n\n if (!isCronDefinition(exported)) {\n throw new Error(\n `Cron \"${entry.name}\" declared in \"${entry.filePath}\" is missing a valid default export. ` +\n 'Expected `export default defineCron(name, { schedule, handler })`. ' +\n 'Re-run `theokit build` if the file changed since the last build.',\n )\n }\n\n definitions.push(exported)\n }\n\n return definitions\n}\n\nfunction isCronDefinition(value: unknown): value is CronDefinition {\n if (typeof value !== 'object' || value === null) return false\n const candidate = value as Record<string, unknown>\n return (\n typeof candidate.name === 'string' &&\n typeof candidate.schedule === 'string' &&\n typeof candidate.handler === 'function'\n )\n}\n","/**\n * Graceful shutdown stage for `theokit start` (T4.2 architecture-cleanup,\n * ADR-0007 D6 — SIGTERM evicts agents + drains StorageManager).\n *\n * EC-13: SIGTERM evicts agents IMMEDIATELY (no per-request drain). In-flight\n * requests get aborted mid-stream — acceptable because the platform LB\n * removed this pod from rotation BEFORE sending SIGTERM (K8s preStop hook +\n * terminationGracePeriodSeconds; same on Vercel/CF/Render).\n *\n * Re-entry guard: multiple SIGTERMs in quick succession run shutdown ONCE.\n */\n\nimport type { Server as HttpServer } from 'node:http'\n\nimport { warnOnce } from '../../../server/observability/logger.js'\n\nexport function installGracefulShutdown(server: HttpServer): void {\n let shuttingDown = false\n const shutdown = (signal: NodeJS.Signals): void => {\n if (shuttingDown) return\n shuttingDown = true\n console.log(`\\n [theokit] ${signal} received — evicting agents`)\n void (async () => {\n // Lazy-import SDK only at shutdown time to avoid forcing the dep on\n // apps that don't use agents at all.\n try {\n const sdk = (await import('@theokit/sdk').catch(() => null)) as {\n Agent?: { registry?: { evictAll?: () => Promise<void> } }\n } | null\n if (sdk?.Agent?.registry?.evictAll !== undefined) {\n await sdk.Agent.registry.evictAll()\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n warnOnce('shutdown.evict_error', {\n event: 'shutdown.evict_error',\n message: msg,\n })\n }\n // T3.1 — drain the StorageManager AFTER agent eviction. Order matters:\n // agents may still hold open pool refs while evicting; closing pools\n // first would break in-flight queries.\n try {\n const { getStorageManager } = await import('../../../server/storage/storage-manager.js')\n const manager = getStorageManager()\n await manager.dispose()\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n warnOnce('shutdown.dispose_error', {\n event: 'shutdown.dispose_error',\n message: msg,\n })\n }\n // #353 — flush telemetry LAST, so the spans covering eviction and the\n // storage drain are in the batch that leaves. Without this the exporter's\n // final buffer dies with the process, and the most interesting spans a\n // deploy produces — the ones from the shutdown itself — are the ones\n // guaranteed never to arrive.\n try {\n const { getObservabilityAdapter } =\n await import('../../../server/observability-bootstrap.js')\n await getObservabilityAdapter()?.shutdown()\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n warnOnce('shutdown.observability_error', {\n event: 'shutdown.observability_error',\n message: msg,\n })\n }\n console.log(` [theokit] shutdown complete`)\n server.close(() => {\n process.exit(0)\n })\n setTimeout(() => {\n warnOnce('shutdown.forced_exit', {\n event: 'shutdown.forced_exit',\n message: 'forced exit after 25s timeout',\n })\n process.exit(0)\n }, 25_000).unref()\n })()\n }\n process.on('SIGTERM', () => {\n shutdown('SIGTERM')\n })\n process.on('SIGINT', () => {\n shutdown('SIGINT')\n })\n}\n","/**\n * Manifest loading stage for `theokit start` (T4.2 architecture-cleanup).\n *\n * Loads pre-built manifest from `.theokit/manifest.json` if present; otherwise\n * scans server/ for routes/actions/ws at startup with a structured warn.\n */\n\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { dirname } from 'node:path'\n\nimport { warnOnce } from '../../../server/observability/logger.js'\nimport type { ActionNode } from '../../../server/scan/action-scan.js'\nimport { scanServerActions } from '../../../server/scan/action-scan.js'\nimport type { AgentNode } from '../../../server/scan/agent-scan.js'\nimport { scanAgents } from '../../../server/scan/agent-scan.js'\nimport { loadManifest } from '../../../server/scan/manifest.js'\nimport type { ServerRouteNode } from '../../../server/scan/match.js'\nimport { scanServerRoutes } from '../../../server/scan/scan.js'\nimport type { WebSocketRouteNode } from '../../../server/scan/ws-scan.js'\nimport { scanWebSocketRoutes } from '../../../server/scan/ws-scan.js'\n\ninterface LoadedRoutes {\n routes: ServerRouteNode[]\n actions: ActionNode[]\n wsRoutes: WebSocketRouteNode[]\n agents: AgentNode[]\n}\n\nexport function loadRoutesAndActions(\n distDir: string,\n serverDir: string,\n // #95 follow-up — agents dir name (config `agentsDir`, default \"agents\") for the live-scan fallback.\n agentsDir = 'agents',\n): LoadedRoutes {\n const manifestPath = join(distDir, 'manifest.json')\n\n if (existsSync(manifestPath)) {\n const manifest = loadManifest(distDir, serverDir)\n return {\n routes: manifest.routes,\n actions: manifest.actions,\n wsRoutes: manifest.websockets,\n agents: manifest.agents,\n }\n }\n warnOnce('bootstrap.manifest_not_found', {\n event: 'bootstrap.manifest_not_found',\n message:\n 'No manifest found, scanning routes at startup. Run \"theo build\" to generate manifest.',\n serverDir,\n })\n return {\n routes: scanServerRoutes(serverDir),\n actions: scanServerActions(serverDir),\n wsRoutes: scanWebSocketRoutes(serverDir),\n // Agents live at <projectRoot>/<agentsDir>; projectRoot = serverDir's parent for the canonical layout.\n agents: scanAgents(dirname(serverDir), agentsDir),\n }\n}\n","/**\n * Refuse to put unauthenticated write routes on a public network interface.\n *\n * `resolve-listen-host.ts` settled WHICH address gets bound and made the log say so. This settles\n * whether that address should be bound at all, given what is behind it. The two are deliberately\n * separate: one is about reachability, this one is about consequence.\n *\n * The gap it closes has a shape worth naming. ADR 0001 made every route declare who may call it and\n * stopped absence from meaning open — a real improvement, and an incomplete one, because `'public'`\n * is a declaration too. A route table where every entry says `policy('public')` passes the build\n * gate perfectly and is, in substance, a table nobody protected. Nothing downstream could tell the\n * two apart, since the policy value never left the module; `detectRoutePolicyKinds` is what made it\n * legible at scan time, and this is the first thing to act on it.\n *\n * ## What it refuses, and what it does not\n *\n * Refused: a non-loopback bind while at least one POST / PUT / PATCH / DELETE declares the literal\n * `'public'`. Those are the requests that spend money, mutate state, or send mail on the operator's\n * behalf, and an unauthenticated one reachable from a network is the shape of an open relay.\n *\n * NOT refused: public GET / HEAD / OPTIONS. Public read endpoints are ordinary — health checks,\n * catalogues, landing APIs — and a gate that fired on them would be switched off within a day. A\n * gate with a stated edge is worth more than a gate nobody runs. That means this does NOT protect\n * against a public GET that leaks data; that is authorization work the policy function must do, and\n * saying so here is cheaper than letting an operator infer a guarantee that was never offered.\n *\n * ## Why absence is not safety\n *\n * A manifest built before this existed carries no `publicMethods`, and reading that silence as\n * \"nothing is public\" would be the same defect the gate exists to prevent, one field over. The\n * verdict is `unverified`: the server still starts (an upgrade must not break a running deploy),\n * and the operator is told plainly that the check did not run and what to do about it.\n */\nimport type { ServerRouteNode } from '../../../server/scan/match.js'\n\nimport type { ListenTarget } from './resolve-listen-host.js'\n\n/**\n * Methods that change something. The rest are readable requests, out of scope by the reasoning in\n * the module docblock.\n */\nconst MUTATING_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\n/** Addresses that reach only this machine. */\nconst LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1'])\n\n/** One unauthenticated write, named so the operator can go and look at it. */\nexport interface Exposure {\n readonly routePath: string\n readonly method: string\n}\n\nexport type ExposureVerdict =\n /** Bound to the loopback — nothing outside this machine can reach it, so there is nothing to judge. */\n | { readonly kind: 'not-exposed' }\n /** Public bind, and every mutating route is guarded. */\n | { readonly kind: 'allowed' }\n /** Public bind with unauthenticated writes, permitted by an explicit written decision. */\n | { readonly kind: 'allowed-by-override'; readonly exposures: readonly Exposure[] }\n /** Public bind with unauthenticated writes and no override. The server must not start. */\n | { readonly kind: 'refused'; readonly exposures: readonly Exposure[]; readonly message: string }\n /** Public bind, and the route table cannot answer the question. Starts, says so. */\n | { readonly kind: 'unverified'; readonly message: string }\n\nexport interface ExposureAssessment {\n readonly routes: readonly ServerRouteNode[]\n readonly target: ListenTarget\n /** `security.allowUnauthenticatedWrites` from theo.config.ts — an explicit, written decision. */\n readonly allowUnauthenticatedWrites: boolean\n /**\n * Whether this build emitted compiled controllers, which `routes` does not describe.\n *\n * The caller already knows: `start` resolves `dist/controllers.json` to decide whether to serve\n * them at all. Passing the fact in is what lets an empty `routes` mean two different things —\n * \"this app serves nothing\", which is safe to bind, and \"this app serves through controllers the\n * manifest does not list\", which is not judged here at all.\n *\n * `undefined` is read as unknown, not as false. Distinguishing them is the point of the field, and\n * a caller that has not been updated should not be answered with confidence it did not supply.\n */\n readonly hasControllers?: boolean\n}\n\n/** Does this address reach anything beyond this machine? */\nfunction isPubliclyBound(host: string): boolean {\n return !LOOPBACK_HOSTS.has(host.toLowerCase())\n}\n\nfunction unauthenticatedWrites(routes: readonly ServerRouteNode[]): Exposure[] {\n const found: Exposure[] = []\n for (const route of routes) {\n for (const method of route.publicMethods ?? []) {\n if (MUTATING_METHODS.has(method)) found.push({ routePath: route.routePath, method })\n }\n }\n return found\n}\n\n/**\n * Why the gate cannot judge this route table, or `null` when it can.\n *\n * Two different absences reach here, and telling them apart matters because the operator's next\n * move differs. Returning a REASON rather than a boolean is what keeps the message honest: a\n * warning that prescribes `theo build` to someone whose build is fine teaches them to ignore it.\n *\n * - `stale-manifest` — routes exist and declare mutating methods, but none carries `publicMethods`.\n * The signature of a manifest built before this check existed. Regenerating it answers the\n * question.\n *\n * - `empty-table` — the manifest describes no routes at all. Regenerating it changes nothing: the\n * scan behind that array reads `server/routes/`, and an app serving through controllers has none\n * (usetheokit/theokit#543). Measured on a nine-controller app: sixteen routes served, `routes: []`\n * written.\n *\n * This one was silently `allowed` until 2026-08-28. `.some()` on an empty array is false for both\n * questions above, so the gate concluded there was nothing to expose and bound a public interface\n * without a word — while a controller declaring `@SetMetadata('theokit:public', true)` on a POST\n * sat on it. Absence of a description is not absence of exposure, which is the sentence this whole\n * file is written around.\n */\nfunction whyCannotAnswer(\n routes: readonly ServerRouteNode[],\n hasControllers: boolean | undefined,\n): 'stale-manifest' | 'empty-table' | null {\n if (routes.length === 0) {\n // An app that serves nothing is safe to bind anywhere, and warning about it is the kind of\n // noise that gets a gate switched off. Only an empty table that is NOT the whole story warrants\n // a word — controllers present, or a caller that did not say.\n return hasControllers === false ? null : 'empty-table'\n }\n if (routes.some((r) => r.publicMethods !== undefined)) return null\n return routes.some((r) => (r.methods ?? []).some((m) => MUTATING_METHODS.has(m)))\n ? 'stale-manifest'\n : null\n}\n\nfunction unverifiedMessage(reason: 'stale-manifest' | 'empty-table', host: string): string {\n const head = `Binding ${host} without checking whether its write routes are authenticated.`\n return reason === 'stale-manifest'\n ? [\n head,\n ' This manifest predates the check and records no policy kinds. Run `theo build` to',\n ' regenerate it; until then the server starts, and this warning is the whole of what',\n ' is known about the exposure.',\n ].join('\\n')\n : [\n head,\n ' The manifest describes no routes, so there is nothing here to judge. If this app serves',\n ' through controllers, that is expected and rebuilding will not change it — the route scan',\n ' reads `server/routes/` only (usetheokit/theokit#543). Whatever your controllers mark',\n ' `theokit:public` on a POST/PUT/PATCH/DELETE is reachable from this address, and this',\n ' warning is the whole of what is known about it.',\n ].join('\\n')\n}\n\nfunction refusalMessage(exposures: readonly Exposure[], host: string): string {\n const list = exposures.map((e) => ` ${e.method} ${e.routePath}`).join('\\n')\n return [\n exposures.length === 1\n ? `Refusing to bind ${host}: 1 write route accepts unauthenticated requests.`\n : `Refusing to bind ${host}: ${String(exposures.length)} write routes accept unauthenticated requests.`,\n '',\n list,\n '',\n \" Each declares `policy('public')`, so anyone who can reach this address can call it.\",\n ' On a loopback bind that is a demo; on this one it is an open endpoint.',\n '',\n ' Resolve it one of two ways:',\n ' • give each route a real policy — `policy(({ subject }) => subject !== null)`, or',\n ' `requireOwner(subject, record.ownerId)` from `theokit/server/define`. A plugin hook',\n ' establishes `ctx.subject`; the policy reads it.',\n ' • decide otherwise, in writing: `security: { allowUnauthenticatedWrites: true }` in',\n ' theo.config.ts. The routes stay open and the startup log keeps saying so.',\n ].join('\\n')\n}\n\n/**\n * Judge a route table against the address about to be bound.\n *\n * Pure — it reads, it decides, it returns. The caller prints and exits, which keeps this testable\n * without a process and keeps the exit policy in one place.\n */\nexport function assessPublicExposure(input: ExposureAssessment): ExposureVerdict {\n if (!isPubliclyBound(input.target.host)) return { kind: 'not-exposed' }\n\n const blind = whyCannotAnswer(input.routes, input.hasControllers)\n if (blind !== null) {\n return { kind: 'unverified', message: unverifiedMessage(blind, input.target.host) }\n }\n\n const exposures = unauthenticatedWrites(input.routes)\n if (exposures.length === 0) return { kind: 'allowed' }\n if (input.allowUnauthenticatedWrites) return { kind: 'allowed-by-override', exposures }\n\n return { kind: 'refused', exposures, message: refusalMessage(exposures, input.target.host) }\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\nimport { extname } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\nimport type { RouteSubject } from '../../../core/contracts/route-policy.js'\nimport { readAgentPolicy } from '../../../server/agent/agent-access.js'\nimport { getApprovalRegistry } from '../../../server/agent/approval-registry.js'\nimport {\n handleAgentApproval,\n isApprovalPath,\n parseApprovalAgentName,\n} from '../../../server/agent/approve-agent.js'\nimport { mountAgent } from '../../../server/agent/mount-agent.js'\nimport { resolveProvider } from '../../../server/agent/provider-resolver.js'\nimport { matchAgentAuxRoute, serveMatchedAuxRoute } from '../../../server/agent/serve-aux-routes.js'\nimport { executeAction } from '../../../server/http/action-execute.js'\nimport { dispatchControllerRequest } from '../../../server/http/controller-dispatch.js'\nimport { executeRoute } from '../../../server/http/execute.js'\nimport { createWebRequestSource } from '../../../server/http/node-request.js'\nimport { writeWebResponseToServerResponse } from '../../../server/http/node-web-adapter.js'\nimport { serveThroughPluginLifecycle } from '../../../server/http/plugin-lifecycle.js'\nimport { createAgentSubjectResolver } from '../../../server/http/resolve-agent-subject.js'\nimport { sendError } from '../../../server/http/send-response.js'\nimport { serveStaticFile } from '../../../server/http/static.js'\nimport { logRequest } from '../../../server/observability/logger.js'\nimport { findSuggestion } from '../../../server/observability/suggest.js'\nimport type { PluginRunner } from '../../../server/plugins/plugin-runner.js'\nimport type { ActionNode } from '../../../server/scan/action-scan.js'\nimport type { AgentNode } from '../../../server/scan/agent-scan.js'\nimport { matchRoute } from '../../../server/scan/match.js'\nimport type { ServerRouteNode } from '../../../server/scan/match.js'\nimport type { LoadModule } from '../../../server/scan/module-loader.js'\nimport type { CsrfMode, DisallowedConfig } from '../../../server/security/csrf.js'\nimport type { TheoTransformer } from '../../../server/transformer.js'\n\n/**\n * Load a controller module that `theokit build` already compiled — theokit#123.\n *\n * A plain dynamic `import()`, deliberately: production must not need `@swc/core`. That peer is a\n * native binary the app would otherwise carry solely to re-do work the build already did, and a\n * missing optional peer would degrade into a runtime 404 instead of a build failure.\n */\nasync function loadCompiledController(absPath: string): Promise<Record<string, unknown>> {\n return (await import(pathToFileURL(absPath).href)) as Record<string, unknown>\n}\n\n/** Response header carrying the per-request correlation id. */\nconst X_REQUEST_ID = 'x-request-id'\n\n/**\n * T6.1 (PV-7 SRP): start.ts request orchestrator decomposed into 5 focused\n * per-branch handlers. Each handler returns `true` if it handled the\n * request (response sent) so the orchestrator can stop iterating.\n *\n * The original 455-LOC monolith closed over 14+ locals; the shared shape\n * `RequestHandlerCtx` makes the dependencies explicit + reviewable.\n */\nexport interface RequestHandlerCtx {\n req: IncomingMessage\n res: ServerResponse\n url: string\n requestId: string\n startTime: number\n // Pre-loaded build artifacts\n clientDir: string\n custom404Html: string | null\n // Manifest-resolved tables\n cachedRoutes: ServerRouteNode[]\n cachedActions: ActionNode[]\n cachedAgents: AgentNode[]\n // Runtime infra\n loadModule: LoadModule\n serverDir: string\n /** App root (= `process.cwd()` at `theokit start`); mountAgent points `.theokit/` discovery here. */\n projectRoot: string\n /**\n * theokit#123 — absolute path to the COMPILED controllers emitted by `theokit build`\n * (`<distDir>/controllers`), or `undefined` when the build produced none.\n *\n * `undefined` is the routes-only app, and it must stay free: no scan, no import, no cost.\n */\n controllersDistDir: string | undefined\n pluginRunner: PluginRunner | undefined\n transformer: TheoTransformer | undefined\n csrfMode: CsrfMode\n disallowed: DisallowedConfig | undefined\n /**\n * Async because the per-route limiter hashes the session cookie with Web Crypto when\n * `keyBy: 'session'`, and `subtle.digest` is promise-based.\n */\n rateLimiter:\n | ((req: IncomingMessage) => Promise<{ limited: boolean; headers: Record<string, string> }>)\n | null\n}\n\n/**\n * The caller's identity, from the application's own `server/context.ts` — the seam every\n * `route()` already reads and no agent URL ever reached (usetheokit/theokit#365).\n *\n * Memoized per request by `createAgentSubjectResolver` and invoked only on a path this process is\n * about to answer, and only when that path's agent declares a policy.\n */\nfunction agentSubjectResolver(c: RequestHandlerCtx): () => Promise<RouteSubject | null> {\n return createAgentSubjectResolver({\n req: c.req,\n res: c.res,\n loadModule: c.loadModule,\n serverDir: c.serverDir,\n pluginRunner: c.pluginRunner,\n })\n}\n\n/** Apply rate limit; return true if request was limited (response sent). */\nasync function applyRateLimit(c: RequestHandlerCtx, method: string): Promise<boolean> {\n if (!c.rateLimiter) return false\n const check = await c.rateLimiter(c.req)\n for (const [k, v] of Object.entries(check.headers)) c.res.setHeader(k, v)\n if (check.limited) {\n sendError(c.res, 'RATE_LIMITED', 'Too many requests', 429, undefined, c.requestId)\n logRequest({\n method,\n url: c.url,\n status: 429,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n return false\n}\n\n/** Branch 1: action routes (`/api/__actions/{file}/{exportName}`). */\nexport async function tryServeAction(c: RequestHandlerCtx): Promise<boolean> {\n if (!c.url.startsWith('/api/__actions/')) return false\n c.res.setHeader(X_REQUEST_ID, c.requestId)\n\n if (await applyRateLimit(c, c.req.method ?? 'POST')) return true\n\n const pathAfterPrefix = c.url.slice('/api/__actions/'.length).split('?')[0]\n const segments = pathAfterPrefix.split('/').filter(Boolean)\n if (segments.length < 2) {\n sendError(\n c.res,\n 'BAD_REQUEST',\n 'Action URL must be /api/__actions/{file}/{exportName}',\n 400,\n undefined,\n c.requestId,\n )\n logRequest({\n method: c.req.method ?? 'POST',\n url: c.url,\n status: 400,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n const exportName = segments[segments.length - 1]\n const actionPath = segments.slice(0, -1).join('/')\n const action = c.cachedActions.find((a) => a.actionPath === actionPath)\n if (!action) {\n const actionPaths = c.cachedActions.map((a) => a.actionPath)\n const suggestion = findSuggestion(actionPath, actionPaths)\n const msg = suggestion\n ? `Action \"${actionPath}\" not found. Did you mean: ${suggestion}?`\n : `Action \"${actionPath}\" not found`\n sendError(c.res, 'NOT_FOUND', msg, 404, undefined, c.requestId)\n logRequest({\n method: c.req.method ?? 'POST',\n url: c.url,\n status: 404,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n await executeAction(\n action.filePath,\n exportName,\n c.req,\n c.res,\n c.loadModule,\n c.serverDir,\n c.requestId,\n c.pluginRunner,\n c.csrfMode,\n c.disallowed,\n )\n logRequest({\n method: c.req.method ?? 'POST',\n url: c.url,\n status: c.res.statusCode,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n}\n\n/**\n * Branch 1.4: agent AUXILIARY routes served identically in dev + prod (M15/M16 follow-up) — agent\n * cards (`/.well-known/<name>/agent-card.json`), MCP (`/api/agents/<name>/mcp`), the pending-approvals\n * listing, the durable run stream and the two thread routes. Before this, they were dev-only, so a\n * built/deployed app 404'd them.\n *\n * theokit#400 — this branch runs for EVERY url, so it must decide ownership without converting the\n * request: `incomingMessageToWebRequest` drains the Node body stream, and a POST with a JSON body to\n * an ordinary `/api` file route then reached `parseJsonBody` with a readable that had already ended\n * and waited forever for an `'end'` that had already fired — no status, no timeout, no response.\n * `matchAgentAuxRoute` is handed a method and a url and cannot convert anything, so the ordering is\n * now a property of the signature rather than of this comment.\n *\n * usetheokit/theokit#405 — and having a match separate from the answer is what lets the plugin\n * lifecycle run here at all. It did not, in either surface: six endpoints answered without\n * `onRequest`/`onResponse`/`onError`, so an app embedding TheoKit could not observe them and the\n * observability plugin emitted no `http.request` span for the two that spend tokens. The same\n * bracket the plain agent turn uses now wraps this one, from the same function.\n */\nexport async function tryServeAgentAux(c: RequestHandlerCtx): Promise<boolean> {\n const urlPath = c.url.split('?')[0]\n const deps = {\n agents: c.cachedAgents,\n loadModule: c.loadModule,\n baseUrl: `http://${c.req.headers.host ?? 'localhost'}`,\n // M34 (#97) — the MCP aux route drives the agent (spends tokens); enforce CSRF like the run route.\n csrfMode: c.csrfMode,\n // M39 — the thread follow-up route drives the agent; resolve the key on demand.\n // theokit#328 — the thread route drives an agent, so its key follows the model too.\n resolveApiKey: (model: string | undefined, plugins?: readonly unknown[]) =>\n resolveProvider(model, { plugins }).apiKey,\n // usetheokit/theokit#365 — who is asking. Memoized and LAZY: built here, invoked only inside\n // `serveMatchedAuxRoute` and only when the matched agent declares a policy, so the application's\n // `createContext` never runs for a url this branch declines.\n resolveSubject: agentSubjectResolver(c),\n }\n\n const method = (c.req.method ?? 'GET').toUpperCase()\n const route = await matchAgentAuxRoute(method, urlPath, deps)\n if (route === null) return false\n\n c.res.setHeader(X_REQUEST_ID, c.requestId)\n await serveThroughPluginLifecycle(\n {\n source: createWebRequestSource(c.req),\n res: c.res,\n requestId: c.requestId,\n pluginRunner: c.pluginRunner,\n failureMessage: 'Agent aux handler failed',\n },\n async (request) => {\n await writeWebResponseToServerResponse(\n await serveMatchedAuxRoute(route, request, deps),\n c.res,\n )\n },\n )\n logRequest({\n method,\n url: c.url,\n status: c.res.statusCode,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n}\n\n/**\n * Branch 1.5: agent convention routes (`/api/agents/<name>`, M2). Runs BEFORE the generic `/api/*`\n * branch so a scanned `agents/<name>.ts` owns its path (parity with dev). Loads the module, resolves\n * the provider apiKey (fail-fast), and streams the M0/M1 UIMessageStream via `mountAgent`.\n */\nexport async function tryServeAgent(c: RequestHandlerCtx): Promise<boolean> {\n if (!c.url.startsWith('/api/agents/')) return false\n const urlPath = c.url.split('?')[0]\n\n // HITL approve route (`/api/agents/<name>/approve/<id>`, M4) — resolve the pending approval.\n // Handled BEFORE the agent-path exact match (the approve path never equals an `agentPath`).\n if (isApprovalPath(urlPath)) {\n c.res.setHeader(X_REQUEST_ID, c.requestId)\n if (await applyRateLimit(c, c.req.method ?? 'POST')) return true\n const method = (c.req.method ?? 'POST').toUpperCase()\n if (method !== 'POST') {\n sendError(\n c.res,\n 'METHOD_NOT_ALLOWED',\n 'Approve endpoints accept POST',\n 405,\n undefined,\n c.requestId,\n )\n logRequest({\n method,\n url: c.url,\n status: 405,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n // usetheokit/theokit#405 — the approve route settles a human decision and answered with no\n // plugin lifecycle at all, so a hook that fires for every other route never fired for this one\n // and no `http.request` span was emitted for it. Same bracket as the turn below.\n await serveThroughPluginLifecycle(\n {\n source: createWebRequestSource(c.req),\n res: c.res,\n requestId: c.requestId,\n pluginRunner: c.pluginRunner,\n failureMessage: 'Approve handler failed',\n },\n async (request) => {\n // usetheokit/theokit#365 — the approve route settles a paused tool, so it answers to the\n // named agent's declared policy. The resolver is memoized and lazy: `handleAgentApproval`\n // invokes it only when the agent declares one.\n const approveAgent = c.cachedAgents.find((a) => a.name === parseApprovalAgentName(urlPath))\n const policy =\n approveAgent === undefined\n ? undefined\n : readAgentPolicy(await c.loadModule(approveAgent.filePath), approveAgent.filePath)\n const response = await handleAgentApproval(\n request,\n urlPath,\n getApprovalRegistry(),\n c.csrfMode,\n { policy, resolveSubject: agentSubjectResolver(c) },\n )\n await writeWebResponseToServerResponse(response, c.res)\n },\n )\n logRequest({\n method,\n url: c.url,\n status: c.res.statusCode,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n\n const agent = c.cachedAgents.find((a) => a.agentPath === urlPath)\n if (!agent) return false // fall through to the generic /api/* branch (may 404 there)\n\n c.res.setHeader(X_REQUEST_ID, c.requestId)\n if (await applyRateLimit(c, c.req.method ?? 'POST')) return true\n\n const method = (c.req.method ?? 'POST').toUpperCase()\n if (method !== 'POST') {\n sendError(\n c.res,\n 'METHOD_NOT_ALLOWED',\n 'Agent endpoints accept POST',\n 405,\n undefined,\n c.requestId,\n )\n logRequest({\n method,\n url: c.url,\n status: 405,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n\n await serveAgentTurn(c, agent, method)\n return true\n}\n\n/**\n * Serves one agent turn through the plugin lifecycle.\n *\n * Extracted from `tryServeAgent` so that function stays what its name says — a router over the\n * agent paths — while the turn itself, which is what grew a lifecycle, reads in one piece.\n */\nasync function serveAgentTurn(\n c: RequestHandlerCtx,\n agent: { filePath: string; name: string },\n method: string,\n): Promise<void> {\n // theokit#324 — the plugin lifecycle runs here too.\n //\n // This branch used to mount the agent without ever consulting the runner, so `onRequest`,\n // `onResponse` and `onError` fired for every OTHER route and never for an agent turn — leaving an\n // app embedding TheoKit with no supported place to observe or bound agent state. Reported with a repro\n // by a consumer who found it by instrumenting a hook and watching it stay silent — the failure is\n // invisible by construction, since a hook that never runs looks exactly like one with nothing to\n // say.\n //\n // The bracket itself moved to `serveThroughPluginLifecycle` when the aux and approve branches\n // needed the same one (usetheokit/theokit#405): this shape was copied twice and drifted five\n // ways, which is the argument for having one of it.\n const resolveSubject = agentSubjectResolver(c)\n\n await serveThroughPluginLifecycle(\n {\n source: createWebRequestSource(c.req),\n res: c.res,\n requestId: c.requestId,\n pluginRunner: c.pluginRunner,\n failureMessage: 'Agent handler failed',\n },\n async (request) => {\n const mod = await c.loadModule(agent.filePath)\n // theokit#326 — resolve against the model the agent declares, not by env priority.\n const apiKey = (model: string | undefined, plugins?: readonly unknown[]): string =>\n resolveProvider(model, { plugins }).apiKey\n const response = await mountAgent(mod, request, apiKey, {\n source: agent.filePath,\n csrfMode: c.csrfMode,\n projectRoot: c.projectRoot,\n // usetheokit/theokit#365 — the policy is read off `mod` inside `mountAgent`; what this\n // caller owes is the identity to judge it against. usetheokit/theokit#406 — and it is the\n // same value the run's spans are labelled with, so `source` above never reaches telemetry.\n agentName: agent.name,\n resolveSubject,\n })\n await writeWebResponseToServerResponse(response, c.res)\n },\n )\n logRequest({\n method,\n url: c.url,\n status: c.res.statusCode,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n}\n\n/** Branch 2: API routes (`/api/*` excluding actions). */\nexport async function tryServeApiRoute(c: RequestHandlerCtx): Promise<boolean> {\n if (!c.url.startsWith('/api/')) return false\n c.res.setHeader(X_REQUEST_ID, c.requestId)\n\n if (await applyRateLimit(c, c.req.method ?? 'GET')) return true\n\n const match = matchRoute(c.url, c.cachedRoutes)\n if (!match) {\n // theokit#123 — controller fall-through, mirroring the dev `api-middleware` arm.\n //\n // Before this, `theokit dev` served a decorator controller and `theokit start` 404'd it: the\n // production path has no Vite/swc transform, so an uncompiled `.controller.ts` could not load.\n // `theokit build` now emits compiled modules and this branch serves them, so the SAME app\n // answers the same routes in both. Reached only after a file-route miss, so file routes keep\n // precedence exactly as in dev.\n if (c.controllersDistDir !== undefined) {\n const handled = await dispatchControllerRequest({\n controllersDir: c.controllersDistDir,\n loadModule: loadCompiledController,\n req: c.req,\n res: c.res,\n csrfMode: c.csrfMode,\n disallowed: c.disallowed,\n requestId: c.requestId,\n // #607 — the runner this branch never received. Without it a `@Controller` route ran no\n // plugin hook at all in production: not `onRequest`, not `preHandler`, not `onResponse`,\n // not `onError`. The file-route branch below has always passed it.\n pluginRunner: c.pluginRunner,\n })\n if (handled) return true\n }\n const urlPath = c.url.split('?')[0]\n const routePaths = c.cachedRoutes.map((r) => r.routePath)\n const suggestion = findSuggestion(urlPath, routePaths)\n const msg = suggestion\n ? `API route not found: ${urlPath}. Did you mean: ${suggestion}?`\n : 'API route not found'\n sendError(c.res, 'NOT_FOUND', msg, 404, undefined, c.requestId)\n logRequest({\n method: c.req.method ?? 'GET',\n url: c.url,\n status: 404,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n }\n const method = (c.req.method ?? 'GET').toUpperCase()\n // T3.1 (ADR-0016) — context object replaces 12 positional args\n await executeRoute({\n route: match.route,\n method,\n params: match.params,\n req: c.req,\n res: c.res,\n loadModule: c.loadModule,\n serverDir: c.serverDir,\n requestId: c.requestId,\n pluginRunner: c.pluginRunner,\n transformer: c.transformer,\n csrfMode: c.csrfMode,\n disallowed: c.disallowed,\n })\n logRequest({\n method,\n url: c.url,\n status: c.res.statusCode,\n duration: Date.now() - c.startTime,\n requestId: c.requestId,\n })\n return true\n}\n\n/** Branch 3: static files (returns true if a static asset was served). */\nexport function tryServeStatic(c: RequestHandlerCtx): boolean {\n return serveStaticFile(c.req, c.res, c.clientDir)\n}\n\n/** Branch 4: custom 404 for URLs that look like missing assets. */\nexport function tryServeCustom404(c: RequestHandlerCtx): boolean {\n const urlPath = c.url.split('?')[0]\n if (c.custom404Html && extname(urlPath)) {\n c.res.writeHead(404, { 'Content-Type': 'text/html' })\n c.res.end(c.custom404Html)\n return true\n }\n return false\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Static-file server. The URL path IS user-controlled, so every fs call here is guarded twice\n * before it runs: a string check that rejects a URL walking out with `..` (403), and then a\n * `realpath` check that asks the filesystem whether the target actually lives under `clientDir`.\n *\n * The second guard is not decoration. This header used to claim the string check alone was\n * authoritative, and #428 disproved it: `path.resolve` never touches the disk, so a symlink inside\n * `clientDir` sailed through it and the server returned a file from anywhere on the host. Both\n * guards are now required for that claim to hold.\n */\nimport { closeSync, fstatSync, openSync, readFileSync, realpathSync } from 'node:fs'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { resolve, extname, sep } from 'node:path'\n\nconst MIME_TYPES: Record<string, string> = {\n '.html': 'text/html',\n '.js': 'application/javascript',\n '.mjs': 'application/javascript',\n '.css': 'text/css',\n '.json': 'application/json',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n '.txt': 'text/plain',\n '.map': 'application/json',\n}\n\nexport function serveStaticFile(\n req: IncomingMessage,\n res: ServerResponse,\n clientDir: string,\n): boolean {\n const urlPath = (req.url ?? '/').split('?')[0]\n\n // Path traversal prevention (EC-1) — rejects a URL that walks out with `..`.\n // `sep` matters: without it `/srv/client-backup` passes as \"inside\" `/srv/client`.\n const filePath = resolve(clientDir, '.' + urlPath)\n if (filePath !== clientDir && !filePath.startsWith(clientDir + sep)) {\n res.writeHead(403)\n res.end('Forbidden')\n return true\n }\n\n // #428 — the check above is string arithmetic; `resolve` never touches the disk, so it cannot\n // see that an entry inside `clientDir` IS a file somewhere else. Ask the filesystem instead.\n // Symlinks are not banned — one that stays inside the served tree is ordinary and still served;\n // only the ones that leave are refused, and they are refused as \"not here\" rather than 403 so\n // the response does not confirm what lies outside.\n let realPath: string\n let realRoot: string\n try {\n realPath = realpathSync(filePath)\n realRoot = realpathSync(clientDir)\n } catch {\n return false // missing, unreadable, or a broken link — all \"not served\"\n }\n if (realPath !== realRoot && !realPath.startsWith(realRoot + sep)) return false\n\n // One descriptor for the type check and the bytes: re-opening by path between them is what\n // lets the file that was checked differ from the file that is served (CodeQL js/file-system-race).\n let fd: number\n try {\n fd = openSync(realPath, 'r')\n } catch {\n return false\n }\n try {\n if (!fstatSync(fd).isFile()) return false\n\n const ext = extname(filePath)\n const contentType = MIME_TYPES[ext] ?? 'application/octet-stream'\n const content = readFileSync(fd)\n\n res.writeHead(200, {\n 'Content-Type': contentType,\n 'Content-Length': content.length,\n })\n res.end(content)\n return true\n } finally {\n closeSync(fd)\n }\n}\n","/**\n * SSR setup stage for `theokit start` (T4.2 architecture-cleanup).\n *\n * Loads the SSR entry-server module if configured + builds template split\n * around the React root div. Returns null renderers when SSR is disabled.\n */\n\nimport type { ServerResponse } from 'node:http'\n\nimport { findRootDiv } from '../../../core/contracts/find-root-div.js'\n\nimport { resolveSsrEntry } from './bootstrap-stages.js'\n\nexport interface SsrRenderResult {\n html: string\n hydrationData: {\n loaderData?: unknown\n actionData?: unknown\n errors?: unknown\n }\n}\n\nexport type RenderStreamingResult = { redirect: Response } | { streaming: true } | undefined\n\nexport type SsrRender = (\n url: string,\n options?: { nonce?: string },\n) => Promise<SsrRenderResult | { redirect: Response } | string>\n\nexport type SsrRenderStreaming = (\n url: string,\n response: ServerResponse,\n options?: {\n signal?: AbortSignal\n nonce?: string\n /**\n * The template up to and including `<div id=\"root\">`, written before React\n * produces a byte. Without it the streamed response is React's output alone\n * — no `<html>`, no `<head>` (usetheokit/theokit#343).\n */\n htmlHead?: string\n /** The template after `</div>`, written after the hydration data script. */\n htmlTail?: string\n },\n) => Promise<RenderStreamingResult>\n\ninterface SsrSetupResult {\n enabled: boolean\n streamingEnabled: boolean\n render: SsrRender | null\n renderStreaming: SsrRenderStreaming | null\n htmlHead: string\n htmlTail: string\n}\n\nexport function isSsrRenderResult(value: unknown): value is SsrRenderResult {\n if (typeof value !== 'object' || value === null) return false\n if (!('html' in value)) return false\n const html = (value as Record<string, unknown>).html\n if (typeof html !== 'string') return false\n return true\n}\n\ninterface SsrEntryServer {\n render: SsrRender\n renderStreaming?: SsrRenderStreaming\n}\n\nexport async function setupSsr(opts: {\n distDir: string\n indexHtml: string\n ssrConfigEnabled: boolean\n ssrStreamingConfig: boolean | undefined\n}): Promise<SsrSetupResult> {\n const ssrServerPath: string | null = opts.ssrConfigEnabled ? resolveSsrEntry(opts.distDir) : null\n const enabled = ssrServerPath !== null\n const streamingEnabled = enabled && Boolean(opts.ssrStreamingConfig)\n\n if (ssrServerPath === null) {\n return {\n enabled: false,\n streamingEnabled: false,\n render: null,\n renderStreaming: null,\n htmlHead: '',\n htmlTail: '',\n }\n }\n\n const mod = (await import(ssrServerPath)) as SsrEntryServer\n const render = mod.render\n const renderStreaming = typeof mod.renderStreaming === 'function' ? mod.renderStreaming : null\n\n // Split HTML template on root div\n let htmlHead = ''\n let htmlTail = ''\n const rootDiv = findRootDiv(opts.indexHtml)\n if (rootDiv) {\n htmlHead = opts.indexHtml.slice(0, rootDiv.insertAt)\n htmlTail = opts.indexHtml.slice(rootDiv.insertAt)\n }\n\n return { enabled, streamingEnabled, render, renderStreaming, htmlHead, htmlTail }\n}\n","/**\n * Inline request handler for `theokit start` (T4.2 architecture-cleanup).\n *\n * Wires the per-request flow: security headers → action/route/static branches\n * → SSR fallback → CSR fallback → 500 page.\n *\n * Refactored: CC reduced from 33 to ≤10 per function\n * (architecture-remediation T2.1, 2026-06-12).\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http'\n\nimport { generateNonce } from '../../../server/auth/nonce.js'\nimport { type ReservedRoutes, serveReservedRoute } from '../../../server/define/health-route.js'\nimport type { CorsHandler } from '../../../server/http/cors.js'\nimport { sendError } from '../../../server/http/send-response.js'\nimport { TRACE_HEADER, extractTraceId } from '../../../server/http/trace-context.js'\nimport { buildSecurityHeaders } from '../../../server/security/security-headers.js'\nimport { extractHeadTags, injectIntoHead } from '../../../vite-plugin/hoist-head-tags.js'\nimport { applyNonceToInlineScripts } from '../../../vite-plugin/ssr-dev-middleware.js'\n\nimport {\n tryServeAction,\n tryServeAgent,\n tryServeAgentAux,\n tryServeApiRoute,\n tryServeCustom404,\n tryServeStatic,\n type RequestHandlerCtx,\n} from './handlers.js'\nimport {\n isSsrRenderResult,\n type SsrRender,\n type SsrRenderResult,\n type SsrRenderStreaming,\n} from './ssr-setup.js'\n\ninterface RequestHandlerContext {\n buildCtx: (\n req: IncomingMessage,\n res: ServerResponse,\n requestId: string,\n startTime: number,\n ) => RequestHandlerCtx\n securityHeadersConfig: Parameters<typeof buildSecurityHeaders>[0]\n /**\n * #409 — `security.cors` had exactly one consumer, Vite's `configureServer` hook, so an app that\n * worked cross-origin under `theokit dev` stopped working the moment this command served it:\n * same config, same code, no error and no warning. `null` when the app declared no `cors` block,\n * which is what it meant before and still means — no headers, not permissive ones.\n */\n corsHandler: CorsHandler | null\n ssrRender: SsrRender | null\n ssrRenderStreaming: SsrRenderStreaming | null\n ssrStreamingEnabled: boolean\n htmlHead: string\n htmlTail: string\n indexHtml: string\n custom500Html: string | null\n /** M7-2: reserved health/ready routes served before the user catch-all. */\n reservedRoutes?: ReservedRoutes\n}\n\n/**\n * M7-2: serve a reserved `/__theo/*` route (health/ready) before any user\n * branch. Returns true when the request was handled.\n */\nasync function tryServeReserved(\n ctx: RequestHandlerContext,\n url: string,\n res: ServerResponse,\n): Promise<boolean> {\n const pathname = new URL(url, 'http://localhost').pathname\n const reserved = await serveReservedRoute(pathname, ctx.reservedRoutes ?? {})\n if (reserved === null) return false\n const payload = JSON.stringify(reserved.body)\n res.writeHead(reserved.status, {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(payload),\n })\n res.end(payload)\n return true\n}\n\nfunction asSsrRenderResult(value: SsrRenderResult): SsrRenderResult {\n return value\n}\n\nfunction isRedirectResult(result: unknown): result is { redirect: Response } {\n return result !== null && typeof result === 'object' && 'redirect' in result\n}\n\nfunction sendRedirect(res: ServerResponse, result: { redirect: Response }): void {\n res.writeHead(302, { Location: result.redirect.headers.get('location') ?? '/' })\n res.end()\n}\n\nfunction send500(res: ServerResponse, custom500Html: string | null): void {\n if (!res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'text/html' })\n }\n if (!res.writableEnded) {\n res.end(custom500Html ?? '<h1>500 — Server Error</h1>')\n }\n}\n\n/**\n * Moves the route's `<title>`/`<meta>`/`<link>` from the rendered body into the head.\n *\n * `ctx.htmlHead` is the template up to and including `<div id=\"root\">`, so it still contains the\n * `</head>` this injects before. Without it, a route's metadata ships inside the body and only\n * reaches the head after hydration — which never happens for a crawler that does not run\n * JavaScript, and those are exactly the ones that render social cards.\n *\n * The dev middleware does the same thing; both paths have to, or previews work in one and not the\n * other, which is worse than neither.\n */\nexport function withHoistedHead(\n htmlHead: string,\n ssrHtml: string,\n nonce: string,\n): { head: string; body: string } {\n const { html, headTags } = extractHeadTags(ssrHtml)\n\n // The nonce is stamped onto the template's own inline scripts here, per request, because the\n // nonce differs per request while `ctx.htmlHead` is computed once at startup. Without it, a CSP\n // with a nonce blocks anything the template inlines — including the theme-init script that\n // applications put in `<head>` specifically to avoid a flash of the wrong theme on load.\n const head = applyNonceToInlineScripts(injectIntoHead(htmlHead, headTags), nonce)\n return { head, body: html }\n}\n\nfunction buildSsrHtml(\n ctx: RequestHandlerContext,\n result: string | SsrRenderResult,\n nonce: string,\n): string {\n if (typeof result === 'string') {\n const { head, body } = withHoistedHead(ctx.htmlHead, result, nonce)\n return head + body + ctx.htmlTail\n }\n if (isSsrRenderResult(result)) {\n const rendered = asSsrRenderResult(result)\n const dataJson = JSON.stringify(rendered.hydrationData).replace(/</g, '\\\\u003c')\n const hydrationScript = `<script${\n nonce ? ` nonce=\"${nonce}\"` : ''\n }>window.__staticRouterHydrationData=${dataJson}</script>`\n const { head, body } = withHoistedHead(ctx.htmlHead, rendered.html, nonce)\n return head + body + hydrationScript + ctx.htmlTail\n }\n return applyNonceToInlineScripts(ctx.htmlHead, nonce) + ctx.htmlTail\n}\n\nasync function handleSsrStreaming(\n ctx: RequestHandlerContext,\n req: IncomingMessage,\n res: ServerResponse,\n url: string,\n nonce: string,\n): Promise<boolean> {\n if (!ctx.ssrStreamingEnabled || !ctx.ssrRenderStreaming) return false\n\n const controller = new AbortController()\n const onClose = (): void => {\n controller.abort()\n }\n req.on('close', onClose)\n try {\n const result = await ctx.ssrRenderStreaming(url, res, {\n signal: controller.signal,\n nonce,\n // #343 — the streamed response carried neither of these, so `ssrStreaming: true`\n // served a bare React tree: no `<head>`, no client entry, no hydration data.\n //\n // `applyNonceToInlineScripts` and not `withHoistedHead`: hoisting reads the\n // RENDERED body for head elements, and nothing is rendered yet when the head\n // has to flush. Metadata hoisting under streaming is the same defect on a\n // different surface, and it is M9's, not this one's.\n htmlHead: applyNonceToInlineScripts(ctx.htmlHead, nonce),\n htmlTail: ctx.htmlTail,\n })\n if (isRedirectResult(result)) sendRedirect(res, result)\n return true\n } catch (streamErr) {\n console.error('[SSR Stream Error]', (streamErr as Error).message)\n send500(res, ctx.custom500Html)\n return true\n } finally {\n req.removeListener('close', onClose)\n }\n}\n\nasync function handleSsrSync(\n ctx: RequestHandlerContext,\n res: ServerResponse,\n url: string,\n nonce: string,\n): Promise<boolean> {\n if (!ctx.ssrRender) return false\n\n try {\n const result = await ctx.ssrRender(url, { nonce })\n if (isRedirectResult(result)) {\n sendRedirect(res, result)\n return true\n }\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(buildSsrHtml(ctx, result, nonce))\n return true\n } catch (ssrErr) {\n console.error('[SSR Error] Falling back to CSR:', (ssrErr as Error).message)\n return false\n }\n}\n\nfunction handleFatalError(ctx: RequestHandlerContext, res: ServerResponse, err: unknown): void {\n if (ctx.custom500Html && !res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'text/html' })\n res.end(ctx.custom500Html)\n } else if (!res.headersSent) {\n sendError(res, 'INTERNAL_ERROR', (err as Error).message, 500)\n } else {\n res.end()\n }\n}\n\nexport function createRequestHandler(\n ctx: RequestHandlerContext,\n): (req: IncomingMessage, res: ServerResponse) => void {\n return (req: IncomingMessage, res: ServerResponse) => {\n void (async () => {\n const url = req.url ?? '/'\n // #353 — production minted a fresh UUID on every request and discarded the\n // incoming W3C `traceparent`, so a trace crossing into this server started\n // over and no span could continue it. `theo dev` has honoured the header on\n // `/api/*` since Phase 7 (`vite-plugin/api-middleware.ts:368`); this is the\n // same resolution on the path a deploy actually serves.\n //\n // The tier-2 fallback (`x-request-id`) is caller-controlled and validated\n // inside `extractTraceId` before it is trusted — it ends up in the logs.\n const requestId = extractTraceId(req)\n const start = Date.now()\n // Echoed under both names, matching dev: `x-request-id` is what existing\n // consumers read, `x-trace-id` is the canonical one.\n res.setHeader('x-request-id', requestId)\n res.setHeader(TRACE_HEADER, requestId)\n\n // Preflight before anything else, and it answers rather than routing — the same order the\n // dev middleware uses (`vite-plugin/api-middleware.ts`), because an OPTIONS the router\n // handles is an OPTIONS the browser never gets a CORS answer to.\n if (ctx.corsHandler?.handlePreflight(req, res) === true) return\n ctx.corsHandler?.applyHeaders(req, res)\n\n const nonce = generateNonce()\n const securityHeaders = buildSecurityHeaders(\n ctx.securityHeadersConfig,\n { production: true },\n { nonce },\n )\n for (const [k, v] of Object.entries(securityHeaders)) {\n res.setHeader(k, v)\n }\n\n const handlerCtx = ctx.buildCtx(req, res, requestId, start)\n\n try {\n if (await tryServeReserved(ctx, url, res)) return\n if (await tryServeAction(handlerCtx)) return\n if (await tryServeAgentAux(handlerCtx)) return\n if (await tryServeAgent(handlerCtx)) return\n if (await tryServeApiRoute(handlerCtx)) return\n if (tryServeStatic(handlerCtx)) return\n if (tryServeCustom404(handlerCtx)) return\n\n if (await handleSsrStreaming(ctx, req, res, url, nonce)) return\n if (await handleSsrSync(ctx, res, url, nonce)) return\n\n // CSR fallback\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(ctx.indexHtml)\n } catch (err) {\n handleFatalError(ctx, res, err)\n }\n })()\n }\n}\n","/**\n * Resolve the address `server.listen` should bind, and say which it is.\n *\n * `config.host` was declared, defaulted to `'localhost'`, documented as the way to\n * open a server to the LAN, and never passed to `listen`. Node with no address\n * binds EVERY interface, so the production server listened wider than its\n * configuration said — and its default said the narrow thing.\n *\n * Fixing that broke containers, which is the second half of this story\n * (usetheokit/theokit#402). Inside a container `localhost` means *nobody*: the\n * image starts, prints a URL, and refuses every request including its own. So the\n * environment gets a say. `HOST` is the variable every container platform already\n * sets or expects to set, and honouring it costs the operator nothing to discover.\n *\n * Precedence, narrowest authority last: explicit config beats `HOST`, because a\n * value written into the project is a decision and an environment variable is a\n * deployment detail. Absent both, the loopback — binding every interface is a\n * decision someone should have to write down.\n *\n * The second export exists because the log lied. `theo start` printed `localhost`\n * whether it bound the loopback or every interface, so a container that serves\n * everyone and a container that serves nobody produced byte-identical output.\n * That is `docs/adr/0002-an-abnormal-ending-is-never-reported-as-normal.md` in the\n * startup path: the observable state must distinguish the two.\n */\n\nexport interface ListenTarget {\n /** The address handed to `server.listen`. */\n readonly host: string\n /** Where the value came from, so the log can say so rather than guess. */\n readonly source: 'config' | 'env' | 'default'\n}\n\nexport function resolveListenTarget(\n host: string | boolean | undefined,\n env: string | undefined = process.env.HOST,\n): ListenTarget {\n if (host === true) return { host: '0.0.0.0', source: 'config' }\n if (typeof host === 'string' && host !== '') return { host, source: 'config' }\n // `host: false` is an explicit \"do not open me up\" and outranks the environment.\n if (host !== false && env !== undefined && env !== '') return { host: env, source: 'env' }\n return { host: 'localhost', source: 'default' }\n}\n\n/**\n * The line `theo start` prints, given what it actually bound.\n *\n * `0.0.0.0` is not a URL a human can open, so the loopback is offered — but the\n * bound address is stated beside it, because \"every interface\" and \"this machine\n * only\" are the two states an operator most needs to tell apart, and they were\n * indistinguishable.\n */\nexport function describeListenTarget(target: ListenTarget, port: number): string {\n const url = target.host === '0.0.0.0' || target.host === '::' ? 'localhost' : target.host\n const bound =\n target.host === '0.0.0.0' || target.host === '::'\n ? `bound to ${target.host} (every interface)`\n : `bound to ${target.host} only`\n return ` → http://${url}:${String(port)} [${bound}${provenance(target.source)}]`\n}\n\n/** Where the address came from, and — when nobody chose it — what to write to change it. */\nfunction provenance(source: ListenTarget['source']): string {\n if (source === 'env') return ' from HOST'\n if (source === 'config') return ''\n return ' — set `host: true` in theo.config.ts or HOST=0.0.0.0 to reach it from outside this machine'\n}\n","/**\n * WebSocket upgrade handler for `theokit start` (T4.2 architecture-cleanup).\n *\n * Wires `server.on('upgrade')` for declared WS routes. Opt-in: only attached\n * when `wsRoutes.length > 0`. Lazy-imports `ws` package — throws an actionable\n * error when wsRoutes declared but `ws` not installed.\n */\n\nimport type { Server as HttpServer } from 'node:http'\n\nimport type * as WsLib from 'ws'\n\nimport type { WebSocketHandler } from '../../../server/define/define-websocket.js'\nimport type { LoadModule } from '../../../server/scan/module-loader.js'\nimport type { WebSocketRouteNode } from '../../../server/scan/ws-scan.js'\n\nexport async function attachWebSocketHandler(\n server: HttpServer,\n wsRoutes: WebSocketRouteNode[],\n loadModule: LoadModule,\n): Promise<void> {\n if (wsRoutes.length === 0) return\n\n let WebSocketServerCtor: typeof WsLib.WebSocketServer\n try {\n const wsModule = await import('ws')\n WebSocketServerCtor = wsModule.WebSocketServer\n } catch {\n throw new Error('WebSocket routes found but \"ws\" package is not installed. Run: npm install ws')\n }\n\n const wss = new WebSocketServerCtor({ noServer: true })\n\n server.on('upgrade', (request, socket, head) => {\n void (async () => {\n const url = request.url ?? '/'\n if (!url.startsWith('/ws/')) {\n socket.destroy()\n return\n }\n\n const wsPath = url.split('?')[0]\n const match = wsRoutes.find((r) => r.wsPath === wsPath)\n if (!match) {\n socket.destroy()\n return\n }\n\n try {\n const mod = await loadModule(match.filePath)\n const handler = ((mod as { default?: unknown }).default ?? mod) as WebSocketHandler\n\n wss.handleUpgrade(request, socket, head, (ws) => {\n handler.onOpen?.(ws, request)\n ws.on('message', (data: Buffer) => {\n handler.onMessage?.(ws, data.toString())\n })\n ws.on('close', (code: number, reason: Buffer) => {\n handler.onClose?.(ws, code, reason)\n })\n ws.on('error', (err: Error) => {\n handler.onError?.(ws, err)\n })\n })\n } catch {\n socket.destroy()\n }\n })()\n })\n}\n"],"mappings":"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","names":["existsSync","readFileSync","join","resolve","require","existsSync","readFileSync","resolve","existsSync","existsSync","extname","readFileSync","resolve","method","extname","resolve","existsSync","readFileSync","join"]}
|
|
File without changes
|