@evomap/evolver-proxy 2.0.0-beta.2 → 2.0.0-beta.22

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 (69) hide show
  1. package/dist/bin/evolver-llm-proxy.js +0 -0
  2. package/dist/bin/evolver-proxy.d.ts +105 -7
  3. package/dist/bin/evolver-proxy.js +877 -121
  4. package/dist/daemon/atpConsent.js +5 -2
  5. package/dist/daemon/collaborationFacade.js +26 -16
  6. package/dist/daemon/proxyDaemon.d.ts +65 -0
  7. package/dist/daemon/proxyDaemon.js +1384 -29
  8. package/dist/daemon/publishRecallVerifier.d.ts +114 -0
  9. package/dist/daemon/publishRecallVerifier.js +495 -0
  10. package/dist/daemon/selectHub.js +5 -3
  11. package/dist/daemon/systemdNotifier.d.ts +48 -0
  12. package/dist/daemon/systemdNotifier.js +163 -0
  13. package/dist/index.d.ts +4 -1
  14. package/dist/index.js +4 -1
  15. package/dist/lifecycle/claimNudge.d.ts +20 -0
  16. package/dist/lifecycle/claimNudge.js +124 -0
  17. package/dist/lifecycle/legacyNodeId.d.ts +11 -13
  18. package/dist/lifecycle/legacyNodeId.js +35 -20
  19. package/dist/lifecycle/manager.d.ts +4 -0
  20. package/dist/lifecycle/manager.js +15 -2
  21. package/dist/llm/server.js +24 -4
  22. package/dist/llm/traceControl.js +1 -1
  23. package/dist/llm/upstream.d.ts +5 -1
  24. package/dist/llm/upstream.js +72 -2
  25. package/dist/private/accountAssetCompatibility.d.ts +29 -0
  26. package/dist/private/accountAssetCompatibility.js +196 -0
  27. package/dist/private/adapterLoader.d.ts +21 -1
  28. package/dist/private/adapterLoader.js +242 -7
  29. package/dist/private/nodeCredentialStore.d.ts +23 -0
  30. package/dist/private/nodeCredentialStore.js +210 -0
  31. package/dist/router/messagesRoute.js +9 -3
  32. package/dist/router/providerRoutes.js +7 -3
  33. package/dist/selfUpdate/bootstrap.d.ts +162 -0
  34. package/dist/selfUpdate/bootstrap.js +3524 -0
  35. package/dist/selfUpdate/bootstrapReadiness.d.ts +9 -0
  36. package/dist/selfUpdate/bootstrapReadiness.js +153 -0
  37. package/dist/selfUpdate/builtinKey.d.ts +4 -0
  38. package/dist/selfUpdate/builtinKey.js +16 -0
  39. package/dist/selfUpdate/controllerLifecycleAuthority.d.ts +45 -0
  40. package/dist/selfUpdate/controllerLifecycleAuthority.js +61 -0
  41. package/dist/selfUpdate/executor.d.ts +27 -11
  42. package/dist/selfUpdate/executor.js +233 -58
  43. package/dist/selfUpdate/failureCodes.d.ts +10 -0
  44. package/dist/selfUpdate/failureCodes.js +13 -0
  45. package/dist/selfUpdate/index.d.ts +5 -1
  46. package/dist/selfUpdate/index.js +5 -1
  47. package/dist/selfUpdate/lastUpdate.d.ts +3 -1
  48. package/dist/selfUpdate/lastUpdate.js +37 -6
  49. package/dist/selfUpdate/migration.d.ts +158 -0
  50. package/dist/selfUpdate/migration.js +2672 -0
  51. package/dist/selfUpdate/policy.d.ts +19 -2
  52. package/dist/selfUpdate/policy.js +76 -2
  53. package/dist/selfUpdate/recoveryChildStartGate.d.ts +29 -0
  54. package/dist/selfUpdate/recoveryChildStartGate.js +319 -0
  55. package/dist/selfUpdate/releaseBinary.d.ts +13 -0
  56. package/dist/selfUpdate/releaseBinary.js +93 -10
  57. package/dist/selfUpdate/transaction.d.ts +117 -0
  58. package/dist/selfUpdate/transaction.js +1322 -0
  59. package/dist/selfUpdate/unixController.d.ts +23 -0
  60. package/dist/selfUpdate/unixController.js +514 -0
  61. package/dist/selfUpdate/version.d.ts +6 -2
  62. package/dist/selfUpdate/version.js +5 -3
  63. package/dist/selfUpdate/windowsController.d.ts +35 -0
  64. package/dist/selfUpdate/windowsController.js +655 -0
  65. package/dist/selfUpdate/windowsUpdater.d.ts +104 -0
  66. package/dist/selfUpdate/windowsUpdater.js +882 -0
  67. package/dist/sync/engine.d.ts +12 -0
  68. package/dist/sync/engine.js +255 -64
  69. package/package.json +10 -3
