@evomap/evolver-proxy 2.0.2 → 2.0.12

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 (53) hide show
  1. package/dist/bin/evolver-proxy.d.ts +12 -0
  2. package/dist/bin/evolver-proxy.js +192 -59
  3. package/dist/daemon/proxyDaemon.js +10 -0
  4. package/dist/daemon/systemdNotifier.d.ts +2 -0
  5. package/dist/daemon/systemdNotifier.js +11 -1
  6. package/dist/lifecycle/claimNudge.js +1 -1
  7. package/dist/lifecycle/deployGuard.js +1 -1
  8. package/dist/lifecycle/legacyNodeId.js +1 -1
  9. package/dist/lifecycle/manager.js +1 -1
  10. package/dist/llm/bodyCapture.js +1 -1
  11. package/dist/llm/index.js +1 -1
  12. package/dist/llm/server.js +1 -1
  13. package/dist/llm/traceBackfill.js +1 -1
  14. package/dist/llm/traceConfig.js +1 -1
  15. package/dist/llm/traceControl.js +1 -1
  16. package/dist/llm/traceEnvelope.js +1 -1
  17. package/dist/llm/traceSink.js +1 -1
  18. package/dist/llm/traceUploadPayload.js +1 -1
  19. package/dist/llm/upstream.js +1 -1
  20. package/dist/router/cachePassthrough.js +1 -1
  21. package/dist/router/features.js +1 -1
  22. package/dist/router/index.js +1 -1
  23. package/dist/router/messagesRoute.js +1 -1
  24. package/dist/router/modelRouter.js +1 -1
  25. package/dist/router/providerRoutes.js +1 -1
  26. package/dist/router/sseScan.js +1 -1
  27. package/dist/selfUpdate/bootstrap.d.ts +106 -13
  28. package/dist/selfUpdate/bootstrap.js +3418 -176
  29. package/dist/selfUpdate/bootstrapReadiness.d.ts +9 -0
  30. package/dist/selfUpdate/bootstrapReadiness.js +153 -0
  31. package/dist/selfUpdate/controllerLifecycleAuthority.d.ts +45 -0
  32. package/dist/selfUpdate/controllerLifecycleAuthority.js +61 -0
  33. package/dist/selfUpdate/executor.d.ts +17 -6
  34. package/dist/selfUpdate/executor.js +158 -58
  35. package/dist/selfUpdate/index.d.ts +2 -1
  36. package/dist/selfUpdate/index.js +2 -1
  37. package/dist/selfUpdate/migration.d.ts +80 -15
  38. package/dist/selfUpdate/migration.js +2513 -156
  39. package/dist/selfUpdate/policy.js +2 -8
  40. package/dist/selfUpdate/recoveryChildStartGate.d.ts +29 -0
  41. package/dist/selfUpdate/recoveryChildStartGate.js +319 -0
  42. package/dist/selfUpdate/releaseBinary.d.ts +3 -0
  43. package/dist/selfUpdate/releaseBinary.js +46 -3
  44. package/dist/selfUpdate/transaction.d.ts +8 -0
  45. package/dist/selfUpdate/transaction.js +166 -18
  46. package/dist/selfUpdate/unixController.d.ts +8 -0
  47. package/dist/selfUpdate/unixController.js +364 -38
  48. package/dist/selfUpdate/windowsController.d.ts +14 -2
  49. package/dist/selfUpdate/windowsController.js +482 -103
  50. package/dist/selfUpdate/windowsUpdater.d.ts +25 -0
  51. package/dist/selfUpdate/windowsUpdater.js +174 -7
  52. package/dist/sync/engine.js +1 -1
  53. package/package.json +3 -3
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomBytes } from 'node:crypto';
2
2
  import { execFile } from 'node:child_process';
3
3
  import { constants } from 'node:fs';
4
- import { chmod, lstat, mkdir, open, realpath, rename, rm, writeFile, } from 'node:fs/promises';
4
+ import { chmod, link, lstat, mkdir, open, realpath, rename, rm, writeFile, } from 'node:fs/promises';
5
5
  import { basename, dirname, isAbsolute, join, resolve, win32 } from 'node:path';
6
6
  import { promisify } from 'node:util';
7
7
  import { ops } from '@evomap/evolver-core';
@@ -17,6 +17,32 @@ const UNIX_CONTROLLER_DIRECTORY = 'unix-controller';
17
17
  const UNIX_CONTROLLER_NAME = 'evolver-recovery-controller';
18
18
  const WINDOWS_CONTROLLER_DIRECTORY = 'windows-controller';
19
19
  const WINDOWS_CONTROLLER_NAME = 'evolver-recovery-controller.exe';
