@evomap/evolver-proxy 2.0.0-beta.9 → 2.0.1

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 (59) hide show
  1. package/dist/bin/evolver-proxy.d.ts +26 -2
  2. package/dist/bin/evolver-proxy.js +284 -51
  3. package/dist/daemon/atpConsent.js +5 -2
  4. package/dist/daemon/collaborationFacade.js +23 -13
  5. package/dist/daemon/proxyDaemon.d.ts +52 -0
  6. package/dist/daemon/proxyDaemon.js +1067 -24
  7. package/dist/daemon/publishRecallVerifier.d.ts +114 -0
  8. package/dist/daemon/publishRecallVerifier.js +495 -0
  9. package/dist/daemon/selectHub.js +5 -3
  10. package/dist/daemon/systemdNotifier.d.ts +46 -0
  11. package/dist/daemon/systemdNotifier.js +153 -0
  12. package/dist/index.d.ts +4 -1
  13. package/dist/index.js +4 -1
  14. package/dist/lifecycle/claimNudge.d.ts +20 -0
  15. package/dist/lifecycle/claimNudge.js +1 -0
  16. package/dist/lifecycle/deployGuard.js +1 -53
  17. package/dist/lifecycle/legacyNodeId.js +1 -178
  18. package/dist/lifecycle/manager.d.ts +4 -0
  19. package/dist/lifecycle/manager.js +1 -390
  20. package/dist/llm/bodyCapture.js +1 -293
  21. package/dist/llm/index.js +1 -3
  22. package/dist/llm/server.js +1 -359
  23. package/dist/llm/traceBackfill.js +1 -525
  24. package/dist/llm/traceConfig.js +1 -44
  25. package/dist/llm/traceControl.js +1 -85
  26. package/dist/llm/traceEnvelope.js +1 -286
  27. package/dist/llm/traceSink.js +1 -278
  28. package/dist/llm/traceUploadPayload.js +1 -27
  29. package/dist/llm/upstream.d.ts +5 -1
  30. package/dist/llm/upstream.js +1 -491
  31. package/dist/private/accountAssetCompatibility.d.ts +29 -0
  32. package/dist/private/accountAssetCompatibility.js +196 -0
  33. package/dist/private/adapterLoader.d.ts +21 -1
  34. package/dist/private/adapterLoader.js +242 -7
  35. package/dist/private/nodeCredentialStore.d.ts +23 -0
  36. package/dist/private/nodeCredentialStore.js +210 -0
  37. package/dist/router/cachePassthrough.js +1 -13
  38. package/dist/router/features.js +1 -52
  39. package/dist/router/index.js +1 -6
  40. package/dist/router/messagesRoute.js +1 -809
  41. package/dist/router/modelRouter.js +1 -66
  42. package/dist/router/providerRoutes.js +1 -1579
  43. package/dist/router/sseScan.js +1 -543
  44. package/dist/selfUpdate/bootstrap.d.ts +69 -0
  45. package/dist/selfUpdate/bootstrap.js +282 -0
  46. package/dist/selfUpdate/builtinKey.d.ts +4 -0
  47. package/dist/selfUpdate/builtinKey.js +16 -0
  48. package/dist/selfUpdate/executor.d.ts +1 -1
  49. package/dist/selfUpdate/executor.js +1 -1
  50. package/dist/selfUpdate/failureCodes.d.ts +4 -0
  51. package/dist/selfUpdate/failureCodes.js +7 -0
  52. package/dist/selfUpdate/migration.d.ts +93 -0
  53. package/dist/selfUpdate/migration.js +315 -0
  54. package/dist/selfUpdate/policy.d.ts +19 -2
  55. package/dist/selfUpdate/policy.js +82 -2
  56. package/dist/selfUpdate/releaseBinary.js +4 -1
  57. package/dist/sync/engine.d.ts +12 -0
  58. package/dist/sync/engine.js +1 -505
  59. package/package.json +7 -4
