@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.
Files changed (172) hide show
  1. package/dist/account-enroll-service.d.ts +1 -3
  2. package/dist/account-enroll-service.js +262 -262
  3. package/dist/account-mutation-service.d.ts +1 -3
  4. package/dist/account-mutation-service.js +302 -283
  5. package/dist/account-query-service.d.ts +1 -3
  6. package/dist/account-query-service.js +88 -86
  7. package/dist/call-attribution-store.js +544 -1
  8. package/dist/control-dispatch.js +9 -15
  9. package/dist/daemon-bootstrap-preflight.js +1 -1
  10. package/dist/daemon-control-handlers.d.ts +1 -1
  11. package/dist/daemon-control-handlers.js +24 -27
  12. package/dist/daemon-generations.d.ts +14 -28
  13. package/dist/daemon-generations.js +114 -53
  14. package/dist/daemon-lifecycle-service.d.ts +1 -3
  15. package/dist/daemon-lifecycle-service.js +88 -94
  16. package/dist/daemon-maintenance.d.ts +27 -49
  17. package/dist/daemon-maintenance.js +5 -1
  18. package/dist/daemon-options.d.ts +3 -2
  19. package/dist/daemon-policy-context.d.ts +1 -0
  20. package/dist/daemon-policy-context.js +1 -0
  21. package/dist/daemon-runtime-state.d.ts +1 -1
  22. package/dist/daemon-runtime-state.js +2 -3
  23. package/dist/daemon-state-context.d.ts +1 -1
  24. package/dist/daemon-state-context.js +1 -38
  25. package/dist/doctor-service.d.ts +1 -3
  26. package/dist/doctor-service.js +87 -91
  27. package/dist/effect/daemon-live.d.ts +7 -6
  28. package/dist/effect/daemon-live.js +57 -135
  29. package/dist/eval-routing-service.d.ts +1 -3
  30. package/dist/eval-routing-service.js +45 -69
  31. package/dist/eval-session-execution-store.d.ts +24 -0
  32. package/dist/eval-session-execution-store.js +253 -0
  33. package/dist/host-protocol.d.ts +1 -0
  34. package/dist/host.js +3 -0
  35. package/dist/internal-providers/provider-anthropic/index.d.ts +23 -0
  36. package/dist/internal-providers/provider-anthropic/index.js +42 -0
  37. package/dist/internal-providers/provider-anthropic/test/provider.test.js +33 -0
  38. package/dist/internal-providers/provider-azure/index.d.ts +25 -0
  39. package/dist/internal-providers/provider-azure/index.js +101 -0
  40. package/dist/internal-providers/provider-azure/test/provider.test.js +83 -0
  41. package/dist/internal-providers/provider-bedrock/codec.d.ts +11 -0
  42. package/dist/internal-providers/provider-bedrock/codec.js +484 -0
  43. package/dist/internal-providers/provider-bedrock/connection-settings.d.ts +6 -0
  44. package/dist/internal-providers/provider-bedrock/connection-settings.js +41 -0
  45. package/dist/internal-providers/provider-bedrock/connection.d.ts +17 -0
  46. package/dist/internal-providers/provider-bedrock/connection.js +143 -0
  47. package/dist/internal-providers/provider-bedrock/index.d.ts +27 -0
  48. package/dist/internal-providers/provider-bedrock/index.js +47 -0
  49. package/dist/internal-providers/provider-bedrock/source.d.ts +39 -0
  50. package/dist/internal-providers/provider-bedrock/source.js +454 -0
  51. package/dist/internal-providers/provider-bedrock/test/bedrock-source.test.d.ts +1 -0
  52. package/dist/internal-providers/provider-bedrock/test/bedrock-source.test.js +786 -0
  53. package/dist/internal-providers/provider-bedrock/test/connection.test.d.ts +1 -0
  54. package/dist/internal-providers/provider-bedrock/test/connection.test.js +249 -0
  55. package/dist/internal-providers/provider-bedrock/test/workload-credentials.test.d.ts +1 -0
  56. package/dist/internal-providers/provider-bedrock/test/workload-credentials.test.js +29 -0
  57. package/dist/internal-providers/provider-bedrock/workload-credentials.d.ts +5 -0
  58. package/dist/internal-providers/provider-bedrock/workload-credentials.js +14 -0
  59. package/dist/internal-providers/provider-claude-code/index.d.ts +6 -0
  60. package/dist/internal-providers/provider-claude-code/index.js +4 -0
  61. package/dist/internal-providers/provider-claude-code/module-api.d.ts +25 -0
  62. package/dist/internal-providers/provider-claude-code/module-api.js +31 -0
  63. package/dist/internal-providers/provider-claude-code/relay.d.ts +20 -0
  64. package/dist/internal-providers/provider-claude-code/relay.js +115 -0
  65. package/dist/internal-providers/provider-claude-code/subscription.d.ts +2 -0
  66. package/dist/internal-providers/provider-claude-code/subscription.js +194 -0
  67. package/dist/internal-providers/provider-claude-code/test/http-submission.test.d.ts +1 -0
  68. package/dist/internal-providers/provider-claude-code/test/http-submission.test.js +140 -0
  69. package/dist/internal-providers/provider-claude-code/test/relay.test.d.ts +1 -0
  70. package/dist/internal-providers/provider-claude-code/test/relay.test.js +379 -0
  71. package/dist/internal-providers/provider-claude-code/test/subscription.test.d.ts +1 -0
  72. package/dist/internal-providers/provider-claude-code/test/subscription.test.js +256 -0
  73. package/dist/internal-providers/provider-cliproxy/index.d.ts +9 -0
  74. package/dist/internal-providers/provider-cliproxy/index.js +15 -0
  75. package/dist/internal-providers/provider-codex/index.d.ts +6 -0
  76. package/dist/internal-providers/provider-codex/index.js +7 -0
  77. package/dist/internal-providers/provider-codex/module-api.d.ts +25 -0
  78. package/dist/internal-providers/provider-codex/module-api.js +31 -0
  79. package/dist/internal-providers/provider-codex/relay.d.ts +123 -0
  80. package/dist/internal-providers/provider-codex/relay.js +261 -0
  81. package/dist/internal-providers/provider-codex/subscription.d.ts +2 -0
  82. package/dist/internal-providers/provider-codex/subscription.js +555 -0
  83. package/dist/internal-providers/provider-codex/test/http-submission.test.d.ts +1 -0
  84. package/dist/internal-providers/provider-codex/test/http-submission.test.js +142 -0
  85. package/dist/internal-providers/provider-codex/test/relay.test.d.ts +1 -0
  86. package/dist/internal-providers/provider-codex/test/relay.test.js +600 -0
  87. package/dist/internal-providers/provider-codex/test/subscription.test.d.ts +1 -0
  88. package/dist/internal-providers/provider-codex/test/subscription.test.js +70 -0
  89. package/dist/internal-providers/provider-codex/test/translated-content.test.d.ts +1 -0
  90. package/dist/internal-providers/provider-codex/test/translated-content.test.js +91 -0
  91. package/dist/internal-providers/provider-custom/index.d.ts +9 -0
  92. package/dist/internal-providers/provider-custom/index.js +15 -0
  93. package/dist/internal-providers/provider-google/index.d.ts +23 -0
  94. package/dist/internal-providers/provider-google/index.js +43 -0
  95. package/dist/internal-providers/provider-google/test/provider.test.d.ts +1 -0
  96. package/dist/internal-providers/provider-google/test/provider.test.js +34 -0
  97. package/dist/internal-providers/provider-google-vertex/index.d.ts +28 -0
  98. package/dist/internal-providers/provider-google-vertex/index.js +137 -0
  99. package/dist/internal-providers/provider-google-vertex/test/provider.test.d.ts +1 -0
  100. package/dist/internal-providers/provider-google-vertex/test/provider.test.js +85 -0
  101. package/dist/internal-providers/provider-openai/documented-reasoning.d.ts +9 -0
  102. package/dist/internal-providers/provider-openai/documented-reasoning.js +26 -0
  103. package/dist/internal-providers/provider-openai/index.d.ts +23 -0
  104. package/dist/internal-providers/provider-openai/index.js +74 -0
  105. package/dist/internal-providers/provider-openai/test/provider.test.d.ts +1 -0
  106. package/dist/internal-providers/provider-openai/test/provider.test.js +268 -0
  107. package/dist/internal-providers/provider-openai-compatible/index.d.ts +9 -0
  108. package/dist/internal-providers/provider-openai-compatible/index.js +15 -0
  109. package/dist/internal-providers/provider-openai-compatible/test/provider.test.d.ts +1 -0
  110. package/dist/internal-providers/provider-openai-compatible/test/provider.test.js +40 -0
  111. package/dist/internal-providers/provider-openrouter/connection.d.ts +5 -0
  112. package/dist/internal-providers/provider-openrouter/connection.js +143 -0
  113. package/dist/internal-providers/provider-openrouter/index.d.ts +28 -0
  114. package/dist/internal-providers/provider-openrouter/index.js +42 -0
  115. package/dist/internal-providers/provider-openrouter/pricing.d.ts +4 -0
  116. package/dist/internal-providers/provider-openrouter/pricing.js +108 -0
  117. package/dist/internal-providers/provider-openrouter/test/connection.test.d.ts +1 -0
  118. package/dist/internal-providers/provider-openrouter/test/connection.test.js +71 -0
  119. package/dist/internal-providers/provider-openrouter/test/pricing.test.d.ts +1 -0
  120. package/dist/internal-providers/provider-openrouter/test/pricing.test.js +45 -0
  121. package/dist/internal-providers/provider-openrouter/test/provider.test.d.ts +1 -0
  122. package/dist/internal-providers/provider-openrouter/test/provider.test.js +141 -0
  123. package/dist/internal-providers/provider-openrouter/test/validation.test.d.ts +1 -0
  124. package/dist/internal-providers/provider-openrouter/test/validation.test.js +225 -0
  125. package/dist/launcher-service.d.ts +3 -7
  126. package/dist/launcher-service.js +144 -133
  127. package/dist/provider-modules.d.ts +6 -0
  128. package/dist/provider-modules.js +55 -0
  129. package/dist/provider-query-service.d.ts +1 -3
  130. package/dist/provider-query-service.js +140 -143
  131. package/dist/router-generation-service.d.ts +1 -3
  132. package/dist/router-generation-service.js +83 -78
  133. package/dist/services/eval-session/service.d.ts +20 -8
  134. package/dist/services/eval-session/service.js +410 -40
  135. package/dist/services/gateway-generation/service.d.ts +7 -3
  136. package/dist/services/gateway-generation/service.js +126 -63
  137. package/dist/services/generations/service.d.ts +3 -3
  138. package/dist/services/generations/service.js +5 -1
  139. package/dist/telemetry-application-service.d.ts +1 -3
  140. package/dist/telemetry-application-service.js +41 -45
  141. package/dist/test/call-attribution-store.test.js +186 -0
  142. package/dist/test/daemon-accounts.test.js +2 -2
  143. package/dist/test/daemon-data-plane.test.js +24 -45
  144. package/dist/test/daemon-fixtures.js +1 -1
  145. package/dist/test/daemon-generation-transaction.test.js +66 -2
  146. package/dist/test/daemon-runtime-state.test.js +28 -1
  147. package/dist/test/effect-daemon.test.js +38 -0
  148. package/dist/test/eval-routing-application-service.test.js +6 -6
  149. package/dist/test/eval-session-execution.test.d.ts +1 -0
  150. package/dist/test/eval-session-execution.test.js +356 -0
  151. package/dist/test/eval-session-service.test.js +70 -1
  152. package/dist/test/host-worker-session.test.js +1 -0
  153. package/dist/test/launcher-session.test.d.ts +1 -0
  154. package/dist/test/launcher-session.test.js +87 -0
  155. package/dist/test/leaderboard.test.js +25 -1
  156. package/dist/test/provider-modules.test.d.ts +1 -0
  157. package/dist/test/provider-modules.test.js +65 -0
  158. package/dist/test/routekit-upstream-sources.test.d.ts +1 -0
  159. package/dist/test/routekit-upstream-sources.test.js +40 -0
  160. package/dist/test/runtime-composition-types.test.js +11 -0
  161. package/dist/token-application-service.d.ts +1 -3
  162. package/dist/token-application-service.js +38 -44
  163. package/dist/worker.js +5 -3
  164. package/package.json +35 -11
  165. package/dist/application-services.d.ts +0 -10
  166. package/dist/application-services.js +0 -22
  167. package/dist/daemon-lifecycle.d.ts +0 -54
  168. package/dist/daemon-lifecycle.js +0 -177
  169. package/dist/test/application-services.test.js +0 -135
  170. package/dist/test/daemon-lifecycle.test.js +0 -182
  171. /package/dist/{test/application-services.test.d.ts → internal-providers/provider-anthropic/test/provider.test.d.ts} +0 -0
  172. /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 { AccountEnrollService } from "./account-enroll-service.js";