20
+ function bootstrapClaimOwnershipReceiptPath(claimPath) {
21
+ const canonical = resolve(claimPath);
22
+ const claimName = basename(canonical);
23
+ if (!/\.bootstrap-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.claim$/i
24
+ .test(claimName)) {
25
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, 'bootstrap_controller_claim_has_no_transaction_owner');
26
+ }
27
+ return join(dirname(canonical), `${claimName.slice(0, -'.claim'.length)}.rollback`);
28
+ }
29
+ async function syncDirectoryDurably(path) {
30
+ let handle;
31
+ try {
32
+ handle = await open(path, constants.O_RDONLY);
33
+ await handle.sync();
34
+ }
35
+ catch (error) {
36
+ if (isErrno(error, 'EINVAL'))
37
+ return;
38
+ if (process.platform === 'win32' && (isErrno(error, 'EPERM') || isErrno(error, 'EACCES')))
39
+ return;
40
+ throw error;
41
+ }
42
+ finally {
43
+ await handle?.close().catch(() => { });
44
+ }
45
+ }
20
46
  export async function inspectDurableSelfUpdate(options) {
21
47
  const paths = await resolveTransactionPaths(options, false);
22
48
  if (!paths)
@@ -52,7 +78,18 @@ export async function provisionStableUnixRecoveryController(options) {
52
78
  const owner = await acquireLock(paths.lock, options.pid ?? process.pid);
53
79
  const controllerDirectory = join(paths.root, UNIX_CONTROLLER_DIRECTORY);
54
80
  const controllerPath = join(controllerDirectory, UNIX_CONTROLLER_NAME);
55
- const temporaryPath = join(controllerDirectory, `.${UNIX_CONTROLLER_NAME}.${randomBytes(8).toString('hex')}.tmp`);
81
+ const temporaryPath = options.artifactClaimPath
82
+ ?? join(controllerDirectory, `.${UNIX_CONTROLLER_NAME}.${randomBytes(8).toString('hex')}.tmp`);
83
+ if (dirname(resolve(temporaryPath)) !== resolve(controllerDirectory)) {
84
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, 'unix_controller_claim_outside_directory');
85
+ }
86
+ const ownershipReceiptPath = options.replaceExisting === false && options.artifactClaimPath !== undefined
87
+ ? bootstrapClaimOwnershipReceiptPath(temporaryPath)
88
+ : undefined;
89
+ if (ownershipReceiptPath && !options.onArtifactPublished) {
90
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, 'bootstrap_controller_claim_has_no_durable_owner_callback');
91
+ }
92
+ let retainClaimOnFailure = options.replaceExisting === false && options.artifactClaimPath !== undefined;
56
93
  try {
57
94
  const loadedJournal = await readJournal(paths.journal);
58
95
  if (loadedJournal) {
@@ -63,16 +100,50 @@ export async function provisionStableUnixRecoveryController(options) {
63
100
  }
64
101
  await ensureSecureDirectory(paths.root);
65
102
  await ensureSecureDirectory(controllerDirectory);
103
+ if (options.replaceExisting === false) {
104
+ try {
105
+ await lstat(controllerPath);
106
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, `bootstrap_controller_already_exists:${controllerPath}`);
107
+ }
108
+ catch (error) {
109
+ if (!isErrno(error, 'ENOENT'))
110
+ throw error;
111
+ }
112
+ }
66
113
  const targetIdentity = await assertRegularNonSymlink(paths.targetPath, 'target');
67
114
  const targetBytes = await readRegularFile(paths.targetPath);
68
- await writeExclusiveFile(temporaryPath, targetBytes, 0o700);
115
+ await writeExclusiveFile(temporaryPath, targetBytes, 0o700, ownershipReceiptPath
116
+ ? {
117
+ ownershipReceiptPath,
118
+ onClaimCreated: () => options.onArtifactPublished(controllerPath, temporaryPath),
119
+ }
120
+ : undefined);
69
121
  await assertSameFileIdentity(paths.targetPath, targetIdentity);
70
- await rename(temporaryPath, controllerPath);
122
+ if (options.replaceExisting === false) {
123
+ await link(temporaryPath, controllerPath);
124
+ await syncDirectoryDurably(controllerDirectory);
125
+ await options.onArtifactPublished?.(controllerPath, temporaryPath);
126
+ if (ownershipReceiptPath) {
127
+ await rm(ownershipReceiptPath, { force: true });
128
+ await syncDirectoryDurably(controllerDirectory);
129
+ }
130
+ await rm(temporaryPath, { force: true });
131
+ await syncDirectoryDurably(controllerDirectory);
132
+ retainClaimOnFailure = false;
133
+ }
134
+ else {
135
+ await rename(temporaryPath, controllerPath);
136
+ await syncDirectoryDurably(controllerDirectory);
137
+ }
71
138
  await chmod(controllerPath, 0o700);
72
139
  return controllerPath;
73
140
  }