@@ -1,13 +1,23 @@
1
1
  import { createHash, randomBytes } from 'node:crypto';
2
+ import { realpathSync } from 'node:fs';
2
3
  import { chmod, copyFile, mkdir, rename, rm, stat, writeFile } from 'node:fs/promises';
3
4
  import { tmpdir } from 'node:os';
4
- import { basename, dirname, join } from 'node:path';
5
+ import { basename, dirname, join, resolve, win32 } from 'node:path';
5
6
  import { createGunzip } from 'node:zlib';
6
7
  import { ops } from '@evomap/evolver-core';
7
8
  import { SELF_UPDATE_FAILURE_CODES, SelfUpdateFailureError, selfUpdateFailure } from './failureCodes.js';
8
9
  const DEFAULT_RELEASES_URL = 'https://github.com/EvoMap/evolver/releases';
9
10
  const SIGNED_MANIFEST_ASSET = 'evolver-update-manifest.json';
10
11
  const RELEASE_DOWNLOAD_TIMEOUT_MS = 60_000;
12
+ /**
13
+ * Hard ceiling for primary release binaries. The binary is buffered before its
14
+ * manifest hash is verified, so an unbounded response could exhaust memory
15
+ * before the verification gate runs. 128MiB leaves headroom above current
16
+ * single-platform binaries while bounding that pre-verification allocation.
17
+ */
18
+ export const MAX_PRIMARY_BINARY_BYTES = 128 * 1024 * 1024;
19
+ /** Release metadata is untrusted and buffered before parsing or verification. */
20
+ export const MAX_RELEASE_METADATA_BYTES = 256 * 1024;
11
21
  /**
12
22
  * Hard ceiling for Channel 1b tarball downloads. A compromised/corrupt release
13
23
  * could advertise a multi-GB tar.gz and OOM us because tarballBytes is buffered
@@ -21,6 +31,10 @@ export const MAX_TARBALL_BYTES = 64 * 1024 * 1024;
21
31
  * bombs fail before the extractor accumulates unbounded output in memory.
22
32
  */
23
33
  export const MAX_EXTRACTED_TARBALL_BYTES = 128 * 1024 * 1024;
