Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,9 @@ const configSchema = z.object({
providerContextCaps: z.record(z.string(), z.number().int().positive()).optional(),
contextCapValue: z.number().int().positive().optional(),
multiAgentGuidanceEnabled: z.boolean().optional(),
// Compatibility Lab integration with the ordinary server runtime is explicit
// opt-in. A bad hand edit degrades to OFF instead of invalidating providers.
labIntegrationEnabled: z.boolean().optional().catch(false),
// Invalid optional recovery config must not discard unrelated provider/account state.
agentTaskRecovery: agentTaskRecoverySchema.optional().catch(undefined),
// These selections pre-date schema validation and used to pass through as
Expand Down
6 changes: 5 additions & 1 deletion src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import {
} from "./routing/trace";
import { getRoutingProfile, resolvePolicyProfileId } from "./routing/profile";
import { evaluatePolicyProfile, type PolicyRequestEvidence } from "./routing/evaluator";
import { assemblePolicyCandidateEvidence } from "./routing/compatibility/assemble";

export class NoEligiblePolicyCandidateError extends Error {
/** Evaluation trace (with per-candidate exclusions) when nothing qualified. */
Expand Down Expand Up @@ -518,6 +517,11 @@ function routeModelInternal(
const policyId = !bypassCombos ? resolvePolicyProfileId(config, modelId) : null;
const profile = policyId ? getRoutingProfile(config, policyId) : undefined;
if (profile && policyId) {
// Compatibility evidence is needed only for an explicit policy route.
// Keep its Lab-backed implementation out of the normal concrete route path.
const { assemblePolicyCandidateEvidence } = require(
"./routing/compatibility/assemble",
) as typeof import("./routing/compatibility/assemble");
// One clock read per decision keeps candidate evidence, exclusions, and
// scores mutually consistent and reproducible.
const now = Date.now();
Expand Down
52 changes: 34 additions & 18 deletions src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,6 @@ import {
registerDefaultAppOwnedObservedBuffers,
} from "../lib/app-owned-memory-stores";
import { acquireServerBackgroundLifecycle } from "./background-lifecycle";
import {
setLabAutomationDispatchDeps,
startLabAutomationScheduler,
} from "../lab/automation/orchestrator";
import { loadLabAutomationPolicy } from "../lab/automation/persistence";
import { createProductionLabRouteExecutor } from "../lib/lab-live-route-production";
import { runOpenAiTierStartupMigration } from "../providers/openai-tier-startup";
import { runAlibabaRegionStartupMigration } from "../providers/alibaba-region-startup";
import { runModelRenameStartupMigration } from "../providers/model-rename-startup";
Expand Down Expand Up @@ -81,6 +75,7 @@ import {
isDraining,
registerTurn,
runListenerShutdown,
setLabAutomationShutdownHook,
setServerRef,
trackStreamLifetime,
tryAdmitTurn,
Expand Down Expand Up @@ -1735,18 +1730,39 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
// Opt-in storage policy (default OFF). Never blocks listen; cancellable on shutdown.
backgroundLifecycle.scheduleStartupRun();

const labConfigDir = getConfigDir();
const productionLabRouteExecutor = createProductionLabRouteExecutor({
configDir: labConfigDir,
loadConfig: () => config,
});
setLabAutomationDispatchDeps({
configDir: labConfigDir,
loadConfig: () => config,
routeExecutor: productionLabRouteExecutor,
});
if (loadLabAutomationPolicy(labConfigDir).enabled) {
startLabAutomationScheduler(labConfigDir);
// Compatibility Lab runtime integration is explicit opt-in. Keep all Lab
// automation modules outside the ordinary server startup graph while
// preserving startServer's synchronous API.
if (config.labIntegrationEnabled === true) {
const {
requestLabAutomationShutdown,
setLabAutomationDispatchDeps,
startLabAutomationScheduler,
stopLabAutomationScheduler,
} = require("../lab/automation/orchestrator") as typeof import("../lab/automation/orchestrator");
const { loadLabAutomationPolicy } = require(
"../lab/automation/persistence",
) as typeof import("../lab/automation/persistence");
const { createProductionLabRouteExecutor } = require(
"../lib/lab-live-route-production",
) as typeof import("../lib/lab-live-route-production");
const labConfigDir = getConfigDir();
const productionLabRouteExecutor = createProductionLabRouteExecutor({
configDir: labConfigDir,
loadConfig: () => config,
});
setLabAutomationDispatchDeps({
configDir: labConfigDir,
loadConfig: () => config,
routeExecutor: productionLabRouteExecutor,
});
setLabAutomationShutdownHook(() => {
requestLabAutomationShutdown();
stopLabAutomationScheduler();
});
if (loadLabAutomationPolicy(labConfigDir).enabled) {
startLabAutomationScheduler(labConfigDir);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

return server;
Expand Down
16 changes: 13 additions & 3 deletions src/server/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
} from "../storage/policy-job";
import { abortRestoreTrashJobAsync } from "../storage/restore-job";
import { stopStorageCleanupScheduler } from "../storage/policy-scheduler";
import { stopLabAutomationScheduler, requestLabAutomationShutdown } from "../lab/automation/orchestrator";
import { stopStateStoreSweeper } from "../lib/state-store-sweeper";
import {
cancelQueuedStorageWorkerSpawns,
Expand Down Expand Up @@ -52,6 +51,17 @@ let _serverRef: ReturnType<typeof Bun.serve> | undefined;
let serverStopFlights = new WeakMap<ReturnType<typeof Bun.serve>, Promise<void>>();
let serverStartupReleaseFlights = new WeakMap<ReturnType<typeof Bun.serve>, Promise<void>>();
let releaseServerStartupLifecycleImpl: typeof releaseNativeMainStartupLifecycle = releaseNativeMainStartupLifecycle;
let labAutomationShutdownHook: (() => void) | null = null;

export function setLabAutomationShutdownHook(hook: (() => void) | null): void {
labAutomationShutdownHook = hook;
}

function runLabAutomationShutdownHook(): void {
const hook = labAutomationShutdownHook;
labAutomationShutdownHook = null;
hook?.();
}

export function setServerRef(server: ReturnType<typeof Bun.serve> | undefined): void { _serverRef = server; }
/**
Expand Down Expand Up @@ -156,6 +166,7 @@ export function resetLifecycleDrainStateForTests(): void {
serverStopFlights = new WeakMap<ReturnType<typeof Bun.serve>, Promise<void>>();
serverStartupReleaseFlights = new WeakMap<ReturnType<typeof Bun.serve>, Promise<void>>();
releaseServerStartupLifecycleImpl = releaseNativeMainStartupLifecycle;
labAutomationShutdownHook = null;
}
export function tryAdmitTurn(): ActiveTurnLease | null {
if (isDraining()) return null;
Expand Down Expand Up @@ -452,8 +463,7 @@ export async function drainAndShutdown(
// Abort each job independently so one wedged join cannot skip the other,
// then drain leftovers; failures must not prevent `server.stop`.
stopStorageCleanupScheduler();
requestLabAutomationShutdown();
stopLabAutomationScheduler();
runLabAutomationShutdownHook();
stopStateStoreSweeper();
// The overlay reconciler is owner-scoped: the startServer stop override
// releases THIS server's lease through runListenerShutdown →
Expand Down
28 changes: 22 additions & 6 deletions src/server/management-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,12 @@ import { handleConfigRoutes } from "./management/config-routes";
import { handleLogsUsageRoutes } from "./management/logs-usage-routes";
import { handleRequestHistoryRoutes } from "./management/request-history-routes";
import { handleRoutingAnalyticsRoutes } from "./management/routing-analytics-routes";
import { handleRoutingProfileRoutes } from "./management/routing-profile-routes";
import { handleProviderRoutes } from "./management/provider-routes";
import { handleModelRoutes } from "./management/model-routes";
import { handleAgentSettingsRoutes } from "./management/agent-settings-routes";
import { handleOauthAccountRoutes } from "./management/oauth-account-routes";
import { handleComboRoutes } from "./management/combo-routes";
import { handleSystemRoutes } from "./management/system-routes";
import { handleLabRoutes } from "./management/lab-routes";
import { handleLabAutomationRoutes } from "./management/lab-automation-routes";
import { handleSidebarRoutes } from "./management/sidebar-routes";
import { handleIntegrationRoutes } from "./management/integration-routes";
import { handleNativeIntegrationRoutes } from "./management/native-integration-routes";
Expand All @@ -91,6 +88,26 @@ export const VERSION = (() => {
}
})();

function pathInManagementNamespace(pathname: string, prefix: string): boolean {
return pathname === prefix || pathname.startsWith(`${prefix}/`);
}

async function handleRoutingProfileRoutesOnDemand(ctx: ManagementContext): Promise<Response | null> {
if (!pathInManagementNamespace(ctx.url.pathname, "/api/routing-profiles")) return null;
const { handleRoutingProfileRoutes } = await import("./management/routing-profile-routes");
return handleRoutingProfileRoutes(ctx);
}

async function handleLabRoutesOnDemand(ctx: ManagementContext): Promise<Response | null> {
if (!pathInManagementNamespace(ctx.url.pathname, "/api/lab")) return null;
if (pathInManagementNamespace(ctx.url.pathname, "/api/lab/automation")) {
const { handleLabAutomationRoutes } = await import("./management/lab-automation-routes");
return handleLabAutomationRoutes(ctx);
}
const { handleLabRoutes } = await import("./management/lab-routes");
return handleLabRoutes(ctx);
}

const managementConvergenceBindings = new WeakMap<object, Readonly<{
factory: (config: Readonly<OcxConfig>) => ConvergeCodex;
converge: ConvergeCodex;
Expand Down Expand Up @@ -180,7 +197,7 @@ export async function handleManagementAPI(
?? (await handleLogsUsageRoutes(ctx))
?? (await handleRequestHistoryRoutes(ctx))
?? (await handleRoutingAnalyticsRoutes(ctx))
?? (await handleRoutingProfileRoutes(ctx))
?? (await handleRoutingProfileRoutesOnDemand(ctx))
?? (await handleProviderRoutes(ctx))
?? (await handleModelRoutes(ctx))
?? (await handleIntegrationRoutes(ctx))
Expand All @@ -189,8 +206,7 @@ export async function handleManagementAPI(
?? (await handleOauthAccountRoutes(ctx))
?? (await handleComboRoutes(ctx))
?? (await handleSystemRoutes(ctx))
?? (await handleLabAutomationRoutes(ctx))
?? (await handleLabRoutes(ctx))
?? (await handleLabRoutesOnDemand(ctx))
?? (await handleSidebarRoutes(ctx));
} catch (error) {
const tooLarge = managementBodyTooLargeResponse(error, req, config);
Expand Down
9 changes: 9 additions & 0 deletions src/server/management/lab-automation-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
cancelLabAutomationRun,
enqueueManualLabRun,
reconcileLabAutomationQueue,
requestLabAutomationShutdown,
startLabAutomationScheduler,
stopLabAutomationScheduler,
} from "../../lab/automation/orchestrator";
Expand All @@ -33,6 +34,7 @@ import { listLabAutomationRuns } from "../../lab/automation/runs-query";
import type { LabAutomationLayer, LabAutomationPolicyV1 } from "../../lab/automation/types";
import { LabAutomationError } from "../../lab/automation/types";
import { jsonResponse } from "../auth-cors";
import { setLabAutomationShutdownHook } from "../lifecycle";
import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
import type { ManagementContext } from "./context";
import { isPlainRecord } from "./shared";
Expand Down Expand Up @@ -76,6 +78,13 @@ export async function handleLabAutomationRoutes(ctx: ManagementContext): Promise
const { url, req, config } = ctx;
if (!url.pathname.startsWith("/api/lab/automation")) return null;

// This module is loaded only after an explicit Lab automation request. Once
// loaded, register cleanup without making lifecycle.ts import Lab code.
setLabAutomationShutdownHook(() => {
requestLabAutomationShutdown();
stopLabAutomationScheduler();
});

const configDir = getConfigDir();

if (url.pathname === "/api/lab/automation" && req.method === "GET") {
Expand Down
16 changes: 11 additions & 5 deletions src/server/responses/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import {
type RouteResult,
} from "../../router";
import { evidenceFromBody } from "../../routing/request-evidence";
import { resolveProductionRouteSubject } from "../../routing/compatibility/subject";
import {
advanceComboAfterFailure,
comboDefaultEffort,
Expand Down Expand Up @@ -1991,11 +1990,18 @@ async function handleResponsesInner(
(logCtx.attempts ??= []).push(attempt);
}
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, adapter.name, logCtx.accountLogLabel);
// CL-09: attach only the opaque exact route-subject identity to the attempt.
// This is best-effort passive metadata: no Lab state is created and failure
// must never alter, retry, or delay the upstream request.
if (logCtx.activeAttempt && !logCtx.activeAttempt.labRouteSubjectId) {
// CL-09 passive linkage is part of the explicit Lab integration only. Keep
// both the module load and subject construction out of the ordinary request
// path when Lab integration is disabled.
if (
config.labIntegrationEnabled === true
&& logCtx.activeAttempt
&& !logCtx.activeAttempt.labRouteSubjectId
) {
try {
const { resolveProductionRouteSubject } = require(
"../../routing/compatibility/subject",
) as typeof import("../../routing/compatibility/subject");
const passiveSubject = resolveProductionRouteSubject(
config,
route.providerName,
Expand Down
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,13 @@ export interface OcxConfig {
};
/** Virtual `combo/<id>` models spanning concrete provider/model targets (issue #133). */
combos?: Record<string, OcxComboConfig>;
/**
* Automatic Compatibility Lab integration with the ordinary server runtime.
* Default false. When disabled/unset, normal requests and server startup do
* not load Lab modules. Explicit Lab commands/APIs and explicit policy routes
* may still load their Lab dependencies on demand.
*/
labIntegrationEnabled?: boolean;
/**
* Routing policy profiles (Router Intelligence, RI-04+): explicitly requested
* `policy/<id>` (or configured alias) models select among an explicit
Expand Down
54 changes: 54 additions & 0 deletions tests/lab-core-isolation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { readFileSync } from "node:fs";
import { describe, expect, test } from "bun:test";

describe("Compatibility Lab core isolation", () => {
test("ordinary responses do not statically load or run Lab subject code", () => {
const source = readFileSync("src/server/responses/core.ts", "utf8");
expect(source).not.toContain(
'import { resolveProductionRouteSubject } from "../../routing/compatibility/subject"',
);
expect(source).toContain("config.labIntegrationEnabled === true");
expect(source).toContain('require(\n "../../routing/compatibility/subject",');
});

test("concrete routing does not statically load compatibility evidence", () => {
const source = readFileSync("src/router.ts", "utf8");
expect(source).not.toContain(
'import { assemblePolicyCandidateEvidence } from "./routing/compatibility/assemble"',
);
const profileBranch = source.indexOf("if (profile && policyId)");
const lazyLoad = source.indexOf('require(\n "./routing/compatibility/assemble",');
expect(profileBranch).toBeGreaterThanOrEqual(0);
expect(lazyLoad).toBeGreaterThan(profileBranch);
});

test("ordinary server startup and shutdown have no static Lab automation dependency", () => {
const indexSource = readFileSync("src/server/index.ts", "utf8");
const lifecycleSource = readFileSync("src/server/lifecycle.ts", "utf8");

expect(indexSource).not.toContain('from "../lab/automation/orchestrator"');
expect(indexSource).not.toContain('from "../lab/automation/persistence"');
expect(indexSource).not.toContain('from "../lib/lab-live-route-production"');
expect(indexSource).toContain("config.labIntegrationEnabled === true");
expect(indexSource).toContain('require("../lab/automation/orchestrator")');

expect(lifecycleSource).not.toContain("../lab/automation/orchestrator");
expect(lifecycleSource).toContain("runLabAutomationShutdownHook()");
});

test("normal management traffic does not load Lab or routing-profile compatibility routes", () => {
const source = readFileSync("src/server/management-api.ts", "utf8");

expect(source).not.toContain('from "./management/lab-routes"');
expect(source).not.toContain('from "./management/lab-automation-routes"');
expect(source).not.toContain('from "./management/routing-profile-routes"');
expect(source).toContain('import("./management/lab-routes")');
expect(source).toContain('import("./management/lab-automation-routes")');
expect(source).toContain('import("./management/routing-profile-routes")');
});

test("Lab integration flag is explicit opt-in in config parsing", () => {
const source = readFileSync("src/config.ts", "utf8");
expect(source).toContain("labIntegrationEnabled: z.boolean().optional().catch(false)");
});
});
Loading