@@ -0,0 +1,69 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { type MigrationOptions } from './migration.js';
3
+ export declare const BOOTSTRAP_SUCCESS_FILE = "bootstrap.json";
4
+ export type BootstrapSkipReason = 'already_supervised' | 'already_bootstrapped' | 'unsupported_install_shape' | 'policy_not_auto' | 'bootstrap_disabled' | 'ci_environment' | 'container_environment' | 'recent_failure';
5
+ export interface BootstrapDecision {
6
+ proceed: boolean;
7
+ reason?: BootstrapSkipReason;
8
+ }
9
+ export interface BootstrapOutcome {
10
+ ok: boolean;
11
+ /**
12
+ * 'bootstrapped' / 'migrated' on success; failure/skip reason otherwise. Migration
13
+ * failures record 'migration_failed' / 'migration_timeout' (both cooldown-worthy).
14
+ */
15
+ reason: string;
16
+ detail?: string;
17
+ }
18
+ export interface BootstrapRunOptions {
19
+ env: NodeJS.ProcessEnv;
20
+ platform?: NodeJS.Platform;
21
+ execPath?: string;
22
+ argv1?: string;
23
+ timeoutMs?: number;
24
+ now?: number;
25
+ exists?: (path: string) => boolean;
26
+ readFile?: (path: string) => string;
27
+ writeFile?: (path: string, content: string) => void;
28
+ spawnFn?: typeof spawn;
29
+ /**
30
+ * Extra seams forwarded to the one-time npm/JS → standalone migration (migration.ts).
31
+ * Bootstrap-level seams (exists/readFile/writeFile/spawnFn/now/execPath) win when both
32
+ * are supplied, so the decision and the migration observe the same injected world.
33
+ */
34
+ migration?: MigrationOptions;
35
+ }
36
+ /** Lifecycle state dir mirror of evolver-cli lifecyclePaths (kept dependency-free across packages). */
37
+ export declare function resolveBootstrapStateDir(env: NodeJS.ProcessEnv): string;
38
+ export declare function looksLikeContainer(exists: (path: string) => boolean, readFile: (path: string) => string): boolean;
39
+ /** True when a recent bootstrap/migration attempt failed within the cooldown window. */
40
+ export declare function recentBootstrapFailure(env: NodeJS.ProcessEnv, readFile: (path: string) => string, now: number): boolean;
41
+ /**
42
+ * Decide whether an unsupervised (degraded) startup should attempt first-run bootstrap.
43
+ * Pure — filesystem access is injectable for tests.
44
+ */
45
+ export declare function shouldBootstrap(env: NodeJS.ProcessEnv, platform?: NodeJS.Platform, options?: Pick<BootstrapRunOptions, 'exists' | 'readFile' | 'now' | 'execPath'>): BootstrapDecision;
46
+ /**
47
+ * Resolve the `lifecycle bootstrap` invocation for the current install shape: standalone binary,
48
+ * CLI entry through node, or the npm-installed @evomap/evolver-cli sibling. Returns undefined when
49
+ * no CLI can be located (degrade to the existing warning instead of failing startup).
50
+ */
51
+ export declare function resolveBootstrapCliInvocation(options?: Pick<BootstrapRunOptions, 'execPath' | 'argv1' | 'exists'>): {
52
+ command: string;
53
+ args: string[];
54
+ } | undefined;
55
+ /** Best-effort attempt marker; never throws — bootstrap bookkeeping must not break startup. */
56
+ export declare function recordBootstrapAttempt(env: NodeJS.ProcessEnv, outcome: BootstrapOutcome, options?: Pick<BootstrapRunOptions, 'writeFile' | 'now'>): void;
57
+ /** Spawn `evolver lifecycle bootstrap` and await its result within a bounded timeout. */
58
+ export declare function runBootstrap(options: BootstrapRunOptions): Promise<BootstrapOutcome>;
59
+ export interface DegradedStartupBootstrapResult {
60
+ /** True when bootstrap succeeded and startup should exit so the new service takes over. */
61
+ handedOver: boolean;
62
+ /** Operator-facing single-line message (stdout when handed over, stderr otherwise). */
63
+ message: string;
64
+ }
65
+ /**
66
+ * Orchestrate bootstrap for a degraded (default-auto, unsupervised) startup: decide, attempt,
67
+ * record, and produce the operator message. Never throws.
68
+ */
69
+ export declare function bootstrapDegradedSelfUpdateStartup(env: NodeJS.ProcessEnv, platform?: NodeJS.Platform, options?: Omit<BootstrapRunOptions, 'env' | 'platform'>): Promise<DegradedStartupBootstrapResult>;
@@ -0,0 +1,282 @@
1
+ // First-run supervision bootstrap for the DEFAULT self-update policy.
2
+ //
3
+ // Default auto self-update degrades to 'off' without a durable supervisor attestation (policy.ts).
4
+ // To make `npm install` a complete zero-config path, an unsupervised foreground startup may ONCE
5
+ // register its own user-level durable launcher (`evolver lifecycle bootstrap`) and hand over to it:
6
+ // the generated launcher carries the EVOLVER_SELF_UPDATE_SUPERVISOR attestation, so the next
7
+ // supervised startup runs auto self-update with the unchanged signature/health-check/rollback gates.
8
+ //
9
+ // Bootstrap is a convenience, never an escalation: it is skipped for attested runs, explicit
10
+ // non-auto policies, the EVOLVER_SELF_BOOTSTRAP kill switch, CI, containers, and within a
11
+ // cooldown window after a failed attempt. The npm/JS install shape has no bindable
12
+ // self-update target, so instead of launcher bootstrap it attempts a one-time migration to
13
+ // the standalone release binary (migration.ts). Any failure degrades back to the existing
14
+ // 'off + warning' startup.
15
+ import { spawn } from 'node:child_process';
16
+ import { createRequire } from 'node:module';
17
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
18
+ import { homedir } from 'node:os';
19
+ import { basename, dirname, join, resolve as resolvePath } from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+ import { isSelfUpdateExplicit, resolveSelfUpdatePolicy, selfUpdateSupervisorAttested, } from './policy.js';
22
+ import { expandHomePath } from '../bin/envFile.js';
23
+ import { resolveSelfUpdateTarget } from './releaseBinary.js';
24
+ import { migrateToStandaloneBinary } from './migration.js';
25
+ const requireFromHere = createRequire(import.meta.url);
26
+ export const BOOTSTRAP_SUCCESS_FILE = 'bootstrap.json';
27
+ const BOOTSTRAP_ATTEMPT_FILE = 'bootstrap-attempt.json';
28
+ const BOOTSTRAP_FAILURE_COOLDOWN_MS = 24 * 60 * 60 * 1000;
29
+ // The CLI-side service activation itself spawns up to ~60s (lifecycle powershell/schtasks
30
+ // activation), so a shorter timeout would kill the child mid-activation and leave the
31
+ // half-installed state: the service registered but bootstrap judged failed.
32
+ const BOOTSTRAP_TIMEOUT_MS = 90_000;
33
+ const BOOTSTRAP_ENV_FILE_HANDOFF = 'EVOLVER_INTERNAL_BOOTSTRAP_ENV_FILE';
34
+ /** Lifecycle state dir mirror of evolver-cli lifecyclePaths (kept dependency-free across packages). */
35
+ export function resolveBootstrapStateDir(env) {
36
+ const explicit = env['EVOLVER_LIFECYCLE_STATE_DIR']?.trim();
37
+ const home = env['EVOLVER_HOME'] ?? env['EVOMAP_HOME'] ?? join(homedir(), '.evomap');
38
+ return resolvePath(explicit || join(home, 'lifecycle'));
39
+ }
40
+ function bootstrapChildEnv(env) {
41
+ const childEnv = { ...env };
42
+ const envFile = env['EVOLVER_ENV_FILE']?.trim();
43
+ delete childEnv['EVOLVER_ENV_FILE'];
44
+ delete childEnv[BOOTSTRAP_ENV_FILE_HANDOFF];
45
+ if (envFile)
46
+ childEnv[BOOTSTRAP_ENV_FILE_HANDOFF] = resolvePath(expandHomePath(envFile));
47
+ // Resolve while the foreground proxy still owns cwd, then carry that identity through the
48
+ // bootstrap child and generated service launcher. Service managers do not share one cwd.
49
+ childEnv['EVOLVER_LIFECYCLE_STATE_DIR'] = resolveBootstrapStateDir(env);
50
+ return childEnv;
51
+ }
52
+ const defaultReadTextFile = (path) => readFileSync(path, 'utf8');
53
+ export function looksLikeContainer(exists, readFile) {
54
+ if (exists('/.dockerenv'))
55
+ return true;
56
+ try {
57
+ return /docker|containerd|kubepods|podman|lxc/.test(readFile('/proc/1/cgroup'));
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ }
63
+ function readBootstrapAttempt(env, readFile) {
64
+ try {
65
+ const parsed = JSON.parse(readFile(join(resolveBootstrapStateDir(env), BOOTSTRAP_ATTEMPT_FILE)));
66
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
67
+ return undefined;
68
+ return parsed;
69
+ }
70
+ catch {
71
+ return undefined;
72
+ }
73
+ }
74
+ // Cooldown-worthy attempt outcomes. Migration adds its own failure/timeout outcomes so a
75
+ // broken release (download/verify/install/register) does not get retried on every startup.
76
+ const BOOTSTRAP_FAILURE_OUTCOMES = new Set([
77
+ 'failed', 'timeout', 'cli_not_found', 'migration_failed', 'migration_timeout',
78
+ ]);
79
+ /** True when a recent bootstrap/migration attempt failed within the cooldown window. */
80
+ export function recentBootstrapFailure(env, readFile, now) {
81
+ const attempt = readBootstrapAttempt(env, readFile);
82
+ if (!attempt)
83
+ return false;
84
+ if (typeof attempt['outcome'] !== 'string' || !BOOTSTRAP_FAILURE_OUTCOMES.has(attempt['outcome']))
85
+ return false;
86
+ if (typeof attempt['attemptedAt'] !== 'string')
87
+ return false;
88
+ const attemptedAt = Date.parse(attempt['attemptedAt']);
89
+ if (Number.isNaN(attemptedAt))
90
+ return false;
91
+ return now - attemptedAt < BOOTSTRAP_FAILURE_COOLDOWN_MS;
92
+ }
93
+ /**
94
+ * Decide whether an unsupervised (degraded) startup should attempt first-run bootstrap.
95
+ * Pure — filesystem access is injectable for tests.
96
+ */
97
+ export function shouldBootstrap(env, platform = process.platform, options = {}) {
98
+ if (selfUpdateSupervisorAttested(env))
99
+ return { proceed: false, reason: 'already_supervised' };
100
+ const bootstrapSwitch = env['EVOLVER_SELF_BOOTSTRAP']?.trim();
101
+ if (bootstrapSwitch === '0' || bootstrapSwitch === 'off')
102
+ return { proceed: false, reason: 'bootstrap_disabled' };
103
+ if (isSelfUpdateExplicit(env) && resolveSelfUpdatePolicy(env) !== 'auto') {
104
+ return { proceed: false, reason: 'policy_not_auto' };
105
+ }
106
+ const exists = options.exists ?? existsSync;
107
+ if (exists(join(resolveBootstrapStateDir(env), BOOTSTRAP_SUCCESS_FILE))) {
108
+ return { proceed: false, reason: 'already_bootstrapped' };
109
+ }
110
+ // The npm/JS install shape has no replaceable standalone binary target, so the launcher
111
+ // bootstrap would register a supervised instance that crashes at self-update target
112
+ // resolution on every startup (crash-loop under the service manager). Skip it; an explicit
113
+ // EVOLVER_SELF_UPDATE_TARGET_PATH keeps the target bindable and bypasses this guard.
114
+ try {
115
+ resolveSelfUpdateTarget({ env, processExecPath: options.execPath });
116
+ }
117
+ catch {
118
+ return { proceed: false, reason: 'unsupported_install_shape' };
119
+ }
120
+ const ci = env['CI']?.trim();
121
+ if (ci && ci.toLowerCase() !== 'false' && ci !== '0')
122
+ return { proceed: false, reason: 'ci_environment' };
123
+ if (platform === 'linux' && looksLikeContainer(exists, options.readFile ?? defaultReadTextFile)) {
124
+ return { proceed: false, reason: 'container_environment' };
125
+ }
126
+ if (recentBootstrapFailure(env, options.readFile ?? defaultReadTextFile, options.now ?? Date.now())) {
127
+ return { proceed: false, reason: 'recent_failure' };
128
+ }
129
+ return { proceed: true };
130
+ }
131
+ /**
132
+ * Resolve the `lifecycle bootstrap` invocation for the current install shape: standalone binary,
133
+ * CLI entry through node, or the npm-installed @evomap/evolver-cli sibling. Returns undefined when
134
+ * no CLI can be located (degrade to the existing warning instead of failing startup).
135
+ */
136
+ export function resolveBootstrapCliInvocation(options = {}) {
137
+ const execPath = options.execPath ?? process.execPath;
138
+ const argv1 = options.argv1 ?? process.argv[1];
139
+ const exists = options.exists ?? existsSync;
140
+ const executableName = basename(execPath).toLowerCase();
141
+ if (/^evolver(?:\.exe|-(?:darwin-(?:arm64|x64)|linux-(?:arm64|x64)|windows-x64\.exe))?$/.test(executableName)) {
142
+ return { command: execPath, args: ['lifecycle', 'bootstrap'] };
143
+ }
144
+ if (argv1 && basename(argv1).toLowerCase() === 'cli.js') {
145
+ return { command: execPath, args: [argv1, 'lifecycle', 'bootstrap'] };
146
+ }
147
+ try {
148
+ const entry = requireFromHere.resolve('@evomap/evolver-cli');
149
+ const cliPath = join(dirname(entry), 'cli.js');
150
+ if (exists(cliPath))
151
+ return { command: execPath, args: [cliPath, 'lifecycle', 'bootstrap'] };
152
+ }
153
+ catch {
154
+ // Not resolvable from the installed proxy package — fall through to the monorepo layout.
155
+ }
156
+ const local = fileURLToPath(new URL('../../../evolver-cli/dist/cli.js', import.meta.url));
157
+ if (exists(local))
158
+ return { command: execPath, args: [local, 'lifecycle', 'bootstrap'] };
159
+ return undefined;
160
+ }
161
+ /** Best-effort attempt marker; never throws — bootstrap bookkeeping must not break startup. */
162
+ export function recordBootstrapAttempt(env, outcome, options = {}) {
163
+ const path = join(resolveBootstrapStateDir(env), BOOTSTRAP_ATTEMPT_FILE);
164
+ const record = {
165
+ attemptedAt: new Date(options.now ?? Date.now()).toISOString(),
166
+ outcome: outcome.reason,
167
+ ...(outcome.detail ? { detail: outcome.detail } : {}),
168
+ };
169
+ try {
170
+ const writeFile = options.writeFile ?? ((target, content) => {
171
+ mkdirSync(dirname(target), { recursive: true, mode: 0o700 });
172
+ writeFileSync(target, content, { encoding: 'utf8', mode: 0o600 });
173
+ });
174
+ writeFile(path, `${JSON.stringify(record)}\n`);
175
+ }
176
+ catch {
177
+ // Marker is advisory; startup continues regardless.
178
+ }
179
+ }
180
+ /** Spawn `evolver lifecycle bootstrap` and await its result within a bounded timeout. */
181
+ export async function runBootstrap(options) {
182
+ const invocation = resolveBootstrapCliInvocation(options);
183
+ if (!invocation)
184
+ return { ok: false, reason: 'cli_not_found' };
185
+ const spawnFn = options.spawnFn ?? spawn;
186
+ const timeoutMs = options.timeoutMs ?? BOOTSTRAP_TIMEOUT_MS;
187
+ return new Promise((resolvePromise) => {
188
+ let child;
189
+ try {
190
+ child = spawnFn(invocation.command, invocation.args, {
191
+ stdio: 'ignore',
192
+ windowsHide: true,
193
+ env: bootstrapChildEnv(options.env),
194
+ });
195
+ }
196
+ catch (error) {
197
+ resolvePromise({ ok: false, reason: 'failed', detail: error instanceof Error ? error.message : String(error) });
198
+ return;
199
+ }
200
+ let settled = false;
201
+ const settle = (outcome) => {
202
+ if (settled)
203
+ return;
204
+ settled = true;
205
+ clearTimeout(timer);
206
+ resolvePromise(outcome);
207
+ };
208
+ const timer = setTimeout(() => {
209
+ child.kill();
210
+ settle({ ok: false, reason: 'timeout' });
211
+ }, timeoutMs);
212
+ child.once('error', (error) => {
213
+ settle({ ok: false, reason: 'failed', detail: error.message });
214
+ });
215
+ child.once('exit', (code) => {
216
+ if (code === 0)
217
+ settle({ ok: true, reason: 'bootstrapped' });
218
+ else
219
+ settle({ ok: false, reason: 'failed', detail: `exit ${code ?? 'null'}` });
220
+ });
221
+ });
222
+ }
223
+ /**
224
+ * Orchestrate bootstrap for a degraded (default-auto, unsupervised) startup: decide, attempt,
225
+ * record, and produce the operator message. Never throws.
226
+ */
227
+ export async function bootstrapDegradedSelfUpdateStartup(env, platform = process.platform, options = {}) {
228
+ const bootstrapEnv = {
229
+ ...env,
230
+ EVOLVER_LIFECYCLE_STATE_DIR: resolveBootstrapStateDir(env),
231
+ };
232
+ const decision = shouldBootstrap(bootstrapEnv, platform, options);
233
+ if (!decision.proceed) {
234
+ const reason = decision.reason ?? 'skipped';
235
+ recordBootstrapAttempt(bootstrapEnv, { ok: false, reason }, options);
236
+ if (reason === 'unsupported_install_shape') {
237
+ // Do not suggest `evolver lifecycle bootstrap` here: under the npm/JS install shape it
238
+ // would register a supervised service that crashes at self-update target resolution on
239
+ // every startup (crash-loop). Only a standalone release binary can host self-update —
240
+ // so attempt the one-time migration to it; any skip/failure keeps the degraded startup.
241
+ const migration = await migrateToStandaloneBinary(env, platform, {
242
+ ...options.migration,
243
+ ...(options.execPath !== undefined ? { execPath: options.execPath } : {}),
244
+ ...(options.exists !== undefined ? { exists: options.exists } : {}),
245
+ ...(options.readFile !== undefined ? { readFile: options.readFile } : {}),
246
+ ...(options.writeFile !== undefined ? { writeFile: options.writeFile } : {}),
247
+ ...(options.spawnFn !== undefined ? { spawnFn: options.spawnFn } : {}),
248
+ ...(options.now !== undefined ? { now: options.now } : {}),
249
+ });
250
+ if (migration.outcome === 'migrated') {
251
+ return { handedOver: true, message: migration.message };
252
+ }
253
+ return {
254
+ handedOver: false,
255
+ message: '[evolver-proxy] self-update: running from the npm/JS install shape, which has no standalone '
256
+ + 'binary target for self-update; bootstrap skipped, continuing with self-update off. '
257
+ + 'Install the standalone binary from GitHub Releases and start it to enable self-update. '
258
+ + `(${migration.message})`,
259
+ };
260
+ }
261
+ return {
262
+ handedOver: false,
263
+ message: '[evolver-proxy] self-update: default auto requires a durable supervisor attestation; '
264
+ + `running with self-update off (bootstrap skipped: ${reason}). `
265
+ + 'Run `evolver lifecycle bootstrap` or `evolver lifecycle install-service` to enable.',
266
+ };
267
+ }
268
+ const outcome = await runBootstrap({ env: bootstrapEnv, platform, ...options });
269
+ recordBootstrapAttempt(bootstrapEnv, outcome, options);
270
+ if (outcome.ok) {
271
+ return {
272
+ handedOver: true,
273
+ message: '[evolver-proxy] self-update: registered durable service supervision via `evolver lifecycle bootstrap`; '
274
+ + 'handing over to the service manager and exiting so it can take the IPC port.',
275
+ };
276
+ }
277
+ return {
278
+ handedOver: false,
279
+ message: `[evolver-proxy] self-update: first-run bootstrap failed (${outcome.reason}); `
280
+ + 'running with self-update off. Run `evolver lifecycle install-service` manually to enable.',
281
+ };
282
+ }
@@ -0,0 +1,4 @@
1
+ /** Ed25519 SPKI public key (base64 DER) matching the v2-beta release environment signing key. */
2
+ export declare const BUILTIN_SELF_UPDATE_PUBLIC_KEY = "MCowBQYDK2VwAyEAAgoV6aWwJd5zlxOcPqWuxkDB+isQnKydFStV8X3DxMk=";
3
+ /** Resolve the self-update verification public key: env override wins, else the built-in key. */
4
+ export declare function resolveSelfUpdatePublicKey(env?: NodeJS.ProcessEnv): string;
@@ -0,0 +1,16 @@
1
+ // Built-in Ed25519 verification public key for the official v2-beta self-update channel.
2
+ //
3
+ // A public key is not secret: embedding it lets nodes installed through a trusted
4
+ // distribution channel (npm tarball or prebuilt release binary) verify signed update
5
+ // manifests with zero per-node configuration. Trust bootstraps from the install
6
+ // channel itself (npm registry integrity / release artifact provenance), and every
7
+ // later self-update stays signature-gated.
8
+ //
9
+ // EVOLVER_SELF_UPDATE_PUBLIC_KEY overrides this when set (rotation / private fleets).
10
+ /** Ed25519 SPKI public key (base64 DER) matching the v2-beta release environment signing key. */
11
+ export const BUILTIN_SELF_UPDATE_PUBLIC_KEY = 'MCowBQYDK2VwAyEAAgoV6aWwJd5zlxOcPqWuxkDB+isQnKydFStV8X3DxMk=';
12
+ /** Resolve the self-update verification public key: env override wins, else the built-in key. */
13
+ export function resolveSelfUpdatePublicKey(env = process.env) {
14
+ const configured = env['EVOLVER_SELF_UPDATE_PUBLIC_KEY']?.trim();
15
+ return configured || BUILTIN_SELF_UPDATE_PUBLIC_KEY;
16
+ }
@@ -81,7 +81,7 @@ export declare function _resetSelfUpdateMutex(): void;
81
81
  * Execute a force_update directive end to end: decide → (mutex) → download → VERIFY → atomic replace → restart.
82
82
  *
83
83
  * Order is load-bearing:
84
- * 1. policy off → do nothing (the default-off risk gate; a half-built channel must not auto-apply).
84
+ * 1. policy off → do nothing (explicit opt-out; auto is hard-gated upstream by supervisor + public key).
85
85
  * 2. decideUpdate (pure): reject bad manifests, NOOP when already satisfied (no download, no restart).
86
86
  * 3. mutex: exactly one execution; concurrent callers get `already_in_progress` and touch no disk.
87
87
  * 4. download the staged release.
@@ -35,7 +35,7 @@ function report(deps, result) {
35
35
  * Execute a force_update directive end to end: decide → (mutex) → download → VERIFY → atomic replace → restart.
36
36
  *
37
37
  * Order is load-bearing:
38
- * 1. policy off → do nothing (the default-off risk gate; a half-built channel must not auto-apply).
38
+ * 1. policy off → do nothing (explicit opt-out; auto is hard-gated upstream by supervisor + public key).
39
39
  * 2. decideUpdate (pure): reject bad manifests, NOOP when already satisfied (no download, no restart).
40
40
  * 3. mutex: exactly one execution; concurrent callers get `already_in_progress` and touch no disk.
41
41
  * 4. download the staged release.
@@ -23,6 +23,10 @@ export declare const SELF_UPDATE_FAILURE_CODES: Readonly<{
23
23
  readonly RESTART_FAILED: "restart_failed";
24
24
  readonly READ_BACK_FAILED: "read_back_failed";
25
25
  readonly ROLLBACK_FAILED: "rollback_failed";
26
+ readonly MIGRATION_DOWNLOAD_FAILED: "migration_download_failed";
27
+ readonly MIGRATION_VERIFY_FAILED: "migration_verify_failed";
28
+ readonly MIGRATION_INSTALL_FAILED: "migration_install_failed";
29
+ readonly MIGRATION_REGISTER_FAILED: "migration_register_failed";
26
30
  }>;
27
31
  export type SelfUpdateFailureCode = typeof SELF_UPDATE_FAILURE_CODES[keyof typeof SELF_UPDATE_FAILURE_CODES];
28
32
  export interface ClassifiedSelfUpdateError {
@@ -28,6 +28,13 @@ export const SELF_UPDATE_FAILURE_CODES = Object.freeze({
28
28
  RESTART_FAILED: 'restart_failed',
29
29
  READ_BACK_FAILED: 'read_back_failed',
30
30
  ROLLBACK_FAILED: 'rollback_failed',
31
+ // One-time npm/JS → standalone binary migration (migration.ts). Append-only:
32
+ // these surface in attempt-marker detail strings and operator messages so
33
+ // telemetry can attribute first-run migration failures by phase.
34
+ MIGRATION_DOWNLOAD_FAILED: 'migration_download_failed',
35
+ MIGRATION_VERIFY_FAILED: 'migration_verify_failed',
36
+ MIGRATION_INSTALL_FAILED: 'migration_install_failed',
37
+ MIGRATION_REGISTER_FAILED: 'migration_register_failed',
31
38
  });
32
39
  export class SelfUpdateFailureError extends Error {
33
40
  failureCode;
@@ -0,0 +1,93 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { ops } from '@evomap/evolver-core';
3
+ import type { DownloadResult, ForceUpdateDirective } from './executor.js';
4
+ import { type ReleaseBinaryOptions } from './releaseBinary.js';
5
+ import { type StagedBinaryProbe } from './transaction.js';
6
+ type DownloadedArtifact = ops.DownloadedArtifact;
7
+ type VerifyResult = ops.VerifyResult;
8
+ type FetchFn = (input: string | URL, init?: RequestInit) => Promise<Response>;
9
+ /** Advisory migration state marker written next to the bootstrap attempt marker. */
10
+ export declare const MIGRATION_STATE_FILE = "migration.json";
11
+ /**
12
+ * The CLI-side service activation itself spawns up to ~60s (lifecycle powershell/schtasks
13
+ * activation), so a shorter timeout would kill the child mid-activation and leave the
14
+ * installed binary registered as failed. Mirrors the bootstrap timeout rationale.
15
+ */
16
+ export declare const MIGRATION_REGISTER_TIMEOUT_MS = 90000;
17
+ /** Minimal stat shape migration needs (symlink guard + regular-file check). */
18
+ export interface MigrationFileStat {
19
+ isFile(): boolean;
20
+ isSymbolicLink(): boolean;
21
+ }
22
+ export interface MigrationOptions {
23
+ /** Arch override for release asset resolution (defaults to process.arch). */
24
+ arch?: NodeJS.Architecture;
25
+ exists?: (path: string) => boolean;
26
+ /** Sync text read (bootstrap.ts mirror); used for container detection. */
27
+ readFile?: (path: string) => string;
28
+ /** Sync text write for advisory state markers (attempt marker + migration.json). */
29
+ writeFile?: (path: string, content: string) => void;
30
+ /** Async binary read of the staged artifact (install copy). */
31
+ readBinary?: (path: string) => Promise<Buffer>;
32
+ /** Async binary write with mode (install tmp copy). */
33
+ writeBinary?: (path: string, content: Buffer, mode: number) => Promise<void>;
34
+ /** Recursive mkdir with mode (install dest dir). */
35
+ mkdir?: (path: string, mode: number) => Promise<void>;
36
+ /** Force/recursive removal (staged tmp dir, leftover tmp copies). */
37
+ rm?: (path: string) => Promise<void>;
38
+ rename?: (from: string, to: string) => Promise<void>;
39
+ chmod?: (path: string, mode: number) => Promise<void>;
40
+ /** lstat-shaped stat for the staged-artifact symlink guard. */
41
+ stat?: (path: string) => Promise<MigrationFileStat>;
42
+ fetchFn?: FetchFn;
43
+ /** Probe used by the default preflight (real execution of `--version` / `proxy --help`). */
44
+ probe?: StagedBinaryProbe;
45
+ spawnFn?: typeof spawn;
46
+ now?: number;
47
+ execPath?: string;
48
+ /** Effective uid (tests / platforms without process.getuid). */
49
+ uid?: number | undefined;
50
+ /** Register-step timeout override (defaults to MIGRATION_REGISTER_TIMEOUT_MS). */
51
+ timeoutMs?: number;
52
+ /** High-level seam: download leg (defaults to downloadGithubReleaseArtifact). */
53
+ downloadFn?: (targetVersion: string, directive: ForceUpdateDirective, opts: ReleaseBinaryOptions) => Promise<DownloadResult>;
54
+ /** High-level seam: manifest verification (defaults to ops.verifySelectedManifestArtifact). */
55
+ verifyFn?: (manifest: unknown, downloaded: readonly DownloadedArtifact[], publicKey: string) => VerifyResult;
56
+ /** High-level seam: preflight (defaults to preflightManagedStagedBinary with options.probe). */
57
+ preflightFn?: (targetPath: string, expectedVersion: string) => Promise<void>;
58
+ }
59
+ export interface MigrationResult {
60
+ outcome: 'migrated' | 'skipped' | 'failed';
61
+ /**
62
+ * Structured reason: 'migrated' / 'disabled' / 'root_user' / 'ci_environment' /
63
+ * 'container_environment' / 'cooldown' / 'unsupported_platform' /
64
+ * 'invalid_version_override' / 'version_unresolvable' /
65
+ * 'migration_download_failed:<detail>' / 'migration_verify_failed:<reason>' /
66
+ * 'migration_install_failed:<detail>' / 'migration_register_failed:<detail>' /
67
+ * 'migration_register_timeout'.
68
+ */
69
+ reason: string;
70
+ destPath?: string;
71
+ /** Operator-facing message (short phrase for skipped/failed; full line for migrated). */
72
+ message: string;
73
+ }
74
+ /**
75
+ * Migration install home — mirrors the CLI-side lifecyclePaths home resolution
76
+ * (kept dependency-free across packages): EVOLVER_HOME ?? EVOMAP_HOME ?? ~/.evomap.
77
+ */
78
+ export declare function resolveMigrationHome(env: NodeJS.ProcessEnv): string;
79
+ /** Migration install path for this platform: <home>/bin/<releaseAssetName>. Throws on unsupported platforms. */
80
+ export declare function resolveMigrationDestPath(env: NodeJS.ProcessEnv, platform?: NodeJS.Platform, arch?: NodeJS.Architecture): string;
81
+ /**
82
+ * Resolve the migration target version: EVOLVER_BOOTSTRAP_MIGRATION_VERSION override
83
+ * (normalized through the self-update version contract) wins, else the current package
84
+ * version. Returns undefined when nothing normalizes to a concrete semver.
85
+ */
86
+ export declare function resolveMigrationVersion(env: NodeJS.ProcessEnv): string | undefined;
87
+ /**
88
+ * One-time migration of the npm/JS install shape to the standalone release binary.
89
+ * Never throws: every failure/skip becomes a structured MigrationResult so degraded
90
+ * startup can keep running with self-update off.
91
+ */
92
+ export declare function migrateToStandaloneBinary(env: NodeJS.ProcessEnv, platform?: NodeJS.Platform, options?: MigrationOptions): Promise<MigrationResult>;
93
+ export {};