34
+ const SELF_UPDATE_RELEASE_BINARY_RE = /^evolver(?:\.exe|-(?:darwin-(?:arm64|x64)|linux-(?:arm64|x64)|windows-x64\.exe))?$/;
35
+ export function isStandaloneReleaseBinaryName(name) {
36
+ return SELF_UPDATE_RELEASE_BINARY_RE.test(name.toLowerCase());
37
+ }
24
38
  export function requiredVersionForRelease(raw) {
25
39
  try {
26
40
  return ops.requireSelfUpdateVersion(raw, 'required_version');
@@ -75,7 +89,7 @@ export async function downloadGithubReleaseArtifact(targetVersion, directive, op
75
89
  }
76
90
  async function downloadBinaryAsset(version, assetName, directive, opts) {
77
91
  const assetUrl = releaseDownloadUrl(directive.release_url, version, assetName);
78
- const bytes = Buffer.from(await fetchBytes(assetUrl, opts.fetchFn));
92
+ const bytes = Buffer.from(await fetchBytes(assetUrl, opts.fetchFn, resolvedPrimaryBinaryLimit(opts.maxPrimaryBinaryBytes)));
79
93
  if (bytes.byteLength === 0) {
80
94
  throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.DOWNLOAD_INCOMPLETE, `empty release asset:${assetName}`);
81
95
  }
@@ -192,22 +206,66 @@ export async function atomicReplaceExecutable(stagedPath, opts = {}) {
192
206
  }
193
207
  }
194
208
  export function resolveSelfUpdateTarget(opts = {}) {
195
- const explicitTarget = opts.targetPath ?? opts.env?.['EVOLVER_SELF_UPDATE_TARGET_PATH'];
209
+ const explicitTarget = opts.targetPath ?? opts.env?.['EVOLVER_SELF_UPDATE_TARGET_PATH']?.trim();
196
210
  if (explicitTarget)
197
211
  return { path: explicitTarget, explicit: true };
198
212
  const execPath = opts.processExecPath ?? process.execPath;
199
- const name = basename(execPath).toLowerCase();
200
- if (name.startsWith('evolver'))
213
+ // Split on both separators so a win32-shaped path (backslashes) is classified
214
+ // correctly even when this check runs on a POSIX host (shape probes, tests).
215
+ const segments = execPath.split(/[/\\]+/).filter(Boolean);
216
+ const name = (segments.length > 0 ? segments[segments.length - 1] : basename(execPath)).toLowerCase();
217
+ if (isStandaloneReleaseBinaryName(name))
201
218
  return { path: execPath, explicit: false };
202
219
  throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.INSTALL_GUARD_UNREADABLE, `self_update_target_required:${execPath}`);
203
220
  }
221
+ export function assertSelfUpdateProcessTargetBound(options, allowUnresolvedTarget = false) {
222
+ let targetPath;
223
+ try {
224
+ targetPath = resolveSelfUpdateTarget(options).path;
225
+ }
226
+ catch {
227
+ if (allowUnresolvedTarget)
228
+ return;
229
+ throw new Error('self_update_process_target_mismatch');
230
+ }
231
+ const processExecPath = options.processExecPath ?? process.execPath;
232
+ try {
233
+ if (canonicalExecutablePath(processExecPath) !== canonicalExecutablePath(targetPath)) {
234
+ throw new Error('self_update_process_target_mismatch');
235
+ }
236
+ }
237
+ catch {
238
+ throw new Error('self_update_process_target_mismatch');
239
+ }
240
+ }
241
+ export function selfUpdateProcessTargetBindable(options) {
242
+ try {
243
+ assertSelfUpdateProcessTargetBound(options);
244
+ return true;
245
+ }
246
+ catch {
247
+ return false;
248
+ }
249
+ }
250
+ function canonicalExecutablePath(path) {
251
+ const canonical = realpathSync.native(path);
252
+ if (process.platform !== 'win32')
253
+ return resolve(canonical);
254
+ const withoutNamespace = canonical
255
+ .replace(/^\\\\\?\\UNC\\/i, '\\\\')
256
+ .replace(/^\\\\\?\\/i, '');
257
+ return win32.normalize(withoutNamespace).toLowerCase();
258
+ }
204
259
  function releaseDownloadUrl(releaseUrl, version, assetName) {
205
260
  let base;
206
261
  try {
207
262
  base = new URL(releaseUrl && releaseUrl.trim() ? releaseUrl : DEFAULT_RELEASES_URL);
208
263
  }
209
264
  catch (err) {
210
- throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.DOWNLOAD_FAILED, errorDetail(err), { cause: err });
265
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.DOWNLOAD_FAILED, 'invalid_release_url', { cause: err });
266
+ }
267
+ if (base.username || base.password) {
268
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.DOWNLOAD_FAILED, 'invalid_release_url_credentials');
211
269
  }
212
270
  if (base.protocol !== 'https:' || base.hostname !== 'github.com') {
213
271
  throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.DOWNLOAD_FAILED, 'invalid_release_url_origin');
