@velum-labs/routekit-daemon 1.0.25 → 1.1.0

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,16 +1,26 @@
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, PublishedRoutingActivation } 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) {
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 decodeLegacyActivation(value) {
14
24
  return Schema.decodeUnknownEffect(PublishedRoutingActivation)(value).pipe(Effect.flatMap((activation) => Effect.try({
15
25
  try: () => {
16
26
  assertPublishedRoutingActivation(activation);
@@ -28,61 +38,123 @@ function decodeActivation(value) {
28
38
  message: `routing activation is invalid: ${detailOf(cause)}`
29
39
  })));
30
40
  }
41
+ function decodeAnyActivation(value) {
42
+ return Schema.decodeUnknownEffect(AnyPublishedRoutingActivation)(value).pipe(Effect.flatMap((activation) => Effect.try({
43
+ try: () => {
44
+ if (activation.version === 2)
45
+ assertPublishedRoutingActivation(activation);
46
+ else
47
+ assertPublishedRoutingActivationV3(activation);
48
+ return activation;
49
+ },
50
+ catch: (cause) => new ControlError({
51
+ code: "bad_request",
52
+ message: `routing activation is invalid: ${detailOf(cause)}`
53
+ })
54
+ })), Effect.mapError((cause) => cause instanceof ControlError
55
+ ? cause
56
+ : new ControlError({
57
+ code: "bad_request",
58
+ message: `routing activation is invalid: ${detailOf(cause)}`
59
+ })));
60
+ }
61
+ const mapStoreError = (operation) => (cause) => cause instanceof RoutingActivationConflictError ||
62
+ cause instanceof RoutingDeploymentConflictError
63
+ ? new ControlError({ code: "conflict", message: cause.message })
64
+ : new ControlError({
65
+ code: "internal",
66
+ message: `failed to ${operation}: ${detailOf(cause)}`
67
+ });
68
+ function validateRuntimeCompatibility(activation) {
69
+ return Effect.gen(function* () {
70
+ const state = yield* DaemonState;
71
+ const gateway = yield* ActiveGateway;
72
+ const router = gateway.router();
73
+ if (router === undefined)
74
+ return yield* Effect.fail(new ControlError({
75
+ code: "unavailable",
76
+ message: "routing activation requires a running data gateway"
77
+ }));
78
+ const classifierModel = activation.version === 2 ? activation.classifierModel : activation.classifier.model;
79
+ if (activation.version === 2) {
80
+ const configuredClassifier = state.config.classifierModel ?? DEFAULT_CLASSIFIER_MODEL;
81
+ if (classifierModel !== configuredClassifier)
82
+ return yield* Effect.fail(new ControlError({
83
+ code: "unavailable",
84
+ message: `routing activation classifier ${JSON.stringify(classifierModel)} does not match the running classifier ${JSON.stringify(configuredClassifier)}`
85
+ }));
86
+ }
87
+ else {
88
+ if (state.config.defaultModel !== undefined &&
89
+ state.config.defaultModel !== activation.defaultModel)
90
+ return yield* Effect.fail(new ControlError({
91
+ code: "unavailable",
92
+ message: "the running default model does not match the V3 activation-pinned default"
93
+ }));
94
+ }
95
+ const served = new Set(router.modelCatalog().map((model) => model.id));
96
+ const required = [
97
+ classifierModel,
98
+ ...(activation.version === 3 ? [activation.defaultModel] : []),
99
+ ...activation.candidateModels
100
+ ];
101
+ const unavailable = required.filter((model, index, models) => !served.has(model) && models.indexOf(model) === index);
102
+ if (unavailable.length > 0)
103
+ return yield* Effect.fail(new ControlError({
104
+ code: "unavailable",
105
+ message: `routing activation references models not served by this target: ${unavailable.map((model) => JSON.stringify(model)).join(", ")}`
106
+ }));
107
+ });
108
+ }
31
109
  /** Owns target-local compositional routing status and atomic activation. */
32
110
  export class EvalRoutingApplicationService {
33
111
  handlers() {
34
112
  return {
35
113
  "evalRouting.status": () => Effect.gen(function* () {
36
114
  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 };
115
+ const deployment = yield* makeRoutingActivationStore(evalRoutingSnapshotDirectory(env.home))
116
+ .readDeployment()
117
+ .pipe(Effect.mapError(mapStoreError("read routing deployment")));
118
+ return {
119
+ stateRevision: deployment.stateRevision,
120
+ authoritative: identity(deployment.authoritative),
121
+ previousAuthoritative: identity(deployment.previousAuthoritative)
122
+ };
43
123
  }),
44
124
  "evalRouting.activate": (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
126
+ const publication = yield* decodeLegacyActivation(params.activation);
127
+ yield* validateRuntimeCompatibility(params.activation);
128
+ const activation = yield* makeRoutingActivationStore(evalRoutingSnapshotDirectory(env.home))
78
129
  .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
- })));
130
+ .pipe(Effect.mapError(mapStoreError("activate routing policy")));
85
131
  return { activated: true, activation };
132
+ }),
133
+ "evalRouting.installAuthoritative": (params) => Effect.gen(function* () {
134
+ const env = yield* DaemonEnv;
135
+ const activation = yield* decodeAnyActivation(params.activation);
136
+ yield* validateRuntimeCompatibility(activation);
137
+ const deployment = yield* makeRoutingActivationStore(evalRoutingSnapshotDirectory(env.home))
138
+ .installAuthoritative(activation, params.expectedAuthoritativeRevisionDigest, params.expectedPreviousAuthoritativeRevisionDigest)
139
+ .pipe(Effect.mapError(mapStoreError("install authoritative routing policy")));
140
+ return {
141
+ installed: true,
142
+ stateRevision: deployment.stateRevision,
143
+ authoritative: identity(deployment.authoritative),
144
+ previousAuthoritative: identity(deployment.previousAuthoritative)
145
+ };
146
+ }),
147
+ "evalRouting.rollbackAuthoritative": (params) => Effect.gen(function* () {
148
+ const env = yield* DaemonEnv;
149
+ const deployment = yield* makeRoutingActivationStore(evalRoutingSnapshotDirectory(env.home))
150
+ .rollbackAuthoritative(params.expectedAuthoritativeRevisionDigest, params.expectedPreviousAuthoritativeRevisionDigest)
151
+ .pipe(Effect.mapError(mapStoreError("roll back authoritative routing policy")));
152
+ return {
153
+ rolledBack: true,
154
+ stateRevision: deployment.stateRevision,
155
+ authoritative: identity(deployment.authoritative),
156
+ previousAuthoritative: identity(deployment.previousAuthoritative)
157
+ };
86
158
  })
