@velum-labs/routekit-daemon 1.0.26 → 1.1.1

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.
@@ -5,7 +5,7 @@ import { type RouteKitPlatform } from "@velum-labs/routekit-runtime/effect";
5
5
  import { Effect } from "effect";
6
6
  import type { CliproxySidecar } from "./cliproxy-sidecar.js";
7
7
  import type { RevisionState } from "./daemon-state.js";
8
- import { type RunningGatewayGeneration } from "./gateway-generation.js";
8
+ import { type RunningGatewayGeneration } from "./services/gateway-generation/service.js";
9
9
  export type DaemonGenerationStage = "prepare" | "validate" | "persist" | "commit" | "retire";
10
10
  export type DaemonGenerationMutation = {
11
11
  write: boolean;
@@ -4,7 +4,7 @@ import { RouteKitFailure, toRouteKitFailure } from "@velum-labs/routekit-runtime
4
4
  import { writeFileAtomic } from "@velum-labs/routekit-runtime/filesystem";
5
5
  import { Effect } from "effect";
6
6
  import { writeDaemonRevisions } from "./daemon-state.js";
7
- import { startGatewayGenerationEffect } from "./gateway-generation.js";
7
+ import { startGatewayGenerationEffect } from "./services/gateway-generation/service.js";
8
8
  const tryPromise = (run) => Effect.tryPromise({
9
9
  try: async () => await run(),
10
10
  catch: toRouteKitFailure
@@ -5,7 +5,7 @@ import type { RunningControlServer } from "@velum-labs/routekit-runtime/control"
5
5
  import { type RouteKitPlatform } from "@velum-labs/routekit-runtime/effect";
6
6
  import { Effect, type ManagedRuntime } from "effect";
7
7
  import type { DaemonRuntimeState } from "./daemon-runtime-state.js";
8
- import type { RunningGatewayGeneration } from "./gateway-generation.js";
8
+ import type { RunningGatewayGeneration } from "./services/gateway-generation/service.js";
9
9
  import type { DaemonTelemetry, GatewayTelemetryAggregator } from "./telemetry.js";
10
10
  type Supervisor = "systemd" | "launchd" | "detached" | "unknown";
11
11
  export type DaemonLifecycleOptions = {
@@ -18,8 +18,8 @@ export { DaemonState } from "./daemon-state-context.js";
18
18
  export type { DataPlaneValue } from "./data-plane-context.js";
19
19
  export { DataPlane } from "./data-plane-context.js";
20
20
  export { EvalSessions } from "./services/eval-session/service.js";
21
- export type { GatewayGenerationOptions, GatewayGenerationRedeemResetOptions, GatewayGenerationRedeemResetResponse, RunningGatewayGeneration } from "./gateway-generation.js";
22
- export { startGatewayGenerationEffect } from "./gateway-generation.js";
21
+ export type { GatewayGenerationOptions, GatewayGenerationRedeemResetOptions, GatewayGenerationRedeemResetResponse, RunningGatewayGeneration } from "./services/gateway-generation/service.js";
22
+ export { startGatewayGenerationEffect } from "./services/gateway-generation/service.js";
23
23
  export type { DaemonGenerationHooks } from "./services/generations/service.js";
24
24
  export { Generations } from "./services/generations/service.js";
25
25
  export type { LeaderboardValue } from "./leaderboard-context.js";
@@ -10,7 +10,7 @@ export { DaemonPolicy } from "./daemon-policy-context.js";
10
10
  export { DaemonState } from "./daemon-state-context.js";
11
11
  export { DataPlane } from "./data-plane-context.js";
12
12
  export { EvalSessions } from "./services/eval-session/service.js";
13
- export { startGatewayGenerationEffect } from "./gateway-generation.js";
13
+ export { startGatewayGenerationEffect } from "./services/gateway-generation/service.js";
14
14
  export { Generations } from "./services/generations/service.js";
15
15
  export { Leaderboard } from "./leaderboard-context.js";
16
16
  export { Sidecar } from "./sidecar-context.js";
@@ -1,8 +1,5 @@
1
1
  import { type CompositionalRoutingPolicyReader } from "@velum-labs/routekit-gateway";
2
2
  /** Daemon-owned location for the compact policy artifact consumed online. */
3
3
  export declare function evalRoutingSnapshotDirectory(routekitHome: string): string;
4
- /**
5
- * Read the compositional snapshot without restarting router generations. A
6
- * corrupt current file falls back to the last known-good publication.
7
- */
4
+ /** Read the authoritative routing activation without restarting router generations. */
8
5
  export declare function makeCompositionalRoutingPolicyReader(routekitHome: string): CompositionalRoutingPolicyReader;
@@ -6,18 +6,15 @@ import { Effect } from "effect";
6
6
  export function evalRoutingSnapshotDirectory(routekitHome) {
7
7
  return join(routekitHome, "eval");
8
8
  }
9
- /**
10
- * Read the compositional snapshot without restarting router generations. A
11
- * corrupt current file falls back to the last known-good publication.
12
- */
9
+ /** Read the authoritative routing activation without restarting router generations. */
13
10
  export function makeCompositionalRoutingPolicyReader(routekitHome) {
14
11
  const snapshots = makeRoutingActivationStore(evalRoutingSnapshotDirectory(routekitHome));
15
12
  return {
16
- getSnapshot: () => snapshots.read().pipe(Effect.catch((currentCause) => snapshots
13
+ getActivation: () => snapshots.readDeployment().pipe(Effect.map((deployment) => deployment.authoritative?.activation), Effect.catch((currentCause) => snapshots
17
14
  .readPrevious()
18
15
  .pipe(Effect.flatMap((previous) => previous === undefined ? Effect.fail(currentCause) : Effect.succeed(previous)))), Effect.mapError((cause) => new RoutingPolicyReadError({
19
16
  profileId: "*",
20
- message: "failed to read the compositional routing snapshot",
17
+ message: "failed to read the authoritative routing activation",
21
18
  cause
22
19
  })))
23
20
  };
@@ -1,5 +1,5 @@
1
1
  import type { EffectRouteKitControlHandlers } from "@velum-labs/routekit-control/effect";
2
- type EvalRoutingHandlers = Pick<EffectRouteKitControlHandlers, "evalRouting.status" | "evalRouting.activate">;
2
+ type EvalRoutingHandlers = Pick<EffectRouteKitControlHandlers, "evalRouting.status" | "evalRouting.activate" | "evalRouting.installAuthoritative" | "evalRouting.rollbackAuthoritative">;
3
3
  /** Owns target-local compositional routing status and atomic activation. */
4
4
  export declare class EvalRoutingApplicationService {
5
5
  handlers(): EvalRoutingHandlers;
@@ -1,21 +1,33 @@
1
1
  import { DEFAULT_CLASSIFIER_MODEL } from "@velum-labs/routekit-config";
2
- import { assertPublishedRoutingActivation, PublishedRoutingActivation } from "@velum-labs/routekit-eval-contracts";
3
- import { makeRoutingActivationStore, RoutingActivationConflictError } from "@velum-labs/routekit-eval-store/effect";
2
+ import { AnyPublishedRoutingActivation, assertPublishedRoutingActivation, assertPublishedRoutingActivationV3 } from "@velum-labs/routekit-eval-contracts";
3
+ import { makeRoutingActivationStore, RoutingActivationConflictError, RoutingDeploymentConflictError } from "@velum-labs/routekit-eval-store/effect";
4
4
  import { ControlError } from "@velum-labs/routekit-runtime/control";
5
5
  import { Effect, Schema } from "effect";
6
- import { ActiveGateway } from "./services/active-gateway/service.js";
7
6
  import { DaemonEnv } from "./daemon-env-context.js";
8
7
  import { DaemonState } from "./daemon-state-context.js";
9
8
  import { evalRoutingSnapshotDirectory } from "./eval-routing-policy.js";
9
+ import { ActiveGateway } from "./services/active-gateway/service.js";
10
10
  function detailOf(cause) {
11
11
  return cause instanceof Error ? cause.message : String(cause);
12
12
  }
13
- function decodeActivation(value) {
14
- return Schema.decodeUnknownEffect(PublishedRoutingActivation)(value).pipe(Effect.flatMap((activation) => Effect.try({
13
+ function identity(revision) {
14
+ return revision === null
15
+ ? null
16
+ : {
17
+ version: revision.activation.version,
18
+ activationRevisionDigest: revision.activationRevisionDigest,
19
+ basisDigest: revision.activation.basisDigest,
20
+ evidenceDigest: revision.activation.evidenceDigest
21
+ };
22
+ }
23
+ function decodeAnyActivation(value) {
24
+ return Schema.decodeUnknownEffect(AnyPublishedRoutingActivation)(value).pipe(Effect.flatMap((activation) => Effect.try({
15
25
  try: () => {
16
- assertPublishedRoutingActivation(activation);
17
- const { version: _version, generatedAt: _generatedAt, ...publication } = activation;
18
- return publication;
26
+ if (activation.version === 2)
27
+ assertPublishedRoutingActivation(activation);
28
+ else
29
+ assertPublishedRoutingActivationV3(activation);
30
+ return activation;
19
31
  },
20
32
  catch: (cause) => new ControlError({
21
33
  code: "bad_request",
@@ -28,61 +40,98 @@ function decodeActivation(value) {
28
40
  message: `routing activation is invalid: ${detailOf(cause)}`
29
41
  })));
30
42
  }
43
+ const mapStoreError = (operation) => (cause) => cause instanceof RoutingActivationConflictError ||
44
+ cause instanceof RoutingDeploymentConflictError
45
+ ? new ControlError({ code: "conflict", message: cause.message })
46
+ : new ControlError({
47
+ code: "internal",
48
+ message: `failed to ${operation}: ${detailOf(cause)}`
49
+ });
50
+ function validateRuntimeCompatibility(activation) {
51
+ return Effect.gen(function* () {
52
+ const state = yield* DaemonState;
53
+ const gateway = yield* ActiveGateway;
54
+ const router = gateway.router();
55
+ if (router === undefined)
56
+ return yield* Effect.fail(new ControlError({
57
+ code: "unavailable",
58
+ message: "routing activation requires a running data gateway"
59
+ }));
60
+ const classifierModel = activation.version === 2 ? activation.classifierModel : activation.classifier.model;
61
+ if (activation.version === 2) {
62
+ const configuredClassifier = state.config.classifierModel ?? DEFAULT_CLASSIFIER_MODEL;
63
+ if (classifierModel !== configuredClassifier)
64
+ return yield* Effect.fail(new ControlError({
65
+ code: "unavailable",
66
+ message: `routing activation classifier ${JSON.stringify(classifierModel)} does not match the running classifier ${JSON.stringify(configuredClassifier)}`
67
+ }));
68
+ }
69
+ else {
70
+ if (state.config.defaultModel !== undefined &&
71
+ state.config.defaultModel !== activation.defaultModel)
72
+ return yield* Effect.fail(new ControlError({
73
+ code: "unavailable",
74
+ message: "the running default model does not match the V3 activation-pinned default"
75
+ }));
76
+ }
77
+ const served = new Set(router.modelCatalog().map((model) => model.id));
78
+ const required = [
79
+ classifierModel,
80
+ ...(activation.version === 3 ? [activation.defaultModel] : []),
81
+ ...activation.candidateModels
82
+ ];
83
+ const unavailable = required.filter((model, index, models) => !served.has(model) && models.indexOf(model) === index);
84
+ if (unavailable.length > 0)
85
+ return yield* Effect.fail(new ControlError({
86
+ code: "unavailable",
87
+ message: `routing activation references models not served by this target: ${unavailable.map((model) => JSON.stringify(model)).join(", ")}`
88
+ }));
89
+ });
90
+ }
31
91
  /** Owns target-local compositional routing status and atomic activation. */
32
92
  export class EvalRoutingApplicationService {
33
93
  handlers() {
34
94
  return {
35
95
  "evalRouting.status": () => Effect.gen(function* () {
36
96
  const env = yield* DaemonEnv;
37
- const store = makeRoutingActivationStore(evalRoutingSnapshotDirectory(env.home));
38
- const activation = yield* store.read().pipe(Effect.mapError((cause) => new ControlError({
39
- code: "internal",
40
- message: `failed to read routing activation: ${detailOf(cause)}`
41
- })));
42
- return { activation: activation ?? null };
97
+ const deployment = yield* makeRoutingActivationStore(evalRoutingSnapshotDirectory(env.home))
98
+ .readDeployment()
99
+ .pipe(Effect.mapError(mapStoreError("read routing deployment")));
100
+ return {
101
+ stateRevision: deployment.stateRevision,
102
+ authoritative: identity(deployment.authoritative),
103
+ previousAuthoritative: identity(deployment.previousAuthoritative)
104
+ };
105
+ }),
106
+ "evalRouting.activate": (params) => Effect.fail(new ControlError({
107
+ code: "bad_request",
108
+ message: "Classification V2 activation is rollback-only; install a qualified Classification V3 activation or restore the retained V2 revision with evalRouting.rollbackAuthoritative"
109
+ })),
110
+ "evalRouting.installAuthoritative": (params) => Effect.gen(function* () {
111
+ const env = yield* DaemonEnv;
112
+ const activation = yield* decodeAnyActivation(params.activation);
113
+ yield* validateRuntimeCompatibility(activation);
114
+ const deployment = yield* makeRoutingActivationStore(evalRoutingSnapshotDirectory(env.home))
115
+ .installAuthoritative(activation, params.expectedAuthoritativeRevisionDigest, params.expectedPreviousAuthoritativeRevisionDigest)
116
+ .pipe(Effect.mapError(mapStoreError("install authoritative routing policy")));
117
+ return {
118
+ installed: true,
119
+ stateRevision: deployment.stateRevision,
120
+ authoritative: identity(deployment.authoritative),
121
+ previousAuthoritative: identity(deployment.previousAuthoritative)
122
+ };
43
123
  }),
44
- "evalRouting.activate": (params) => Effect.gen(function* () {
124
+ "evalRouting.rollbackAuthoritative": (params) => Effect.gen(function* () {
45
125
  const env = yield* DaemonEnv;
46
- const state = yield* DaemonState;
47
- const gateway = yield* ActiveGateway;
48
- const publication = yield* decodeActivation(params.activation);
49
- const router = gateway.router();
50
- if (router === undefined) {
51
- return yield* Effect.fail(new ControlError({
52
- code: "unavailable",
53
- message: "routing activation requires a running data gateway"
54
- }));
55
- }
56
- const configuredClassifier = state.config.classifierModel ?? DEFAULT_CLASSIFIER_MODEL;
57
- if (publication.classifierModel !== configuredClassifier) {
58
- return yield* Effect.fail(new ControlError({
59
- code: "unavailable",
60
- message: `routing activation classifier ${JSON.stringify(publication.classifierModel)} does not match the running classifier ${JSON.stringify(configuredClassifier)}`
61
- }));
62
- }
63
- const served = new Set(router.modelCatalog().map((model) => model.id));
64
- const unavailableModels = [
65
- publication.classifierModel,
66
- ...publication.candidateModels
67
- ].filter((model, index, models) => !served.has(model) && models.indexOf(model) === index);
68
- if (unavailableModels.length > 0) {
69
- return yield* Effect.fail(new ControlError({
70
- code: "unavailable",
71
- message: `routing activation references models not served by this target: ${unavailableModels
72
- .map((model) => JSON.stringify(model))
73
- .join(", ")}`
74
- }));
75
- }
76
- const store = makeRoutingActivationStore(evalRoutingSnapshotDirectory(env.home));
77
- const activation = yield* store
78
- .publishIfCurrent(publication, params.expectedEvidenceDigest ?? undefined)
79
- .pipe(Effect.mapError((cause) => cause instanceof RoutingActivationConflictError
80
- ? new ControlError({ code: "conflict", message: cause.message })
81
- : new ControlError({
82
- code: "internal",
83
- message: `failed to activate routing policy: ${detailOf(cause)}`
84
- })));
85
- return { activated: true, activation };
126
+ const deployment = yield* makeRoutingActivationStore(evalRoutingSnapshotDirectory(env.home))
127
+ .rollbackAuthoritative(params.expectedAuthoritativeRevisionDigest, params.expectedPreviousAuthoritativeRevisionDigest)
128
+ .pipe(Effect.mapError(mapStoreError("roll back authoritative routing policy")));
129
+ return {
130
+ rolledBack: true,
131
+ stateRevision: deployment.stateRevision,
132
+ authoritative: identity(deployment.authoritative),
133
+ previousAuthoritative: identity(deployment.previousAuthoritative)
134
+ };
86
135
  })
87
136
  };
88
137
  }
@@ -1,7 +1,7 @@
1
1
  import type { SwitchingGatewayProxy } from "@velum-labs/routekit-gateway";
2
2
  import type { RunningControlServer } from "@velum-labs/routekit-runtime/control";
3
3
  import { Context, Layer, Ref } from "effect";
4
- import type { RunningGatewayGeneration } from "../../gateway-generation.js";
4
+ import type { RunningGatewayGeneration } from "../gateway-generation/service.js";
5
5
  export type ActiveGatewayState = Readonly<{
6
6
  router?: RunningGatewayGeneration;
7
7
  proxy?: SwitchingGatewayProxy;
@@ -1,10 +1,10 @@
1
1
  import type { RedeemResetCreditResult, ResetCreditSnapshot, SubscriptionAccountSetSnapshot, SubscriptionUsageResponse } from "@velum-labs/routekit-accounts";
2
2
  import type { AccountActivityService, AccountAuthService } from "@velum-labs/routekit-accounts/effect";
3
3
  import { type ProviderId, type RouterConfig } from "@velum-labs/routekit-config";
4
- import type { CatalogModelInfo, CompositionalRoutingObservation, CompositionalRoutingPolicyReader, Gateway, ProvenanceSink, ProviderSource, RequestDecomposerService } from "@velum-labs/routekit-gateway";
5
- import { RoutingBackend } from "@velum-labs/routekit-gateway";
4
+ import type { CatalogModelInfo, CompositionalRoutingObservation, CompositionalRoutingPolicyReader, Gateway, ProvenanceSink, ProviderSource } from "@velum-labs/routekit-gateway";
5
+ import { RequestDecomposer, RoutingBackend } from "@velum-labs/routekit-gateway";
6
6
  import { type RouteKitPlatform } from "@velum-labs/routekit-runtime/effect";
7
- import { Effect } from "effect";
7
+ import { Effect, Layer } from "effect";
8
8
  export type GatewayGenerationOptions = {
9
9
  config: RouterConfig;
10
10
  host?: string;
@@ -15,8 +15,8 @@ export type GatewayGenerationOptions = {
15
15
  provenance?: ProvenanceSink;
16
16
  /** Published model-by-dimension evidence used by automatic routing. */
17
17
  compositionalPolicyReader?: CompositionalRoutingPolicyReader;
18
- /** Override the default small-LM semantic dimension classifier. */
19
- requestDecomposer?: RequestDecomposerService;
18
+ /** Override the default semantic classifier Layer. */
19
+ requestDecomposerLayer?: Layer.Layer<RequestDecomposer>;
20
20
  /** Receives sanitized automatic-routing decisions and failures. */
21
21
  onCompositionalRoutingObservation?(observation: CompositionalRoutingObservation): void;
22
22
  /**
@@ -1,6 +1,6 @@
1
1
  import { CLIPROXY_API_KEY_ENV, cliproxyApiKey, closeSubscriptionAccountSets, collectSubscriptionUsage, defaultSubscriptionAccountDirectory, defaultSubscriptionCredentialPath, openSubscriptionAccountSets, SubscriptionAccountBackend, snapshotsToUsage, subscriptionRelaysFromAccountSets } from "@velum-labs/routekit-accounts";
2
2
  import { DEFAULT_CLASSIFIER_MODEL, resolveCompositionalRoutingConfig } from "@velum-labs/routekit-config";
3
- import { AnthropicBackend, ClassificationError, CodexResponsesBackend, invokeObservedModelCall, makeLanguageModelDimensionClassifier, RoutingBackend, routingModelAvailability } from "@velum-labs/routekit-gateway";
3
+ import { AnthropicBackend, CodexResponsesBackend, invokeObservedModelCall, lunaDirectRequestDecomposerLayer, RequestDecomposer, RoutingBackend, routingModelAvailability } from "@velum-labs/routekit-gateway";
4
4
  import { startGatewayEffect } from "@velum-labs/routekit-gateway/effect";
5
5
  import { RouteKitFailure, toRouteKitFailure } from "@velum-labs/routekit-runtime/effect";
6
6
  import { assertAuthenticatedBind } from "@velum-labs/routekit-runtime/network";
@@ -130,29 +130,38 @@ const acquireGatewayGeneration = Effect.fn("GatewayGeneration.acquire")(function
130
130
  yield* addOwnedFinalizer(scope, platform, closeErrors, backend.close());
131
131
  const configuredClassifierModel = options.config.classifierModel;
132
132
  const classifierModel = configuredClassifierModel ?? DEFAULT_CLASSIFIER_MODEL;
133
- const classifierComplete = (endpointId) => (body) => invokeObservedModelCall(options.provenance, {
134
- dialect: "openai-chat",
135
- body,
136
- defaultModel: backend.defaultModel,
137
- requestedModel: classifierModel,
138
- endpointId,
139
- invoke: (callId, signal, onAttribution) => backend.chat(body, signal, {
140
- modelCallId: callId,
141
- responseMode: "buffered",
142
- onAttribution
143
- })
144
- }).pipe(Effect.provide(platform));
145
- const unavailableClassifier = () => Effect.fail(new ClassificationError({
146
- message: `classifier model ${JSON.stringify(configuredClassifierModel ?? DEFAULT_CLASSIFIER_MODEL)} is unavailable; configure classifierModel to a served model`
147
- }));
148
- const compositionalConfig = resolveCompositionalRoutingConfig(options.config);
149
- const requestDecomposer = options.requestDecomposer ??
150
- (backend.ports.models.serves(classifierModel)
151
- ? makeLanguageModelDimensionClassifier({
152
- model: classifierModel,
153
- complete: classifierComplete("dimension-request-classifier")
133
+ const classifierComplete = (endpointId) => (body) => {
134
+ const requestedModel = typeof body === "object" &&
135
+ body !== null &&
136
+ !Array.isArray(body) &&
137
+ typeof body.model === "string"
138
+ ? body.model.trim()
139
+ : "";
140
+ if (requestedModel.length === 0) {
141
+ return Effect.fail(new RouteKitFailure({
142
+ message: "classifier completion requires an explicit provider/model id"
143
+ }));
144
+ }
145
+ return invokeObservedModelCall(options.provenance, {
146
+ dialect: "openai-chat",
147
+ body,
148
+ defaultModel: requestedModel,
149
+ requestedModel,
150
+ endpointId,
151
+ invoke: (callId, signal, onAttribution) => backend.chat(body, signal, {
152
+ modelCallId: callId,
153
+ responseMode: "buffered",
154
+ onAttribution
154
155
  })
155
- : { classify: unavailableClassifier });
156
+ }).pipe(Effect.provide(platform));
157
+ };
158
+ const compositionalConfig = resolveCompositionalRoutingConfig(options.config);
159
+ const requestDecomposer = yield* RequestDecomposer.pipe(Effect.provide(options.requestDecomposerLayer ??
160
+ lunaDirectRequestDecomposerLayer({
161
+ model: classifierModel,
162
+ modelAvailable: (model) => backend.ports.models.serves(model),
163
+ complete: (body) => classifierComplete("dimension-request-classifier")(body)
164
+ })));
156
165
  const compositionalRouting = {
157
166
  policyReader: options.compositionalPolicyReader,
158
167
  classifier: requestDecomposer,
@@ -97,6 +97,8 @@ test("application services expose concrete bounded handler groups", () => {
97
97
  ]);
98
98
  assert.deepEqual(Object.keys(new EvalRoutingApplicationService().handlers()).sort(), [
99
99
  "evalRouting.activate",
100
+ "evalRouting.installAuthoritative",
101
+ "evalRouting.rollbackAuthoritative",
100
102
  "evalRouting.status"
101
103
  ]);
102
104
  assert.deepEqual(Object.keys(new TelemetryApplicationService().handlers()).sort(), [
@@ -1,8 +1,9 @@
1
1
  import assert from "node:assert/strict";
2
- import { mkdtempSync, rmSync } from "node:fs";
2
+ import { mkdtempSync, readFileSync, rmSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import test from "node:test";
6
+ import { parseRouterConfig } from "@velum-labs/routekit-config";
6
7
  import { ControlError } from "@velum-labs/routekit-runtime/control";
7
8
  import { runRouteKitEffect } from "@velum-labs/routekit-runtime/effect";
8
9
  import { Effect, Layer, Ref } from "effect";
@@ -11,47 +12,7 @@ import { ActiveGateway } from "../services/active-gateway/service.js";
11
12
  import { DaemonEnv } from "../daemon-env-context.js";
12
13
  import { DaemonState } from "../daemon-state-context.js";
13
14
  import { EvalRoutingApplicationService } from "../eval-routing-service.js";
14
- function activation(evidenceDigest) {
15
- const dimensions = [
16
- "gateway-protocol",
17
- "eval-routing",
18
- "account-pooling",
19
- "typescript-maintenance",
20
- "release-operations"
21
- ].map((id) => ({
22
- id,
23
- description: `Requests about ${id}`,
24
- includes: [`Tasks specifically involving ${id}`],
25
- excludes: [`Tasks unrelated to ${id}`]
26
- }));
27
- return {
28
- version: 2,
29
- generatedAt: "2026-08-18T00:00:00.000Z",
30
- basisDigest: "basis-1",
31
- evidenceDigest,
32
- classifierModel: "openai/classifier",
33
- objective: { kind: "highest-quality" },
34
- maximumUnknownWeight: 0.2,
35
- dimensions,
36
- candidateModels: ["openai/model-a"],
37
- evidence: dimensions.map((dimension) => ({
38
- model: "openai/model-a",
39
- dimensionId: dimension.id,
40
- suiteDigest: `suite-${dimension.id}`,
41
- evidenceDigest: `cell-${dimension.id}`,
42
- quality: {
43
- passRate: 0.9,
44
- lowerConfidenceBound: 0.8,
45
- sampleCount: 20
46
- },
47
- failureRate: 0.1,
48
- averageJudgeScore: 0.85,
49
- p95DurationMs: 1_000,
50
- unpricedCalls: 20
51
- }))
52
- };
53
- }
54
- test("routing activation handlers expose status and enforce compare-and-swap", async () => {
15
+ test("legacy V2 publication is rejected because V2 is rollback-only", async () => {
55
16
  const home = mkdtempSync(join(tmpdir(), "routekit-routing-activation-"));
56
17
  const daemonEnv = Layer.succeed(DaemonEnv, DaemonEnv.of({
57
18
  home,
@@ -72,9 +33,9 @@ test("routing activation handlers expose status and enforce compare-and-swap", a
72
33
  revisions: { daemon: 1, config: 1, accounts: 1 }
73
34
  });
74
35
  const activeGateway = Layer.succeed(ActiveGateway, (() => {
75
- const router = ({
36
+ const router = {
76
37
  modelCatalog: () => ["openai/classifier", "openai/model-a"].map((id) => ({ id }))
77
- });
38
+ };
78
39
  const state = Ref.makeUnsafe({ router, dataUrl: "http://127.0.0.1:8080" });
79
40
  return ActiveGateway.of({
80
41
  state,
@@ -89,32 +50,89 @@ test("routing activation handlers expose status and enforce compare-and-swap", a
89
50
  const run = (effect) => runRouteKitEffect(effect.pipe(Effect.provide(services)));
90
51
  try {
91
52
  assert.deepEqual(await run(handlers["evalRouting.status"]({}, undefined)), {
92
- activation: null
53
+ stateRevision: 0,
54
+ authoritative: null,
55
+ previousAuthoritative: null
93
56
  });
94
- const first = await run(handlers["evalRouting.activate"]({
95
- expectedEvidenceDigest: null,
96
- activation: activation("evidence-first")
97
- }, undefined));
98
- assert.equal(first.activated, true);
99
- assert.equal(first.activation.evidenceDigest, "evidence-first");
100
57
  await assert.rejects(run(handlers["evalRouting.activate"]({
101
58
  expectedEvidenceDigest: null,
102
- activation: activation("evidence-stale")
103
- }, undefined)), (error) => error instanceof ControlError && error.code === "conflict");
104
- await assert.rejects(run(handlers["evalRouting.activate"]({
105
- expectedEvidenceDigest: "evidence-first",
106
59
  activation: {
107
- ...activation("wrong-classifier"),
108
- classifierModel: "openai/other-classifier"
60
+ version: 2,
61
+ generatedAt: "2026-08-18T00:00:00.000Z",
62
+ basisDigest: "legacy-basis",
63
+ evidenceDigest: "legacy-evidence",
64
+ classifierModel: "openai/classifier",
65
+ objective: { kind: "highest-quality" },
66
+ maximumUnknownWeight: 0.2,
67
+ dimensions: [],
68
+ candidateModels: [],
69
+ evidence: []
109
70
  }
110
- }, undefined)), (error) => error instanceof ControlError && error.code === "unavailable");
111
- const second = await run(handlers["evalRouting.activate"]({
112
- expectedEvidenceDigest: "evidence-first",
113
- activation: activation("evidence-second")
114
- }, undefined));
115
- assert.equal(second.activation.evidenceDigest, "evidence-second");
116
- assert.equal((await run(handlers["evalRouting.status"]({}, undefined)))
117
- .activation.evidenceDigest, "evidence-second");
71
+ }, undefined)), (error) => error instanceof ControlError &&
72
+ error.code === "bad_request" &&
73
+ /V2 activation is rollback-only/u.test(error.message));
74
+ assert.deepEqual(await run(handlers["evalRouting.status"]({}, undefined)), {
75
+ stateRevision: 0,
76
+ authoritative: null,
77
+ previousAuthoritative: null
78
+ });
79
+ }
80
+ finally {
81
+ rmSync(home, { recursive: true, force: true });
82
+ }
83
+ });
84
+ test("V3 authority installs when its pinned models match the running gateway", async () => {
85
+ const home = mkdtempSync(join(tmpdir(), "routekit-routing-v3-authority-"));
86
+ const activationV3 = JSON.parse(readFileSync(new URL("../../../../test/fixtures/routing-v3/examples/published-routing-activation-v3.example.json", import.meta.url), "utf8"));
87
+ const daemonEnv = Layer.succeed(DaemonEnv, DaemonEnv.of({
88
+ home,
89
+ configPath: join(home, "router.yaml"),
90
+ env: {},
91
+ packageVersion: "0.0.0-test",
92
+ generation: 1,
93
+ startedAt: "2026-08-23T00:00:00.000Z",
94
+ hosted: undefined
95
+ }));
96
+ const runtimeState = new DaemonRuntimeState({
97
+ config: parseRouterConfig({
98
+ providers: { openai: {}, anthropic: {} },
99
+ defaultModel: activationV3.defaultModel
100
+ }),
101
+ document: "",
102
+ revisions: { daemon: 1, config: 1, accounts: 1 }
103
+ });
104
+ const requiredModels = [
105
+ activationV3.classifier.model,
106
+ activationV3.defaultModel,
107
+ ...activationV3.candidateModels
108
+ ];
109
+ const activeGateway = Layer.succeed(ActiveGateway, (() => {
110
+ const router = {
111
+ modelCatalog: () => [...new Set(requiredModels)].map((id) => ({
112
+ id
113
+ }))
114
+ };
115
+ const state = Ref.makeUnsafe({ router, dataUrl: "http://127.0.0.1:8080" });
116
+ return ActiveGateway.of({
117
+ state,
118
+ router: () => router,
119
+ proxy: () => undefined,
120
+ dataUrl: () => "http://127.0.0.1:8080",
121
+ control: () => undefined
122
+ });
123
+ })());
124
+ const services = Layer.mergeAll(daemonEnv, DaemonState.layer(runtimeState), activeGateway);
125
+ const handler = new EvalRoutingApplicationService().handlers()["evalRouting.installAuthoritative"];
126
+ const run = (effect) => runRouteKitEffect(effect.pipe(Effect.provide(services)));
127
+ const params = {
128
+ expectedAuthoritativeRevisionDigest: null,
129
+ expectedPreviousAuthoritativeRevisionDigest: null,
130
+ activation: activationV3
131
+ };
132
+ try {
133
+ const installed = await run(handler(params, undefined));
134
+ assert.equal(installed.installed, true);
135
+ assert.equal(installed.authoritative.version, 3);
118
136
  }
119
137
  finally {
120
138
  rmSync(home, { recursive: true, force: true });
@@ -6,7 +6,7 @@ import test from "node:test";
6
6
  import { makeRoutingActivationStore } from "@velum-labs/routekit-eval-store/effect";
7
7
  import { runRouteKitEffect } from "@velum-labs/routekit-runtime/effect";
8
8
  import { evalRoutingSnapshotDirectory, makeCompositionalRoutingPolicyReader } from "../eval-routing-policy.js";
9
- test("daemon compositional reader observes publications and falls back to previous", async () => {
9
+ test("daemon compositional reader projects authority and falls back to previous V2", async () => {
10
10
  const home = mkdtempSync(join(tmpdir(), "routekit-daemon-compositional-policy-"));
11
11
  const dimensions = [
12
12
  "code-change",
@@ -41,13 +41,13 @@ test("daemon compositional reader observes publications and falls back to previo
41
41
  });
42
42
  try {
43
43
  const reader = makeCompositionalRoutingPolicyReader(home);
44
- assert.equal(await runRouteKitEffect(reader.getSnapshot()), undefined);
44
+ assert.equal(await runRouteKitEffect(reader.getActivation()), undefined);
45
45
  const store = makeRoutingActivationStore(evalRoutingSnapshotDirectory(home));
46
46
  await runRouteKitEffect(store.publish(publication("first")));
47
47
  await runRouteKitEffect(store.publish(publication("second")));
48
- assert.equal((await runRouteKitEffect(reader.getSnapshot()))?.evidenceDigest, "second");
49
- writeFileSync(join(evalRoutingSnapshotDirectory(home), "published-routing.json"), '{"version":2}');
50
- assert.equal((await runRouteKitEffect(reader.getSnapshot()))?.evidenceDigest, "first");
48
+ assert.equal((await runRouteKitEffect(reader.getActivation()))?.evidenceDigest, "second");
49
+ writeFileSync(join(evalRoutingSnapshotDirectory(home), "routing-deployment.v1.json"), '{"version":2}');
50
+ assert.equal((await runRouteKitEffect(reader.getActivation()))?.evidenceDigest, "first");
51
51
  }
52
52
  finally {
53
53
  rmSync(home, { recursive: true, force: true });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@velum-labs/routekit-daemon",
3
3
  "private": false,
4
- "version": "1.0.26",
4
+ "version": "1.1.1",
5
5
  "description": "Singleton RouteKit control daemon and stable model gateway.",
6
6
  "repository": {
7
7
  "type": "git",
@@ -44,15 +44,15 @@
44
44
  "effect": "4.0.0-rc.108",
45
45
  "posthog-node": "5.46.1",
46
46
  "yaml": "2.9.0",
47
- "@velum-labs/routekit-accounts": "1.0.26",
48
- "@velum-labs/routekit-config": "1.0.26",
49
- "@velum-labs/routekit-control": "1.0.26",
50
- "@velum-labs/routekit-eval-contracts": "1.0.26",
51
- "@velum-labs/routekit-eval-store": "1.0.26",
52
- "@velum-labs/routekit-gateway": "1.0.26",
53
- "@velum-labs/routekit-registry": "1.0.26",
54
- "@velum-labs/routekit-runtime": "1.0.26",
55
- "@velum-labs/routekit-telemetry-core": "1.0.26"
47
+ "@velum-labs/routekit-accounts": "1.1.1",
48
+ "@velum-labs/routekit-config": "1.1.1",
49
+ "@velum-labs/routekit-control": "1.1.1",
50
+ "@velum-labs/routekit-eval-contracts": "1.1.1",
51
+ "@velum-labs/routekit-eval-store": "1.1.1",
52
+ "@velum-labs/routekit-gateway": "1.1.1",
53
+ "@velum-labs/routekit-registry": "1.1.1",
54
+ "@velum-labs/routekit-runtime": "1.1.1",
55
+ "@velum-labs/routekit-telemetry-core": "1.1.1"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsc -b",