@@ -242,8 +300,8 @@ async function fetchSignedReleaseManifest(releaseUrl, version, assetName, fetchF
242
300
  if (!manifestVersion) {
243
301
  throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.DOWNLOAD_INCOMPLETE, 'signed_manifest_invalid_version');
244
302
  }
245
- if (!ops.currentSatisfiesRequiredVersion(manifestVersion, version)) {
246
- throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.DOWNLOADED_VERSION_MISMATCH, 'signed_manifest_below_required');
303
+ if (manifestVersion !== version) {
304
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.DOWNLOADED_VERSION_MISMATCH, 'signed_manifest_version_mismatch');
247
305
  }
248
306
  const artifacts = Array.isArray(manifest.artifacts) ? manifest.artifacts : [];
249
307
  if (!artifacts.some((artifact) => artifact && typeof artifact.path === 'string' && basename(artifact.path) === assetName)) {
@@ -253,7 +311,8 @@ async function fetchSignedReleaseManifest(releaseUrl, version, assetName, fetchF
253
311
  }
254
312
  async function fetchText(url, fetchFn) {
255
313
  const fetched = await fetchWith(url, fetchFn);
256
- return readBodyWithTimeout(url, 'text', () => fetched.response.text(), fetched.abort);
314
+ const bytes = await readBodyWithTimeout(url, 'text', () => readBytesWithLimit(fetched.response, MAX_RELEASE_METADATA_BYTES, fetched.abort), fetched.abort);
315
+ return Buffer.from(bytes).toString('utf8');
257
316
  }
258
317
  async function fetchBytes(url, fetchFn, maxBytes) {
259
318
  const fetched = await fetchWith(url, fetchFn);
@@ -267,6 +326,12 @@ async function readBytesWithLimit(response, maxBytes, abort) {
267
326
  }
268
327
  return arr;
269
328
  }
329
+ const declaredBytes = parseContentLength(response.headers.get('content-length'));
330
+ if (declaredBytes !== undefined && declaredBytes > maxBytes) {
331
+ abort();
332
+ await response.body.cancel().catch(() => { });
333
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.DOWNLOAD_INCOMPLETE, `release_body_too_large:${declaredBytes} > ${maxBytes}`);
334
+ }
270
335
  const reader = response.body.getReader();
271
336
  const chunks = [];
272
337
  let totalBytes = 0;
@@ -297,6 +362,15 @@ async function readBytesWithLimit(response, maxBytes, abort) {
297
362
  }
298
363
  return out.buffer;
299
364
  }