2
- import { AccountMutationService } from "./account-mutation-service.js";
3
- import { AccountQueryService } from "./account-query-service.js";
4
- import { DaemonLifecycleService } from "./daemon-lifecycle-service.js";
5
- import { DoctorApplicationService } from "./doctor-service.js";
6
- import { EvalRoutingApplicationService } from "./eval-routing-service.js";
7
- import { EvalSessionApplicationService } from "./services/eval-session/service.js";
8
- import { LauncherApplicationService } from "./launcher-service.js";
9
- import { ProviderQueryService } from "./provider-query-service.js";
10
- import { RouterGenerationService } from "./router-generation-service.js";
11
- import { TelemetryApplicationService } from "./telemetry-application-service.js";
12
- import { TokenApplicationService } from "./token-application-service.js";
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 application services into the daemon control handler map.
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
- ...new DaemonLifecycleService().handlers(),
22
- ...new RouterGenerationService().handlers(),
20
+ ...daemonLifecycleHandlers,
21
+ ...routerGenerationHandlers,
23
22
  ...providerHandlers,
24
- ...new AccountQueryService().handlers(),
25
- ...new AccountEnrollService().handlers(),
26
- ...new AccountMutationService().handlers(),
27
- ...new TelemetryApplicationService().handlers(),
28
- ...new DoctorApplicationService().handlers(),
29
- ...new LauncherApplicationService({
30
- listModels: providerHandlers["models.list"]
31
- }).handlers(),
32
- ...new TokenApplicationService().handlers(),
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 type { CompositionalRoutingPolicyReader, ProvenanceSink, SwitchingGatewayProxy } from "@velum-labs/routekit-gateway";
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 type { CliproxySidecar } from "./cliproxy-sidecar.js";
7
- import type { RevisionState } from "./daemon-state.js";
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 DaemonGenerationManagerOptions = {
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 function createDaemonGenerationManager(options: DaemonGenerationManagerOptions): DaemonGenerationManager;
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
- export function createDaemonGenerationManager(options) {
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: options.routerEnv(),
19
- provenance: options.provenance,
20
- ...(options.compositionalPolicyReader !== undefined
21
- ? { compositionalPolicyReader: options.compositionalPolicyReader }
22
- : {}),
23
- activity: options.activity,
24
- authHealth: options.authHealth,
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 = options.getProxy();
29
- const previousRouter = options.getActiveRouter();
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(() => options.onStage?.("prepare"));
45
- yield* options.sidecar.reconcile(options.wantsSidecar(nextConfig));
97
+ yield* tryPromise(() => settings.onStage?.("prepare"));
98
+ yield* sidecar.reconcile(wantsCliproxySidecar(nextConfig));
46
99
  candidate = yield* start(nextConfig);
47
- yield* tryPromise(() => options.onStage?.("validate"));
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(options.sidecar.reconcile(options.wantsSidecar(options.getCurrentConfig())));
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 = options.getCurrentDocument();
64
- const previousConfig = options.getCurrentConfig();
65
- const previousRevisions = { ...options.getRevisions() };
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
- options.onStage?.("persist");
127
+ settings.onStage?.("persist");
75
128
  if (mutation.write)
76
- writeRouterConfig(options.configPath, nextConfig);
77
- writeDaemonRevisions(options.home, nextRevisions);
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(options.configPath, "utf8")
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(options.configPath, previousDocument, { mode: 0o600 });
89
- chmodSync(options.configPath, 0o600);
141
+ writeFileAtomic(env.configPath, previousDocument, { mode: 0o600 });
142
+ chmodSync(env.configPath, 0o600);
90
143
  }
91
- options.setRevisions(previousRevisions);
92
- writeDaemonRevisions(options.home, previousRevisions);
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(options.sidecar.reconcile(options.wantsSidecar(options.getCurrentConfig())));
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
- // Prepare every daemon-local view before publishing the new target. These
108
- // mutations are synchronous and are rolled back if the commit hook rejects.
109
- // swapTarget below is deliberately the final publication operation.
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* options.setActiveRouter(candidate);
112
- options.setCurrentConfig(nextConfig);
113
- options.setCurrentDocument(committedDocument);
114
- options.setRevisions(nextRevisions);
115
- options.applyConfig(nextConfig);
116
- yield* options.authHealth.reconcileActiveCredentials(options.activeCredentialFingerprints());
117
- yield* tryPromise(() => options.onStage?.("commit"));
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* options.setActiveRouter(previousRouter);
180
+ yield* setActiveRouter(previousRouter);
122
181
  yield* tryPromise(() => {
123
- options.setCurrentConfig(previousConfig);
124
- options.setCurrentDocument(previousDocument);
125
- options.setRevisions(previousRevisions);
126
- options.applyConfig(previousConfig);
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(options.configPath, previousDocument, { mode: 0o600 });
134
- chmodSync(options.configPath, 0o600);
193
+ writeFileAtomic(env.configPath, previousDocument, { mode: 0o600 });
194
+ chmodSync(env.configPath, 0o600);
135
195
  }
136
- writeDaemonRevisions(options.home, previousRevisions);
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(options.sidecar.reconcile(options.wantsSidecar(previousConfig)));
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 retireFailure = yield* collectRollback(tryPromise(() => options.onStage?.("retire")));
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, options.drainGraceMs));
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(options.drainGraceMs));
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 class DaemonLifecycleService {
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 class DaemonLifecycleService {
13
- handlers() {
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
- "daemon.status": () => Effect.gen(function* () {
16
- const env = yield* DaemonEnv;
17
- const state = yield* DaemonState;
18
- const gateway = yield* ActiveGateway;
19
- return {
20
- pid: process.pid,
21
- workerPid: process.pid,
22
- hostPid: env.hosted?.hostPid ?? process.pid,
23
- hostStartedAt: env.hosted?.hostStartedAt ?? env.startedAt,
24
- startedAt: env.startedAt,
25
- packageVersion: env.packageVersion,
26
- protocolVersion: CONTROL_PROTOCOL_VERSION,
27
- hostProtocolVersion: env.hosted === undefined ? 0 : DAEMON_HOST_PROTOCOL_VERSION,
28
- generation: env.generation,
29
- configRevision: state.revisions.config,
30
- accountRevision: state.revisions.accounts,
31
- controlUrl: gateway.control()?.url ?? "",
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
+ };