74
141
  finally {
75
- await rm(temporaryPath, { force: true }).catch(() => { });
142
+ if (!retainClaimOnFailure) {
143
+ if (ownershipReceiptPath)
144
+ await rm(ownershipReceiptPath, { force: true }).catch(() => { });
145
+ await rm(temporaryPath, { force: true }).catch(() => { });
146
+ }
76
147
  await releaseLock(paths.lock, owner).catch(() => { });
77
148
  }
78
149
  }
@@ -132,7 +203,17 @@ export async function provisionStableWindowsRecoveryController(options, processE
132
203
  const owner = await acquireLock(paths.lock, options.pid ?? process.pid);
133
204
  const controllerDirectory = join(paths.root, WINDOWS_CONTROLLER_DIRECTORY);
134
205
  const controllerPath = stableWindowsRecoveryControllerPathForStateDir(paths.root);
135
- const temporaryPath = join(controllerDirectory, `.${WINDOWS_CONTROLLER_NAME}.${randomBytes(8).toString('hex')}.tmp`);
206
+ const temporaryPath = options.artifactClaimPath ?? join(controllerDirectory, `.${WINDOWS_CONTROLLER_NAME}.${randomBytes(8).toString('hex')}.tmp`);
207
+ if (dirname(resolve(temporaryPath)) !== resolve(controllerDirectory)) {
208
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, 'windows_controller_claim_outside_directory');
209
+ }
210
+ const ownershipReceiptPath = options.replaceExisting === false && options.artifactClaimPath !== undefined
211
+ ? bootstrapClaimOwnershipReceiptPath(temporaryPath)
212
+ : undefined;
213
+ if (ownershipReceiptPath && !options.onArtifactPublished) {
214
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, 'bootstrap_windows_controller_claim_has_no_durable_owner_callback');
215
+ }
216
+ let retainClaimOnFailure = options.replaceExisting === false && options.artifactClaimPath !== undefined;
136
217
  try {
137
218
  const loadedJournal = await readJournal(paths.journal);
138
219
  if (loadedJournal) {
@@ -151,24 +232,60 @@ export async function provisionStableWindowsRecoveryController(options, processE
151
232
  }
152
233
  await ensureSecureDirectory(paths.root);
153
234
  await ensureSecureDirectory(controllerDirectory);
235
+ if (options.replaceExisting === false) {
236
+ try {
237
+ await lstat(controllerPath);
238
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, `bootstrap_windows_controller_already_exists:${controllerPath}`);
239
+ }
240
+ catch (error) {
241
+ if (!isErrno(error, 'ENOENT'))
242
+ throw error;
243
+ }
244
+ }
154
245
  const targetIdentity = await assertRegularNonSymlink(paths.targetPath, 'target');
155
246
  const targetBytes = await readRegularFile(paths.targetPath);
156
- await writeExclusiveFile(temporaryPath, targetBytes, 0o700);
247
+ await writeExclusiveFile(temporaryPath, targetBytes, 0o700, ownershipReceiptPath
248
+ ? {
249
+ ownershipReceiptPath,
250
+ onClaimCreated: () => options.onArtifactPublished(controllerPath, temporaryPath),
251
+ }
252
+ : undefined);
157
253
  await assertSameFileIdentity(paths.targetPath, targetIdentity);
158
- try {
159
- await assertRegularNonSymlink(controllerPath, 'windows_controller');
160
- await rm(controllerPath);
254
+ if (options.replaceExisting !== false) {
255
+ try {
256
+ await assertRegularNonSymlink(controllerPath, 'windows_controller');
257
+ await rm(controllerPath);
258
+ }
259
+ catch (error) {
260
+ if (!isErrno(error, 'ENOENT'))
261
+ throw error;
262
+ }
161
263
  }
162
- catch (error) {
163
- if (!isErrno(error, 'ENOENT'))
164
- throw error;
264
+ if (options.replaceExisting === false) {
265
+ await link(temporaryPath, controllerPath);
266
+ await syncDirectoryDurably(controllerDirectory);
267
+ await options.onArtifactPublished?.(controllerPath, temporaryPath);
268
+ if (ownershipReceiptPath) {
269
+ await rm(ownershipReceiptPath, { force: true });
270
+ await syncDirectoryDurably(controllerDirectory);
271
+ }
272
+ await rm(temporaryPath, { force: true });
273
+ await syncDirectoryDurably(controllerDirectory);
274
+ retainClaimOnFailure = false;
275
+ }
276
+ else {
277
+ await rename(temporaryPath, controllerPath);
278
+ await syncDirectoryDurably(controllerDirectory);
165
279
  }
166
- await rename(temporaryPath, controllerPath);
167
280
  await chmod(controllerPath, 0o700);
168
281
  return controllerPath;
169
282
  }
170
283
  finally {
171
- await rm(temporaryPath, { force: true }).catch(() => { });
284
+ if (!retainClaimOnFailure) {
285
+ if (ownershipReceiptPath)
286
+ await rm(ownershipReceiptPath, { force: true }).catch(() => { });
287
+ await rm(temporaryPath, { force: true }).catch(() => { });
288
+ }
172
289
  await releaseLock(paths.lock, owner).catch(() => { });
173
290
  }
174
291
  }
@@ -787,15 +904,46 @@ async function readRegularFile(source) {
787
904
  await sourceHandle.close().catch(() => { });
788
905
  }
789
906
  }