365
+ function parseContentLength(raw) {
366
+ const trimmed = raw?.trim();
367
+ if (!trimmed || !/^\d+$/.test(trimmed))
368
+ return undefined;
369
+ const parsed = Number(trimmed);
370
+ if (!Number.isSafeInteger(parsed))
371
+ return Number.POSITIVE_INFINITY;
372
+ return parsed;
373
+ }
300
374
  async function fetchWith(url, fetchFn) {
301
375
  const fn = fetchFn ?? globalThis.fetch;
302
376
  if (!fn)
@@ -360,7 +434,7 @@ function shaForAsset(sums, assetName) {
360
434
  }
361
435
  function assertSafeExecutableTarget(targetPath, explicitTarget) {
362
436
  const name = basename(targetPath).toLowerCase();
363
- if ((name === 'node' || name === 'node.exe' || !name.startsWith('evolver')) && !explicitTarget) {
437
+ if (!isStandaloneReleaseBinaryName(name) && !explicitTarget) {
364
438
  throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.INSTALL_GUARD_NAME_MISMATCH, `unsafe_self_update_target:${targetPath}`);
365
439
  }
366
440
  }
@@ -472,6 +546,15 @@ function resolvedExtractedTarballLimit(requested) {
472
546
  return MAX_EXTRACTED_TARBALL_BYTES;
473
547
  return Math.min(Math.floor(requested), MAX_EXTRACTED_TARBALL_BYTES);
474
548
  }
549
+ function resolvedPrimaryBinaryLimit(requested) {
550
+ if (requested === undefined)
551
+ return MAX_PRIMARY_BINARY_BYTES;
552
+ if (!Number.isFinite(requested) || requested <= 0)
553
+ return MAX_PRIMARY_BINARY_BYTES;
554
+ // Tests may lower the cap without providing a production escape hatch that
555
+ // could raise or disable the hard safety boundary.
556
+ return Math.min(Math.floor(requested), MAX_PRIMARY_BINARY_BYTES);
557
+ }
475
558
  function readTarString(block, start, length) {
476
559
  let end = start;
477
560
  const max = start + length;
@@ -0,0 +1,117 @@
1
+ import type { DownloadResult } from './executor.js';
2
+ import { type ReleaseBinaryOptions } from './releaseBinary.js';
3
+ export type SelfUpdateJournalStage = 'preparing' | 'downloaded' | 'verified' | 'backed_up' | 'install_pending' | 'installed' | 'restarted' | 'health_check_pending' | 'rolling_back' | 'rollback_pending' | 'confirmed' | 'rolled_back' | 'rollback_failed';
4
+ export interface SelfUpdateJournal {
5
+ schema_version: 2;
6
+ transaction_id: string;
7
+ stage: SelfUpdateJournalStage;
8
+ from_version: string;
9
+ target_version: string;
10
+ platform: NodeJS.Platform;
11
+ arch: NodeJS.Architecture;
12
+ installing_pid: number;
13
+ created_at: string;
14
+ updated_at: string;
15
+ recovery_attempts: number;
16
+ /** Canonical logical install path: real parent directory plus the target leaf name. */
17
+ target_path: string;
18
+ /** Normalized operator-configured spelling used only when its parent can no longer be resolved. */
19
+ configured_target_path?: string;
20
+ staged_name?: string;
21
+ backup_name?: string;
22
+ failure_code?: string;
23
+ verified_sha256?: string;
24
+ }
25
+ export interface DurableSelfUpdateSession {
26
+ adoptDownloaded(download: DownloadResult): Promise<DownloadResult>;
27
+ markVerified(artifacts: readonly {
28
+ bytes?: Uint8Array;
29
+ sha256?: string;
30
+ }[]): Promise<void>;
31
+ install(): Promise<void>;
32
+ markRestartRequested(): Promise<void>;
33
+ abort(failureCode: string): Promise<void>;
34
+ rollback(failureCode: string): Promise<void>;
35
+ release(): Promise<void>;
36
+ }
37
+ export interface SelfUpdateRecoveryResult {
38
+ outcome: 'none' | 'pending_health' | 'rollback_pending' | 'confirmed' | 'rolled_back' | 'blocked';
39
+ stage?: SelfUpdateJournalStage;
40
+ targetVersion?: string;
41
+ fromVersion?: string;
42
+ restartRequired?: boolean;
43
+ failureCode?: string;
44
+ }
45
+ export interface StagedBinaryProbeOptions {
46
+ cwd: string;
47
+ env: NodeJS.ProcessEnv;
48
+ timeout: number;
49
+ windowsHide: boolean;
50
+ maxBuffer: number;
51
+ }
52
+ export type StagedBinaryProbe = (targetPath: string, args: readonly string[], options: StagedBinaryProbeOptions) => Promise<{
53
+ stdout: string;
54
+ }>;
55
+ export interface DurableSelfUpdateOptions extends ReleaseBinaryOptions {
56
+ stateDir?: string;
57
+ currentVersion: string;
58
+ platform?: NodeJS.Platform;
59
+ arch?: NodeJS.Architecture;
60
+ pid?: number;
61
+ now?: () => Date;
62
+ readBackVersion?: (targetPath: string) => Promise<string>;
63
+ stagedBinaryProbe?: StagedBinaryProbe;
64
+ /** Test hook invoked after a stale lock generation is observed and before its successor is published. */
65
+ beforeStaleLockReclaim?: () => void | Promise<void>;
66
+ }
67
+ export type SelfUpdateRecoveryOptions = Omit<DurableSelfUpdateOptions, 'currentVersion'> & {
68
+ currentVersion?: string;
69
+ /** Runs after a durable journal is loaded and before recovery changes the journal, target, or managed artifacts. */
70
+ beforeJournalMutation?: () => void | Promise<void>;
71
+ };
72
+ export interface StableUnixRecoveryControllerOptions extends SelfUpdateRecoveryOptions {
73
+ platform?: NodeJS.Platform;
74
+ /** Bootstrap uses create-only ownership; explicit install-service keeps replacement semantics. */
75
+ replaceExisting?: boolean;
76
+ artifactClaimPath?: string;
77
+ onArtifactPublished?: (path: string, claimPath: string) => void | Promise<void>;
78
+ }
79
+ export interface StableWindowsRecoveryControllerOptions extends SelfUpdateRecoveryOptions {
80
+ platform?: NodeJS.Platform;
81
+ /** Bootstrap uses create-only ownership; explicit install-service keeps replacement semantics. */
82
+ replaceExisting?: boolean;
83
+ artifactClaimPath?: string;
84
+ onArtifactPublished?: (path: string, claimPath: string) => void | Promise<void>;
85
+ }
86
+ export declare function inspectDurableSelfUpdate(options: SelfUpdateRecoveryOptions): Promise<SelfUpdateRecoveryResult>;
87
+ export declare function resolveStableUnixRecoveryControllerPath(options: StableUnixRecoveryControllerOptions): Promise<string>;
88
+ export declare function stableUnixRecoveryControllerPathForTarget(targetPath: string, stateDir?: string): string;
89
+ /**
90
+ * Installs an executable copy outside the mutable target path. The transaction
91
+ * lock and the existing no-follow file primitives keep service installation
92
+ * from racing an update or copying through a symlink.
93
+ */
94
+ export declare function provisionStableUnixRecoveryController(options: StableUnixRecoveryControllerOptions): Promise<string>;
95
+ export declare function bindStableUnixRecoveryController(options: StableUnixRecoveryControllerOptions, processExecPath: string): Promise<{
96
+ controllerPath: string;
97
+ targetPath: string;
98
+ }>;
99
+ export declare function bindStableWindowsRecoveryController(options: StableWindowsRecoveryControllerOptions, processExecPath: string): Promise<{
100
+ controllerPath: string;
101
+ stateDir: string;
102
+ targetPath: string;
103
+ }>;
104
+ export declare function stableWindowsRecoveryControllerPathForStateDir(stateDir: string): string;
105
+ /**
106
+ * Provision or refresh the long-lived controller while it is not running.
107
+ * Service installation stops the Scheduled Task before calling this command;
108
+ * each self-update only replaces the separate windows-updater worker path.
109
+ */
110
+ export declare function provisionStableWindowsRecoveryController(options: StableWindowsRecoveryControllerOptions, processExecPath: string): Promise<string>;
111
+ export declare function beginDurableSelfUpdate(targetVersion: string, options: DurableSelfUpdateOptions): Promise<DurableSelfUpdateSession>;
112
+ export declare function recoverDurableSelfUpdate(options: SelfUpdateRecoveryOptions): Promise<SelfUpdateRecoveryResult>;
113
+ export declare function markWindowsInstallApplied(options: SelfUpdateRecoveryOptions): Promise<SelfUpdateRecoveryResult>;
114
+ export declare function confirmDurableSelfUpdate(options: SelfUpdateRecoveryOptions): Promise<SelfUpdateRecoveryResult>;
115
+ export declare function rollbackDurableSelfUpdate(options: SelfUpdateRecoveryOptions, failureCode: string): Promise<SelfUpdateRecoveryResult>;
116
+ export declare function normalizeCanonicalSelfUpdateTargetPath(targetPath: string, platform?: NodeJS.Platform): string;
117
+ export declare function preflightManagedStagedBinary(targetPath: string, expectedVersion: string, probe?: StagedBinaryProbe): Promise<void>;