@velum-labs/routekit-daemon 1.3.2 → 1.5.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.
- package/dist/account-enroll-service.d.ts +1 -3
- package/dist/account-enroll-service.js +262 -262
- package/dist/account-mutation-service.d.ts +1 -3
- package/dist/account-mutation-service.js +302 -283
- package/dist/account-query-service.d.ts +1 -3
- package/dist/account-query-service.js +88 -86
- package/dist/call-attribution-store.js +544 -1
- package/dist/control-dispatch.js +9 -15
- package/dist/daemon-bootstrap-preflight.js +1 -1
- package/dist/daemon-control-handlers.d.ts +1 -1
- package/dist/daemon-control-handlers.js +24 -27
- package/dist/daemon-generations.d.ts +14 -28
- package/dist/daemon-generations.js +114 -53
- package/dist/daemon-lifecycle-service.d.ts +1 -3
- package/dist/daemon-lifecycle-service.js +88 -94
- package/dist/daemon-maintenance.d.ts +27 -49
- package/dist/daemon-maintenance.js +5 -1
- package/dist/daemon-options.d.ts +3 -2
- package/dist/daemon-policy-context.d.ts +1 -0
- package/dist/daemon-policy-context.js +1 -0
- package/dist/daemon-runtime-state.d.ts +1 -1
- package/dist/daemon-runtime-state.js +2 -3
- package/dist/daemon-state-context.d.ts +1 -1
- package/dist/daemon-state-context.js +1 -38
- package/dist/doctor-service.d.ts +1 -3
- package/dist/doctor-service.js +87 -91
- package/dist/effect/daemon-live.d.ts +7 -6
- package/dist/effect/daemon-live.js +57 -135
- package/dist/eval-routing-service.d.ts +1 -3
- package/dist/eval-routing-service.js +45 -69
- package/dist/eval-session-execution-store.d.ts +24 -0
- package/dist/eval-session-execution-store.js +253 -0
- package/dist/host-protocol.d.ts +1 -0
- package/dist/host.js +3 -0
- package/dist/internal-providers/provider-anthropic/index.d.ts +23 -0
- package/dist/internal-providers/provider-anthropic/index.js +42 -0
- package/dist/internal-providers/provider-anthropic/test/provider.test.js +33 -0
- package/dist/internal-providers/provider-azure/index.d.ts +25 -0
- package/dist/internal-providers/provider-azure/index.js +101 -0
- package/dist/internal-providers/provider-azure/test/provider.test.js +83 -0
- package/dist/internal-providers/provider-bedrock/codec.d.ts +11 -0
- package/dist/internal-providers/provider-bedrock/codec.js +484 -0
- package/dist/internal-providers/provider-bedrock/connection-settings.d.ts +6 -0
- package/dist/internal-providers/provider-bedrock/connection-settings.js +41 -0
- package/dist/internal-providers/provider-bedrock/connection.d.ts +17 -0
- package/dist/internal-providers/provider-bedrock/connection.js +143 -0
- package/dist/internal-providers/provider-bedrock/index.d.ts +27 -0
- package/dist/internal-providers/provider-bedrock/index.js +47 -0
- package/dist/internal-providers/provider-bedrock/source.d.ts +39 -0
- package/dist/internal-providers/provider-bedrock/source.js +454 -0
- package/dist/internal-providers/provider-bedrock/test/bedrock-source.test.d.ts +1 -0
- package/dist/internal-providers/provider-bedrock/test/bedrock-source.test.js +786 -0
- package/dist/internal-providers/provider-bedrock/test/connection.test.d.ts +1 -0
- package/dist/internal-providers/provider-bedrock/test/connection.test.js +249 -0
- package/dist/internal-providers/provider-bedrock/test/workload-credentials.test.d.ts +1 -0
- package/dist/internal-providers/provider-bedrock/test/workload-credentials.test.js +29 -0
- package/dist/internal-providers/provider-bedrock/workload-credentials.d.ts +5 -0
- package/dist/internal-providers/provider-bedrock/workload-credentials.js +14 -0
- package/dist/internal-providers/provider-claude-code/index.d.ts +6 -0
- package/dist/internal-providers/provider-claude-code/index.js +4 -0
- package/dist/internal-providers/provider-claude-code/module-api.d.ts +25 -0
- package/dist/internal-providers/provider-claude-code/module-api.js +31 -0
- package/dist/internal-providers/provider-claude-code/relay.d.ts +20 -0
- package/dist/internal-providers/provider-claude-code/relay.js +115 -0
- package/dist/internal-providers/provider-claude-code/subscription.d.ts +2 -0
- package/dist/internal-providers/provider-claude-code/subscription.js +194 -0
- package/dist/internal-providers/provider-claude-code/test/http-submission.test.d.ts +1 -0
- package/dist/internal-providers/provider-claude-code/test/http-submission.test.js +140 -0
- package/dist/internal-providers/provider-claude-code/test/relay.test.d.ts +1 -0
- package/dist/internal-providers/provider-claude-code/test/relay.test.js +379 -0
- package/dist/internal-providers/provider-claude-code/test/subscription.test.d.ts +1 -0
- package/dist/internal-providers/provider-claude-code/test/subscription.test.js +256 -0
- package/dist/internal-providers/provider-cliproxy/index.d.ts +9 -0
- package/dist/internal-providers/provider-cliproxy/index.js +15 -0
- package/dist/internal-providers/provider-codex/index.d.ts +6 -0
- package/dist/internal-providers/provider-codex/index.js +7 -0
- package/dist/internal-providers/provider-codex/module-api.d.ts +25 -0
- package/dist/internal-providers/provider-codex/module-api.js +31 -0
- package/dist/internal-providers/provider-codex/relay.d.ts +123 -0
- package/dist/internal-providers/provider-codex/relay.js +261 -0
- package/dist/internal-providers/provider-codex/subscription.d.ts +2 -0
- package/dist/internal-providers/provider-codex/subscription.js +555 -0
- package/dist/internal-providers/provider-codex/test/http-submission.test.d.ts +1 -0
- package/dist/internal-providers/provider-codex/test/http-submission.test.js +142 -0
- package/dist/internal-providers/provider-codex/test/relay.test.d.ts +1 -0
- package/dist/internal-providers/provider-codex/test/relay.test.js +600 -0
- package/dist/internal-providers/provider-codex/test/subscription.test.d.ts +1 -0
- package/dist/internal-providers/provider-codex/test/subscription.test.js +70 -0
- package/dist/internal-providers/provider-codex/test/translated-content.test.d.ts +1 -0
- package/dist/internal-providers/provider-codex/test/translated-content.test.js +91 -0
- package/dist/internal-providers/provider-custom/index.d.ts +9 -0
- package/dist/internal-providers/provider-custom/index.js +15 -0
- package/dist/internal-providers/provider-google/index.d.ts +23 -0
- package/dist/internal-providers/provider-google/index.js +43 -0
- package/dist/internal-providers/provider-google/test/provider.test.d.ts +1 -0
- package/dist/internal-providers/provider-google/test/provider.test.js +34 -0
- package/dist/internal-providers/provider-google-vertex/index.d.ts +28 -0
- package/dist/internal-providers/provider-google-vertex/index.js +137 -0
- package/dist/internal-providers/provider-google-vertex/test/provider.test.d.ts +1 -0
- package/dist/internal-providers/provider-google-vertex/test/provider.test.js +85 -0
- package/dist/internal-providers/provider-openai/documented-reasoning.d.ts +9 -0
- package/dist/internal-providers/provider-openai/documented-reasoning.js +26 -0
- package/dist/internal-providers/provider-openai/index.d.ts +23 -0
- package/dist/internal-providers/provider-openai/index.js +74 -0
- package/dist/internal-providers/provider-openai/test/provider.test.d.ts +1 -0
- package/dist/internal-providers/provider-openai/test/provider.test.js +268 -0
- package/dist/internal-providers/provider-openai-compatible/index.d.ts +9 -0
- package/dist/internal-providers/provider-openai-compatible/index.js +15 -0
- package/dist/internal-providers/provider-openai-compatible/test/provider.test.d.ts +1 -0
- package/dist/internal-providers/provider-openai-compatible/test/provider.test.js +40 -0
- package/dist/internal-providers/provider-openrouter/connection.d.ts +5 -0
- package/dist/internal-providers/provider-openrouter/connection.js +143 -0
- package/dist/internal-providers/provider-openrouter/index.d.ts +28 -0
- package/dist/internal-providers/provider-openrouter/index.js +42 -0
- package/dist/internal-providers/provider-openrouter/pricing.d.ts +4 -0
- package/dist/internal-providers/provider-openrouter/pricing.js +108 -0
- package/dist/internal-providers/provider-openrouter/test/connection.test.d.ts +1 -0
- package/dist/internal-providers/provider-openrouter/test/connection.test.js +71 -0
- package/dist/internal-providers/provider-openrouter/test/pricing.test.d.ts +1 -0
- package/dist/internal-providers/provider-openrouter/test/pricing.test.js +45 -0
- package/dist/internal-providers/provider-openrouter/test/provider.test.d.ts +1 -0
- package/dist/internal-providers/provider-openrouter/test/provider.test.js +141 -0
- package/dist/internal-providers/provider-openrouter/test/validation.test.d.ts +1 -0
- package/dist/internal-providers/provider-openrouter/test/validation.test.js +225 -0
- package/dist/launcher-service.d.ts +3 -7
- package/dist/launcher-service.js +144 -133
- package/dist/provider-modules.d.ts +6 -0
- package/dist/provider-modules.js +55 -0
- package/dist/provider-query-service.d.ts +1 -3
- package/dist/provider-query-service.js +140 -143
- package/dist/router-generation-service.d.ts +1 -3
- package/dist/router-generation-service.js +83 -78
- package/dist/services/eval-session/service.d.ts +20 -8
- package/dist/services/eval-session/service.js +410 -40
- package/dist/services/gateway-generation/service.d.ts +7 -3
- package/dist/services/gateway-generation/service.js +126 -63
- package/dist/services/generations/service.d.ts +3 -3
- package/dist/services/generations/service.js +5 -1
- package/dist/telemetry-application-service.d.ts +1 -3
- package/dist/telemetry-application-service.js +41 -45
- package/dist/test/call-attribution-store.test.js +186 -0
- package/dist/test/daemon-accounts.test.js +2 -2
- package/dist/test/daemon-data-plane.test.js +24 -45
- package/dist/test/daemon-fixtures.js +1 -1
- package/dist/test/daemon-generation-transaction.test.js +66 -2
- package/dist/test/daemon-runtime-state.test.js +28 -1
- package/dist/test/effect-daemon.test.js +38 -0
- package/dist/test/eval-routing-application-service.test.js +6 -6
- package/dist/test/eval-session-execution.test.d.ts +1 -0
- package/dist/test/eval-session-execution.test.js +356 -0
- package/dist/test/eval-session-service.test.js +70 -1
- package/dist/test/host-worker-session.test.js +1 -0
- package/dist/test/launcher-session.test.d.ts +1 -0
- package/dist/test/launcher-session.test.js +87 -0
- package/dist/test/leaderboard.test.js +25 -1
- package/dist/test/provider-modules.test.d.ts +1 -0
- package/dist/test/provider-modules.test.js +65 -0
- package/dist/test/routekit-upstream-sources.test.d.ts +1 -0
- package/dist/test/routekit-upstream-sources.test.js +40 -0
- package/dist/test/runtime-composition-types.test.js +11 -0
- package/dist/token-application-service.d.ts +1 -3
- package/dist/token-application-service.js +38 -44
- package/dist/worker.js +5 -3
- package/package.json +35 -11
- package/dist/application-services.d.ts +0 -10
- package/dist/application-services.js +0 -22
- package/dist/daemon-lifecycle.d.ts +0 -54
- package/dist/daemon-lifecycle.js +0 -177
- package/dist/test/application-services.test.js +0 -135
- package/dist/test/daemon-lifecycle.test.js +0 -182
- /package/dist/{test/application-services.test.d.ts → internal-providers/provider-anthropic/test/provider.test.d.ts} +0 -0
- /package/dist/{test/daemon-lifecycle.test.d.ts → internal-providers/provider-azure/test/provider.test.d.ts} +0 -0
|
@@ -1,36 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
1
|
+
import { accountEnrollHandlers } from "./account-enroll-service.js";
|
|
2
|
+
import { accountMutationHandlers } from "./account-mutation-service.js";
|
|
3
|
+
import { accountQueryHandlers } from "./account-query-service.js";
|
|
4
|
+
import { daemonLifecycleHandlers } from "./daemon-lifecycle-service.js";
|
|
5
|
+
import { doctorHandlers } from "./doctor-service.js";
|
|
6
|
+
import { evalRoutingHandlers } from "./eval-routing-service.js";
|
|
7
|
+
import { evalSessionHandlers } from "./services/eval-session/service.js";
|
|
8
|
+
import { launcherHandlers } from "./launcher-service.js";
|
|
9
|
+
import { providerHandlers } from "./provider-query-service.js";
|
|
10
|
+
import { routerGenerationHandlers } from "./router-generation-service.js";
|
|
11
|
+
import { telemetryHandlers } from "./telemetry-application-service.js";
|
|
12
|
+
import { tokenHandlers } from "./token-application-service.js";
|
|
13
13
|
/**
|
|
14
|
-
* Composes owned
|
|
14
|
+
* Composes owned handler groups into the daemon control handler map.
|
|
15
15
|
* Protocol policy lives in the method table; this function only binds use cases.
|
|
16
16
|
* Daemon-lifetime state is yielded from `daemonLive`.
|
|
17
17
|
*/
|
|
18
18
|
export function createDaemonControlHandlers() {
|
|
19
|
-
const providerHandlers = new ProviderQueryService().handlers();
|
|
20
19
|
return {
|
|
21
|
-
...
|
|
22
|
-
...
|
|
20
|
+
...daemonLifecycleHandlers,
|
|
21
|
+
...routerGenerationHandlers,
|
|
23
22
|
...providerHandlers,
|
|
24
|
-
...
|
|
25
|
-
...
|
|
26
|
-
...
|
|
27
|
-
...
|
|
28
|
-
...
|
|
29
|
-
...
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
...
|
|
33
|
-
...new EvalRoutingApplicationService().handlers(),
|
|
34
|
-
...new EvalSessionApplicationService().handlers()
|
|
23
|
+
...accountQueryHandlers,
|
|
24
|
+
...accountEnrollHandlers,
|
|
25
|
+
...accountMutationHandlers,
|
|
26
|
+
...telemetryHandlers,
|
|
27
|
+
...doctorHandlers,
|
|
28
|
+
...launcherHandlers({ listModels: providerHandlers["models.list"] }),
|
|
29
|
+
...tokenHandlers,
|
|
30
|
+
...evalRoutingHandlers,
|
|
31
|
+
...evalSessionHandlers
|
|
35
32
|
};
|
|
36
33
|
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import type { AccountActivityService, AccountAuthService } from "@velum-labs/routekit-accounts/effect";
|
|
2
1
|
import { type RouterConfig } from "@velum-labs/routekit-config";
|
|
3
|
-
import
|
|
2
|
+
import { AccountActivity, AccountAuth } from "@velum-labs/routekit-accounts/effect";
|
|
4
3
|
import { type RouteKitPlatform } from "@velum-labs/routekit-runtime/effect";
|
|
5
4
|
import { Effect } from "effect";
|
|
6
|
-
import
|
|
7
|
-
import
|
|
5
|
+
import { DaemonEnv } from "./daemon-env-context.js";
|
|
6
|
+
import { DaemonState } from "./daemon-state-context.js";
|
|
7
|
+
import { Leaderboard } from "./leaderboard-context.js";
|
|
8
|
+
import { ActiveGateway } from "./services/active-gateway/service.js";
|
|
9
|
+
import { CallAttributions } from "./services/call-attributions/service.js";
|
|
10
|
+
import { EvalSessions } from "./services/eval-session/service.js";
|
|
8
11
|
import { type RunningGatewayGeneration } from "./services/gateway-generation/service.js";
|
|
12
|
+
import { Telemetry } from "./services/telemetry/service.js";
|
|
13
|
+
import { Sidecar } from "./sidecar-context.js";
|
|
9
14
|
export type DaemonGenerationStage = "prepare" | "validate" | "persist" | "commit" | "retire";
|
|
10
15
|
export type DaemonGenerationMutation = {
|
|
11
16
|
write: boolean;
|
|
@@ -14,34 +19,15 @@ export type DaemonGenerationMutation = {
|
|
|
14
19
|
/** Complete all fallible domain writes before the live target is published. */
|
|
15
20
|
persist?: () => Effect.Effect<void, Error, RouteKitPlatform>;
|
|
16
21
|
};
|
|
17
|
-
export type
|
|
18
|
-
configPath: string;
|
|
19
|
-
home: string;
|
|
22
|
+
export type DaemonGenerationSettings = {
|
|
20
23
|
drainGraceMs: number;
|
|
21
|
-
sidecar: CliproxySidecar;
|
|
22
|
-
routerEnv: () => NodeJS.ProcessEnv;
|
|
23
|
-
provenance: ProvenanceSink;
|
|
24
|
-
/** Published model-by-dimension evidence used by automatic routing. */
|
|
25
|
-
compositionalPolicyReader?: CompositionalRoutingPolicyReader;
|
|
26
|
-
activity: AccountActivityService;
|
|
27
|
-
authHealth: AccountAuthService;
|
|
28
|
-
wantsSidecar(config: RouterConfig): boolean;
|
|
29
|
-
getCurrentConfig(): RouterConfig;
|
|
30
|
-
setCurrentConfig(config: RouterConfig): void;
|
|
31
|
-
getCurrentDocument(): string;
|
|
32
|
-
setCurrentDocument(document: string): void;
|
|
33
|
-
getRevisions(): RevisionState;
|
|
34
|
-
setRevisions(revisions: RevisionState): void;
|
|
35
|
-
getActiveRouter(): RunningGatewayGeneration | undefined;
|
|
36
|
-
setActiveRouter(router: RunningGatewayGeneration): Effect.Effect<void>;
|
|
37
|
-
getProxy(): SwitchingGatewayProxy | undefined;
|
|
38
|
-
activeCredentialFingerprints(): Map<string, string>;
|
|
39
|
-
/** Apply daemon-local configuration before the proxy publishes the candidate. */
|
|
40
|
-
applyConfig(config: RouterConfig): void;
|
|
41
24
|
onStage?: (stage: DaemonGenerationStage) => void;
|
|
42
25
|
};
|
|
43
26
|
export type DaemonGenerationManager = {
|
|
44
27
|
start(config: RouterConfig): Effect.Effect<RunningGatewayGeneration, Error, RouteKitPlatform>;
|
|
45
28
|
replace(nextConfig: RouterConfig, nextDocument: string, mutation: DaemonGenerationMutation): Effect.Effect<void, Error, RouteKitPlatform>;
|
|
46
29
|
};
|
|
47
|
-
export declare
|
|
30
|
+
export declare const createDaemonGenerationManager: (settings: DaemonGenerationSettings) => Effect.Effect<{
|
|
31
|
+
start: (config: RouterConfig) => Effect.Effect<RunningGatewayGeneration, Error, RouteKitPlatform>;
|
|
32
|
+
replace: (nextConfig: RouterConfig, nextDocument: string, mutation: DaemonGenerationMutation) => Effect.Effect<void, Error, RouteKitPlatform>;
|
|
33
|
+
}, never, DaemonEnv | DaemonState | Leaderboard | ActiveGateway | CallAttributions | EvalSessions | Telemetry | Sidecar | AccountActivity | AccountAuth>;
|
|
@@ -1,32 +1,85 @@
|
|
|
1
1
|
import { chmodSync, readFileSync } from "node:fs";
|
|
2
|
+
import { CLIPROXY_API_KEY_ENV, CLIPROXY_BASE_URL_ENV, cliproxyApiKey, cliproxyBaseUrl } from "@velum-labs/routekit-accounts";
|
|
2
3
|
import { writeRouterConfig } from "@velum-labs/routekit-config";
|
|
4
|
+
import { AccountActivity, AccountAuth } from "@velum-labs/routekit-accounts/effect";
|
|
3
5
|
import { RouteKitFailure, toRouteKitFailure } from "@velum-labs/routekit-runtime/effect";
|
|
4
6
|
import { writeFileAtomic } from "@velum-labs/routekit-runtime/filesystem";
|
|
5
|
-
import { Effect } from "effect";
|
|
7
|
+
import { Effect, Ref } from "effect";
|
|
8
|
+
import { callInspection } from "./call-attribution-store.js";
|
|
6
9
|
import { writeDaemonRevisions } from "./daemon-state.js";
|
|
10
|
+
import { DaemonEnv } from "./daemon-env-context.js";
|
|
11
|
+
import { wantsCliproxySidecar } from "./daemon-policy-context.js";
|
|
12
|
+
import { DaemonState } from "./daemon-state-context.js";
|
|
13
|
+
import { makeCompositionalRoutingPolicyReader } from "./eval-routing-policy.js";
|
|
14
|
+
import { Leaderboard } from "./leaderboard-context.js";
|
|
15
|
+
import { ActiveGateway } from "./services/active-gateway/service.js";
|
|
16
|
+
import { CallAttributions } from "./services/call-attributions/service.js";
|
|
17
|
+
import { EvalSessions } from "./services/eval-session/service.js";
|
|
7
18
|
import { startGatewayGenerationEffect } from "./services/gateway-generation/service.js";
|
|
19
|
+
import { Telemetry } from "./services/telemetry/service.js";
|
|
20
|
+
import { Sidecar } from "./sidecar-context.js";
|
|
8
21
|
const tryPromise = (run) => Effect.tryPromise({
|
|
9
22
|
try: async () => await run(),
|
|
10
23
|
catch: toRouteKitFailure
|
|
11
24
|
});
|
|
12
25
|
const collectRollback = (work) => work.pipe(Effect.as(undefined), Effect.catch((error) => Effect.succeed(error)));
|
|
13
|
-
|
|
26
|
+
function routerEnv(env) {
|
|
27
|
+
const injected = { ...env };
|
|
28
|
+
if ((env[CLIPROXY_API_KEY_ENV] ?? "").length === 0) {
|
|
29
|
+
const key = cliproxyApiKey(env);
|
|
30
|
+
if (key !== undefined)
|
|
31
|
+
injected[CLIPROXY_API_KEY_ENV] = key;
|
|
32
|
+
}
|
|
33
|
+
if ((env[CLIPROXY_BASE_URL_ENV] ?? "").length === 0) {
|
|
34
|
+
injected[CLIPROXY_BASE_URL_ENV] = cliproxyBaseUrl(env);
|
|
35
|
+
}
|
|
36
|
+
return injected;
|
|
37
|
+
}
|
|
38
|
+
export const createDaemonGenerationManager = Effect.fn("DaemonGenerations.construct")(function* (settings) {
|
|
39
|
+
const env = yield* DaemonEnv;
|
|
40
|
+
const state = yield* DaemonState;
|
|
41
|
+
const sidecar = yield* Sidecar;
|
|
42
|
+
const activity = yield* AccountActivity;
|
|
43
|
+
const auth = yield* AccountAuth;
|
|
44
|
+
const gateway = yield* ActiveGateway;
|
|
45
|
+
const callAttributions = yield* CallAttributions;
|
|
46
|
+
const leaderboard = yield* Leaderboard;
|
|
47
|
+
const telemetry = yield* Telemetry;
|
|
48
|
+
const evalSessions = yield* EvalSessions;
|
|
49
|
+
const applyConfig = (config) => {
|
|
50
|
+
leaderboard.applyConfig(config);
|
|
51
|
+
const current = leaderboard.config();
|
|
52
|
+
callAttributions.configureBudget({
|
|
53
|
+
limit: current.liveLimit,
|
|
54
|
+
ttlMs: current.liveTtlHours * 60 * 60 * 1_000
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
const setActiveRouter = (router) => Ref.update(gateway.state, (current) => ({ ...current, router }));
|
|
58
|
+
const provenance = {
|
|
59
|
+
onModelCall(record) {
|
|
60
|
+
callAttributions.onModelCall(record);
|
|
61
|
+
const inspection = callInspection(record);
|
|
62
|
+
if (inspection !== undefined)
|
|
63
|
+
leaderboard.rollups.record(inspection);
|
|
64
|
+
telemetry.gateway.record(record);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const compositionalPolicyReader = makeCompositionalRoutingPolicyReader(env.home);
|
|
14
68
|
const start = (config) => startGatewayGenerationEffect({
|
|
15
69
|
config,
|
|
16
70
|
host: "127.0.0.1",
|
|
17
71
|
port: 0,
|
|
18
|
-
env:
|
|
19
|
-
provenance
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
drainGraceMs: options.drainGraceMs
|
|
72
|
+
env: routerEnv(env.env),
|
|
73
|
+
provenance,
|
|
74
|
+
resolveHttpSubmission: (binding) => evalSessions.resolveSubmission(binding),
|
|
75
|
+
compositionalPolicyReader,
|
|
76
|
+
activity,
|
|
77
|
+
authHealth: auth,
|
|
78
|
+
drainGraceMs: settings.drainGraceMs
|
|
26
79
|
});
|
|
27
80
|
const replace = (nextConfig, nextDocument, mutation) => Effect.scoped(Effect.gen(function* () {
|
|
28
|
-
const proxy =
|
|
29
|
-
const previousRouter =
|
|
81
|
+
const proxy = gateway.proxy();
|
|
82
|
+
const previousRouter = gateway.router();
|
|
30
83
|
if (proxy === undefined || previousRouter === undefined) {
|
|
31
84
|
return yield* new RouteKitFailure({
|
|
32
85
|
message: "router generation cannot replace before daemon publication"
|
|
@@ -41,10 +94,10 @@ export function createDaemonGenerationManager(options) {
|
|
|
41
94
|
: Effect.void;
|
|
42
95
|
});
|
|
43
96
|
yield* Effect.gen(function* () {
|
|
44
|
-
yield* tryPromise(() =>
|
|
45
|
-
yield*
|
|
97
|
+
yield* tryPromise(() => settings.onStage?.("prepare"));
|
|
98
|
+
yield* sidecar.reconcile(wantsCliproxySidecar(nextConfig));
|
|
46
99
|
candidate = yield* start(nextConfig);
|
|
47
|
-
yield* tryPromise(() =>
|
|
100
|
+
yield* tryPromise(() => settings.onStage?.("validate"));
|
|
48
101
|
}).pipe(Effect.catch((error) => Effect.gen(function* () {
|
|
49
102
|
const rollbackFailures = [];
|
|
50
103
|
if (candidate !== undefined) {
|
|
@@ -52,7 +105,7 @@ export function createDaemonGenerationManager(options) {
|
|
|
52
105
|
if (failure !== undefined)
|
|
53
106
|
rollbackFailures.push(failure);
|
|
54
107
|
}
|
|
55
|
-
const sidecarFailure = yield* collectRollback(
|
|
108
|
+
const sidecarFailure = yield* collectRollback(sidecar.reconcile(wantsCliproxySidecar(state.config)));
|
|
56
109
|
if (sidecarFailure !== undefined)
|
|
57
110
|
rollbackFailures.push(sidecarFailure);
|
|
58
111
|
if (rollbackFailures.length > 0) {
|
|
@@ -60,9 +113,9 @@ export function createDaemonGenerationManager(options) {
|
|
|
60
113
|
}
|
|
61
114
|
return yield* Effect.fail(error);
|
|
62
115
|
})));
|
|
63
|
-
const previousDocument =
|
|
64
|
-
const previousConfig =
|
|
65
|
-
const previousRevisions = { ...
|
|
116
|
+
const previousDocument = state.document;
|
|
117
|
+
const previousConfig = state.config;
|
|
118
|
+
const previousRevisions = { ...state.revisions };
|
|
66
119
|
const nextRevisions = { ...previousRevisions };
|
|
67
120
|
if (mutation.configRevision === true)
|
|
68
121
|
nextRevisions.config += 1;
|
|
@@ -71,32 +124,32 @@ export function createDaemonGenerationManager(options) {
|
|
|
71
124
|
let committedDocument = nextDocument;
|
|
72
125
|
yield* Effect.gen(function* () {
|
|
73
126
|
yield* tryPromise(() => {
|
|
74
|
-
|
|
127
|
+
settings.onStage?.("persist");
|
|
75
128
|
if (mutation.write)
|
|
76
|
-
writeRouterConfig(
|
|
77
|
-
writeDaemonRevisions(
|
|
129
|
+
writeRouterConfig(env.configPath, nextConfig);
|
|
130
|
+
writeDaemonRevisions(env.home, nextRevisions);
|
|
78
131
|
});
|
|
79
132
|
if (mutation.persist !== undefined)
|
|
80
133
|
yield* mutation.persist();
|
|
81
134
|
committedDocument = mutation.write
|
|
82
|
-
? readFileSync(
|
|
135
|
+
? readFileSync(env.configPath, "utf8")
|
|
83
136
|
: nextDocument;
|
|
84
137
|
}).pipe(Effect.catch((error) => Effect.gen(function* () {
|
|
85
138
|
const rollbackFailures = [];
|
|
86
139
|
const persistFailure = yield* collectRollback(tryPromise(() => {
|
|
87
140
|
if (mutation.write) {
|
|
88
|
-
writeFileAtomic(
|
|
89
|
-
chmodSync(
|
|
141
|
+
writeFileAtomic(env.configPath, previousDocument, { mode: 0o600 });
|
|
142
|
+
chmodSync(env.configPath, 0o600);
|
|
90
143
|
}
|
|
91
|
-
|
|
92
|
-
writeDaemonRevisions(
|
|
144
|
+
state.revisions = previousRevisions;
|
|
145
|
+
writeDaemonRevisions(env.home, previousRevisions);
|
|
93
146
|
}));
|
|
94
147
|
if (persistFailure !== undefined)
|
|
95
148
|
rollbackFailures.push(persistFailure);
|
|
96
149
|
const closeFailure = yield* collectRollback(candidate.close);
|
|
97
150
|
if (closeFailure !== undefined)
|
|
98
151
|
rollbackFailures.push(closeFailure);
|
|
99
|
-
const sidecarFailure = yield* collectRollback(
|
|
152
|
+
const sidecarFailure = yield* collectRollback(sidecar.reconcile(wantsCliproxySidecar(state.config)));
|
|
100
153
|
if (sidecarFailure !== undefined)
|
|
101
154
|
rollbackFailures.push(sidecarFailure);
|
|
102
155
|
if (rollbackFailures.length > 0) {
|
|
@@ -104,43 +157,50 @@ export function createDaemonGenerationManager(options) {
|
|
|
104
157
|
}
|
|
105
158
|
return yield* Effect.fail(error);
|
|
106
159
|
})));
|
|
107
|
-
|
|
108
|
-
//
|
|
109
|
-
//
|
|
160
|
+
let previousTarget;
|
|
161
|
+
// Prepare every daemon-local view before publishing the new target. The
|
|
162
|
+
// final synchronous section switches auth identity and proxy target as one
|
|
163
|
+
// publication handoff. It retains retired fingerprint evidence until after
|
|
164
|
+
// publication, so any rollback restores the exact prior runtime state.
|
|
110
165
|
yield* Effect.gen(function* () {
|
|
111
|
-
yield*
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
yield*
|
|
117
|
-
yield*
|
|
166
|
+
yield* setActiveRouter(candidate);
|
|
167
|
+
state.config = nextConfig;
|
|
168
|
+
state.document = committedDocument;
|
|
169
|
+
state.revisions = nextRevisions;
|
|
170
|
+
applyConfig(nextConfig);
|
|
171
|
+
yield* tryPromise(() => settings.onStage?.("commit"));
|
|
172
|
+
yield* Effect.sync(() => {
|
|
173
|
+
auth.switchActiveCredentials(candidate.activeCredentialFingerprints());
|
|
174
|
+
previousTarget = proxy.swapTarget(candidate.url);
|
|
175
|
+
published = true;
|
|
176
|
+
});
|
|
118
177
|
}).pipe(Effect.catch((error) => Effect.gen(function* () {
|
|
119
178
|
const rollbackFailures = [];
|
|
120
179
|
const viewFailure = yield* collectRollback(Effect.gen(function* () {
|
|
121
|
-
yield*
|
|
180
|
+
yield* setActiveRouter(previousRouter);
|
|
122
181
|
yield* tryPromise(() => {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
182
|
+
state.config = previousConfig;
|
|
183
|
+
state.document = previousDocument;
|
|
184
|
+
state.revisions = previousRevisions;
|
|
185
|
+
applyConfig(previousConfig);
|
|
127
186
|
});
|
|
187
|
+
yield* auth.reconcileActiveCredentials(previousRouter.activeCredentialFingerprints());
|
|
128
188
|
}));
|
|
129
189
|
if (viewFailure !== undefined)
|
|
130
190
|
rollbackFailures.push(viewFailure);
|
|
131
191
|
const persistFailure = yield* collectRollback(tryPromise(() => {
|
|
132
192
|
if (mutation.write) {
|
|
133
|
-
writeFileAtomic(
|
|
134
|
-
chmodSync(
|
|
193
|
+
writeFileAtomic(env.configPath, previousDocument, { mode: 0o600 });
|
|
194
|
+
chmodSync(env.configPath, 0o600);
|
|
135
195
|
}
|
|
136
|
-
writeDaemonRevisions(
|
|
196
|
+
writeDaemonRevisions(env.home, previousRevisions);
|
|
137
197
|
}));
|
|
138
198
|
if (persistFailure !== undefined)
|
|
139
199
|
rollbackFailures.push(persistFailure);
|
|
140
200
|
const closeFailure = yield* collectRollback(candidate.close);
|
|
141
201
|
if (closeFailure !== undefined)
|
|
142
202
|
rollbackFailures.push(closeFailure);
|
|
143
|
-
const sidecarFailure = yield* collectRollback(
|
|
203
|
+
const sidecarFailure = yield* collectRollback(sidecar.reconcile(wantsCliproxySidecar(previousConfig)));
|
|
144
204
|
if (sidecarFailure !== undefined)
|
|
145
205
|
rollbackFailures.push(sidecarFailure);
|
|
146
206
|
if (rollbackFailures.length > 0) {
|
|
@@ -148,18 +208,19 @@ export function createDaemonGenerationManager(options) {
|
|
|
148
208
|
}
|
|
149
209
|
return yield* Effect.fail(error);
|
|
150
210
|
})));
|
|
151
|
-
const previousTarget = proxy.swapTarget(candidate.url);
|
|
152
|
-
published = true;
|
|
153
211
|
const retirementFailures = [];
|
|
154
|
-
const
|
|
212
|
+
const authFailure = yield* collectRollback(auth.reconcileActiveCredentials(candidate.activeCredentialFingerprints()));
|
|
213
|
+
if (authFailure !== undefined)
|
|
214
|
+
retirementFailures.push(authFailure);
|
|
215
|
+
const retireFailure = yield* collectRollback(tryPromise(() => settings.onStage?.("retire")));
|
|
155
216
|
if (retireFailure !== undefined)
|
|
156
217
|
retirementFailures.push(retireFailure);
|
|
157
218
|
if (previousTarget !== undefined) {
|
|
158
|
-
const idleFailure = yield* collectRollback(proxy.waitForTargetIdle(previousTarget,
|
|
219
|
+
const idleFailure = yield* collectRollback(proxy.waitForTargetIdle(previousTarget, settings.drainGraceMs));
|
|
159
220
|
if (idleFailure !== undefined)
|
|
160
221
|
retirementFailures.push(idleFailure);
|
|
161
222
|
}
|
|
162
|
-
const drainFailure = yield* collectRollback(previousRouter.gateway.drain(
|
|
223
|
+
const drainFailure = yield* collectRollback(previousRouter.gateway.drain(settings.drainGraceMs));
|
|
163
224
|
if (drainFailure !== undefined)
|
|
164
225
|
retirementFailures.push(drainFailure);
|
|
165
226
|
const previousCloseFailure = yield* collectRollback(previousRouter.close);
|
|
@@ -173,4 +234,4 @@ export function createDaemonGenerationManager(options) {
|
|
|
173
234
|
}
|
|
174
235
|
}));
|
|
175
236
|
return { start, replace };
|
|
176
|
-
}
|
|
237
|
+
});
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { EffectRouteKitControlHandlers } from "@velum-labs/routekit-control/effect";
|
|
2
2
|
type LifecycleHandlers = Pick<EffectRouteKitControlHandlers, "daemon.status" | "daemon.roll" | "daemon.prepareShutdown">;
|
|
3
3
|
/** Owns daemon status, rolling replacement, and shutdown preparation. */
|
|
4
|
-
export declare
|
|
5
|
-
handlers(): LifecycleHandlers;
|
|
6
|
-
}
|
|
4
|
+
export declare const daemonLifecycleHandlers: LifecycleHandlers;
|
|
7
5
|
export {};
|
|
@@ -9,99 +9,93 @@ import { DaemonHost } from "./daemon-host-context.js";
|
|
|
9
9
|
import { DaemonState } from "./daemon-state-context.js";
|
|
10
10
|
import { Telemetry } from "./services/telemetry/service.js";
|
|
11
11
|
/** Owns daemon status, rolling replacement, and shutdown preparation. */
|
|
12
|
-
export
|
|
13
|
-
|
|
12
|
+
export const daemonLifecycleHandlers = {
|
|
13
|
+
"daemon.status": () => Effect.gen(function* () {
|
|
14
|
+
const env = yield* DaemonEnv;
|
|
15
|
+
const state = yield* DaemonState;
|
|
16
|
+
const gateway = yield* ActiveGateway;
|
|
14
17
|
return {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
dataUrl: env.hosted?.dataUrl() ?? gateway.dataUrl() ?? "",
|
|
33
|
-
dataPort: gateway.proxy()?.port() ?? 0,
|
|
34
|
-
supervisor: supervisorFromEnv(env.env),
|
|
35
|
-
draining: state.draining,
|
|
36
|
-
rolling: env.hosted?.rolling() ?? false
|
|
37
|
-
};
|
|
38
|
-
}),
|
|
39
|
-
"daemon.roll": (params) => Effect.gen(function* () {
|
|
40
|
-
const env = yield* DaemonEnv;
|
|
41
|
-
const telemetry = yield* Telemetry;
|
|
42
|
-
const host = yield* DaemonHost;
|
|
43
|
-
if (host.onRollRequested === undefined) {
|
|
44
|
-
return yield* Effect.fail(new ControlError({
|
|
45
|
-
code: "upgrade_required",
|
|
46
|
-
message: "this daemon does not support rolling process replacement"
|
|
47
|
-
}));
|
|
48
|
-
}
|
|
49
|
-
const startedAt = yield* Clock.currentTimeMillis;
|
|
50
|
-
const supervisor = ["systemd", "launchd", "detached"].includes(supervisorFromEnv(env.env))
|
|
51
|
-
? supervisorFromEnv(env.env)
|
|
52
|
-
: "unknown";
|
|
53
|
-
const toVersion = params.candidate?.expectedVersion ?? env.packageVersion;
|
|
54
|
-
telemetry.daemon?.capture("routekit.daemon_lifecycle", {
|
|
55
|
-
action: "roll_started",
|
|
56
|
-
outcome: "success",
|
|
57
|
-
supervisor,
|
|
58
|
-
version: env.packageVersion,
|
|
59
|
-
reason: params.reason,
|
|
60
|
-
from_version: env.packageVersion,
|
|
61
|
-
to_version: toVersion
|
|
62
|
-
});
|
|
63
|
-
return yield* host.onRollRequested(params).pipe(Effect.tap((result) => Effect.gen(function* () {
|
|
64
|
-
const finishedAt = yield* Clock.currentTimeMillis;
|
|
65
|
-
telemetry.daemon?.capture("routekit.daemon_lifecycle", {
|
|
66
|
-
action: "roll_committed",
|
|
67
|
-
outcome: "success",
|
|
68
|
-
supervisor,
|
|
69
|
-
version: result.packageVersion,
|
|
70
|
-
reason: params.reason,
|
|
71
|
-
from_version: env.packageVersion,
|
|
72
|
-
to_version: result.packageVersion,
|
|
73
|
-
duration_bucket: durationBucket(finishedAt - startedAt)
|
|
74
|
-
});
|
|
75
|
-
})), Effect.tapError(() => Effect.gen(function* () {
|
|
76
|
-
const finishedAt = yield* Clock.currentTimeMillis;
|
|
77
|
-
telemetry.daemon?.capture("routekit.daemon_lifecycle", {
|
|
78
|
-
action: "roll_failed",
|
|
79
|
-
outcome: "error",
|
|
80
|
-
supervisor,
|
|
81
|
-
version: env.packageVersion,
|
|
82
|
-
reason: params.reason,
|
|
83
|
-
from_version: env.packageVersion,
|
|
84
|
-
to_version: toVersion,
|
|
85
|
-
rollback_stage: "candidate",
|
|
86
|
-
duration_bucket: durationBucket(finishedAt - startedAt)
|
|
87
|
-
});
|
|
88
|
-
})));
|
|
89
|
-
}),
|
|
90
|
-
"daemon.prepareShutdown": (params) => Effect.gen(function* () {
|
|
91
|
-
const state = yield* DaemonState;
|
|
92
|
-
if (state.lifecycle === "quiescing" ||
|
|
93
|
-
state.lifecycle === "draining" ||
|
|
94
|
-
state.lifecycle === "closed") {
|
|
95
|
-
return { accepted: true };
|
|
96
|
-
}
|
|
97
|
-
const host = yield* DaemonHost;
|
|
98
|
-
state.beginRetire();
|
|
99
|
-
yield* state.awaitMutations;
|
|
100
|
-
if (host.onShutdownRequested !== undefined) {
|
|
101
|
-
yield* host.onShutdownRequested(params.reason).pipe(Effect.forkDetach);
|
|
102
|
-
}
|
|
103
|
-
return { accepted: true };
|
|
104
|
-
})
|
|
18
|
+
pid: process.pid,
|
|
19
|
+
workerPid: process.pid,
|
|
20
|
+
hostPid: env.hosted?.hostPid ?? process.pid,
|
|
21
|
+
hostStartedAt: env.hosted?.hostStartedAt ?? env.startedAt,
|
|
22
|
+
startedAt: env.startedAt,
|
|
23
|
+
packageVersion: env.packageVersion,
|
|
24
|
+
protocolVersion: CONTROL_PROTOCOL_VERSION,
|
|
25
|
+
hostProtocolVersion: env.hosted === undefined ? 0 : DAEMON_HOST_PROTOCOL_VERSION,
|
|
26
|
+
generation: env.generation,
|
|
27
|
+
configRevision: state.revisions.config,
|
|
28
|
+
accountRevision: state.revisions.accounts,
|
|
29
|
+
controlUrl: gateway.control()?.url ?? "",
|
|
30
|
+
dataUrl: env.hosted?.dataUrl() ?? gateway.dataUrl() ?? "",
|
|
31
|
+
dataPort: gateway.proxy()?.port() ?? 0,
|
|
32
|
+
supervisor: supervisorFromEnv(env.env),
|
|
33
|
+
draining: state.draining,
|
|
34
|
+
rolling: env.hosted?.rolling() ?? false
|
|
105
35
|
};
|
|
106
|
-
}
|
|
107
|
-
|
|
36
|
+
}),
|
|
37
|
+
"daemon.roll": (params) => Effect.gen(function* () {
|
|
38
|
+
const env = yield* DaemonEnv;
|
|
39
|
+
const telemetry = yield* Telemetry;
|
|
40
|
+
const host = yield* DaemonHost;
|
|
41
|
+
if (host.onRollRequested === undefined) {
|
|
42
|
+
return yield* Effect.fail(new ControlError({
|
|
43
|
+
code: "upgrade_required",
|
|
44
|
+
message: "this daemon does not support rolling process replacement"
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
const startedAt = yield* Clock.currentTimeMillis;
|
|
48
|
+
const supervisor = supervisorFromEnv(env.env);
|
|
49
|
+
const toVersion = params.candidate?.expectedVersion ?? env.packageVersion;
|
|
50
|
+
telemetry.daemon?.capture("routekit.daemon_lifecycle", {
|
|
51
|
+
action: "roll_started",
|
|
52
|
+
outcome: "success",
|
|
53
|
+
supervisor,
|
|
54
|
+
version: env.packageVersion,
|
|
55
|
+
reason: params.reason,
|
|
56
|
+
from_version: env.packageVersion,
|
|
57
|
+
to_version: toVersion
|
|
58
|
+
});
|
|
59
|
+
return yield* host.onRollRequested(params).pipe(Effect.tap((result) => Effect.gen(function* () {
|
|
60
|
+
const finishedAt = yield* Clock.currentTimeMillis;
|
|
61
|
+
telemetry.daemon?.capture("routekit.daemon_lifecycle", {
|
|
62
|
+
action: "roll_committed",
|
|
63
|
+
outcome: "success",
|
|
64
|
+
supervisor,
|
|
65
|
+
version: result.packageVersion,
|
|
66
|
+
reason: params.reason,
|
|
67
|
+
from_version: env.packageVersion,
|
|
68
|
+
to_version: result.packageVersion,
|
|
69
|
+
duration_bucket: durationBucket(finishedAt - startedAt)
|
|
70
|
+
});
|
|
71
|
+
})), Effect.tapError(() => Effect.gen(function* () {
|
|
72
|
+
const finishedAt = yield* Clock.currentTimeMillis;
|
|
73
|
+
telemetry.daemon?.capture("routekit.daemon_lifecycle", {
|
|
74
|
+
action: "roll_failed",
|
|
75
|
+
outcome: "error",
|
|
76
|
+
supervisor,
|
|
77
|
+
version: env.packageVersion,
|
|
78
|
+
reason: params.reason,
|
|
79
|
+
from_version: env.packageVersion,
|
|
80
|
+
to_version: toVersion,
|
|
81
|
+
rollback_stage: "candidate",
|
|
82
|
+
duration_bucket: durationBucket(finishedAt - startedAt)
|
|
83
|
+
});
|
|
84
|
+
})));
|
|
85
|
+
}),
|
|
86
|
+
"daemon.prepareShutdown": (params) => Effect.gen(function* () {
|
|
87
|
+
const state = yield* DaemonState;
|
|
88
|
+
if (state.lifecycle === "quiescing" ||
|
|
89
|
+
state.lifecycle === "draining" ||
|
|
90
|
+
state.lifecycle === "closed") {
|
|
91
|
+
return { accepted: true };
|
|
92
|
+
}
|
|
93
|
+
const host = yield* DaemonHost;
|
|
94
|
+
state.beginRetire();
|
|
95
|
+
yield* state.awaitMutations;
|
|
96
|
+
if (host.onShutdownRequested !== undefined) {
|
|
97
|
+
yield* host.onShutdownRequested(params.reason).pipe(Effect.forkDetach);
|
|
98
|
+
}
|
|
99
|
+
return { accepted: true };
|
|
100
|
+
})
|
|
101
|
+
};
|