87
159
  };
88
160
  }
@@ -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";
@@ -72,9 +73,9 @@ test("routing activation handlers expose status and enforce compare-and-swap", a
72
73
  revisions: { daemon: 1, config: 1, accounts: 1 }
73
74
  });
74
75
  const activeGateway = Layer.succeed(ActiveGateway, (() => {
75
- const router = ({
76
+ const router = {
76
77
  modelCatalog: () => ["openai/classifier", "openai/model-a"].map((id) => ({ id }))
77
- });
78
+ };
78
79
  const state = Ref.makeUnsafe({ router, dataUrl: "http://127.0.0.1:8080" });
79
80
  return ActiveGateway.of({
80
81
  state,
@@ -89,7 +90,9 @@ test("routing activation handlers expose status and enforce compare-and-swap", a
89
90
  const run = (effect) => runRouteKitEffect(effect.pipe(Effect.provide(services)));
90
91
  try {
91
92
  assert.deepEqual(await run(handlers["evalRouting.status"]({}, undefined)), {
92
- activation: null
93
+ stateRevision: 0,
94
+ authoritative: null,
95
+ previousAuthoritative: null
93
96
  });
94
97
  const first = await run(handlers["evalRouting.activate"]({
95
98
  expectedEvidenceDigest: null,
@@ -113,8 +116,65 @@ test("routing activation handlers expose status and enforce compare-and-swap", a
113
116
  activation: activation("evidence-second")
114
117
  }, undefined));
115
118
  assert.equal(second.activation.evidenceDigest, "evidence-second");
116
- assert.equal((await run(handlers["evalRouting.status"]({}, undefined)))
117
- .activation.evidenceDigest, "evidence-second");
119
+ assert.equal((await run(handlers["evalRouting.status"]({}, undefined))).authoritative
120
+ ?.evidenceDigest, "evidence-second");
121
+ }
122
+ finally {
123
+ rmSync(home, { recursive: true, force: true });
124
+ }
125
+ });
126
+ test("V3 authority installs when its pinned models match the running gateway", async () => {
127
+ const home = mkdtempSync(join(tmpdir(), "routekit-routing-v3-authority-"));
128
+ const activationV3 = JSON.parse(readFileSync(new URL("../../../../test/fixtures/routing-v3/examples/published-routing-activation-v3.example.json", import.meta.url), "utf8"));
129
+ const daemonEnv = Layer.succeed(DaemonEnv, DaemonEnv.of({
130
+ home,
131
+ configPath: join(home, "router.yaml"),
132
+ env: {},
133
+ packageVersion: "0.0.0-test",
134
+ generation: 1,
135
+ startedAt: "2026-08-23T00:00:00.000Z",
136
+ hosted: undefined
137
+ }));
138
+ const runtimeState = new DaemonRuntimeState({
139
+ config: parseRouterConfig({
140
+ providers: { openai: {}, anthropic: {} },
141
+ defaultModel: activationV3.defaultModel
142
+ }),
143
+ document: "",
144
+ revisions: { daemon: 1, config: 1, accounts: 1 }
145
+ });
146
+ const requiredModels = [
147
+ activationV3.classifier.model,
148
+ activationV3.defaultModel,
149
+ ...activationV3.candidateModels
150
+ ];
151
+ const activeGateway = Layer.succeed(ActiveGateway, (() => {
152
+ const router = {
153
+ modelCatalog: () => [...new Set(requiredModels)].map((id) => ({
154
+ id
155
+ }))
156
+ };
157
+ const state = Ref.makeUnsafe({ router, dataUrl: "http://127.0.0.1:8080" });
158
+ return ActiveGateway.of({
159
+ state,
160
+ router: () => router,
161
+ proxy: () => undefined,
162
+ dataUrl: () => "http://127.0.0.1:8080",
163
+ control: () => undefined
164
+ });
165
+ })());
166
+ const services = Layer.mergeAll(daemonEnv, DaemonState.layer(runtimeState), activeGateway);
167
+ const handler = new EvalRoutingApplicationService().handlers()["evalRouting.installAuthoritative"];
168
+ const run = (effect) => runRouteKitEffect(effect.pipe(Effect.provide(services)));
169
+ const params = {
170
+ expectedAuthoritativeRevisionDigest: null,
171
+ expectedPreviousAuthoritativeRevisionDigest: null,
172
+ activation: activationV3
173
+ };
174
+ try {
175
+ const installed = await run(handler(params, undefined));
176
+ assert.equal(installed.installed, true);
177
+ assert.equal(installed.authoritative.version, 3);
118
178
  }
119
179
  finally {
120
180
  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.25",
4
+ "version": "1.1.0",
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.25",
48
- "@velum-labs/routekit-config": "1.0.25",
49
- "@velum-labs/routekit-control": "1.0.25",
50
- "@velum-labs/routekit-eval-contracts": "1.0.25",
51
- "@velum-labs/routekit-eval-store": "1.0.25",
52
- "@velum-labs/routekit-gateway": "1.0.25",
53
- "@velum-labs/routekit-registry": "1.0.25",
54
- "@velum-labs/routekit-runtime": "1.0.25",
55
- "@velum-labs/routekit-telemetry-core": "1.0.25"
47
+ "@velum-labs/routekit-accounts": "1.1.0",
48
+ "@velum-labs/routekit-config": "1.1.0",
49
+ "@velum-labs/routekit-control": "1.1.0",
50
+ "@velum-labs/routekit-eval-contracts": "1.1.0",
51
+ "@velum-labs/routekit-eval-store": "1.1.0",
52
+ "@velum-labs/routekit-gateway": "1.1.0",
53
+ "@velum-labs/routekit-registry": "1.1.0",
54
+ "@velum-labs/routekit-runtime": "1.1.0",
55
+ "@velum-labs/routekit-telemetry-core": "1.1.0"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsc -b",