790
- async function writeExclusiveFile(destination, bytes, mode) {
791
- const destinationHandle = await open(destination, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, mode);
907
+ async function writeExclusiveFile(destination, bytes, mode, publication) {
908
+ const stagingPath = publication?.ownershipReceiptPath ?? destination;
909
+ if (publication
910
+ && dirname(resolve(publication.ownershipReceiptPath)) !== dirname(resolve(destination))) {
911
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, 'bootstrap_controller_ownership_receipt_outside_directory');
912
+ }
913
+ let stagingCreated = false;
914
+ let claimPublished = false;
915
+ const destinationHandle = await open(stagingPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, mode);
916
+ stagingCreated = true;
792
917
  try {
793
918
  await destinationHandle.writeFile(bytes);
794
- await destinationHandle.sync();
795
919
  await destinationHandle.chmod(mode);
920
+ await destinationHandle.sync();
921
+ if (publication) {
922
+ await syncDirectoryDurably(dirname(destination));
923
+ const [opened, receipt] = await Promise.all([
924
+ destinationHandle.stat({ bigint: true }),
925
+ lstat(publication.ownershipReceiptPath, { bigint: true }),
926
+ ]);
927
+ if (!opened.isFile() || opened.dev <= 0n || opened.ino <= 0n
928
+ || receipt.dev !== opened.dev || receipt.ino !== opened.ino) {
929
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, 'bootstrap_controller_ownership_receipt_mismatch');
930
+ }
931
+ await link(publication.ownershipReceiptPath, destination);
932
+ await syncDirectoryDurably(dirname(destination));
933
+ claimPublished = true;
934
+ const claim = await lstat(destination, { bigint: true });
935
+ if (claim.dev !== opened.dev || claim.ino !== opened.ino) {
936
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, 'bootstrap_controller_claim_receipt_mismatch');
937
+ }
938
+ await publication.onClaimCreated();
939
+ }
796
940
  }
797
941
  finally {
798
942
  await destinationHandle.close().catch(() => { });
943
+ if (publication && stagingCreated && !claimPublished) {
944
+ await rm(stagingPath, { force: true }).catch(() => { });
945
+ await syncDirectoryDurably(dirname(stagingPath)).catch(() => { });
946
+ }
799
947
  }
800
948
  }
801
949
  async function restoreBackup(paths, journal, force = false) {
@@ -1,4 +1,6 @@
1
+ import { type ChildProcess } from 'node:child_process';
1
2
  import { type SelfUpdateRecoveryOptions } from './transaction.js';
3
+ import { type RecoveryControllerAuthorityDependencies } from './controllerLifecycleAuthority.js';
2
4
  export declare const UNIX_RECOVERY_CONTROLLER_ARG = "--evolver-unix-recovery-controller";
3
5
  export interface UnixRecoveryControllerOptions extends SelfUpdateRecoveryOptions {
4
6
  argv?: readonly string[];
@@ -7,9 +9,15 @@ export interface UnixRecoveryControllerOptions extends SelfUpdateRecoveryOptions
7
9
  confirmationTimeoutMs?: number;
8
10
  pollIntervalMs?: number;
9
11
  stopTimeoutMs?: number;
12
+ startupGateTimeoutMs?: number;
13
+ startupAttestationTimeoutMs?: number;
10
14
  logger?: {
11
15
  write(chunk: string): unknown;
12
16
  };
17
+ /** Test-only process adapter; production always executes the bound target with the fixed argv. */
18
+ spawnTarget?: (targetPath: string, env: NodeJS.ProcessEnv, startupAttestation: boolean) => ChildProcess;
19
+ /** Test seam for exact lifecycle-owner and supervised-activation authority. */
20
+ lifecycleAuthority?: RecoveryControllerAuthorityDependencies;
13
21
  }
14
22
  export declare function maybeRunUnixRecoveryController(options?: UnixRecoveryControllerOptions): Promise<number | undefined>;
15
23
  export declare function runUnixRecoveryController(options?: UnixRecoveryControllerOptions): Promise<number>;