@evomap/evolver-proxy 2.0.0-beta.19 → 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 (43) hide show
  1. package/dist/bin/evolver-proxy.d.ts +12 -0
  2. package/dist/bin/evolver-proxy.js +257 -56
  3. package/dist/daemon/proxyDaemon.d.ts +12 -0
  4. package/dist/daemon/proxyDaemon.js +313 -18
  5. package/dist/daemon/systemdNotifier.d.ts +2 -0
  6. package/dist/daemon/systemdNotifier.js +11 -1
  7. package/dist/llm/upstream.js +54 -7
  8. package/dist/private/accountAssetCompatibility.d.ts +1 -0
  9. package/dist/private/accountAssetCompatibility.js +3 -3
  10. package/dist/private/adapterLoader.js +4 -3
  11. package/dist/router/messagesRoute.js +9 -3
  12. package/dist/router/providerRoutes.js +7 -3
  13. package/dist/selfUpdate/bootstrap.d.ts +162 -0
  14. package/dist/selfUpdate/bootstrap.js +3524 -0
  15. package/dist/selfUpdate/bootstrapReadiness.d.ts +9 -0
  16. package/dist/selfUpdate/bootstrapReadiness.js +153 -0
  17. package/dist/selfUpdate/builtinKey.d.ts +4 -0
  18. package/dist/selfUpdate/builtinKey.js +16 -0
  19. package/dist/selfUpdate/controllerLifecycleAuthority.d.ts +45 -0
  20. package/dist/selfUpdate/controllerLifecycleAuthority.js +61 -0
  21. package/dist/selfUpdate/executor.d.ts +18 -7
  22. package/dist/selfUpdate/executor.js +159 -59
  23. package/dist/selfUpdate/failureCodes.d.ts +4 -0
  24. package/dist/selfUpdate/failureCodes.js +7 -0
  25. package/dist/selfUpdate/index.d.ts +2 -1
  26. package/dist/selfUpdate/index.js +2 -1
  27. package/dist/selfUpdate/migration.d.ts +158 -0
  28. package/dist/selfUpdate/migration.js +2672 -0
  29. package/dist/selfUpdate/policy.d.ts +19 -2
  30. package/dist/selfUpdate/policy.js +76 -2
  31. package/dist/selfUpdate/recoveryChildStartGate.d.ts +29 -0
  32. package/dist/selfUpdate/recoveryChildStartGate.js +319 -0
  33. package/dist/selfUpdate/releaseBinary.d.ts +3 -0
  34. package/dist/selfUpdate/releaseBinary.js +50 -4
  35. package/dist/selfUpdate/transaction.d.ts +8 -0
  36. package/dist/selfUpdate/transaction.js +166 -18
  37. package/dist/selfUpdate/unixController.d.ts +8 -0
  38. package/dist/selfUpdate/unixController.js +366 -38
  39. package/dist/selfUpdate/windowsController.d.ts +14 -2
  40. package/dist/selfUpdate/windowsController.js +484 -103
  41. package/dist/selfUpdate/windowsUpdater.d.ts +25 -0
  42. package/dist/selfUpdate/windowsUpdater.js +174 -7
  43. package/package.json +3 -3
@@ -2,12 +2,14 @@
2
2
  import { mailbox } from '@evomap/evolver-core';
3
3
  import { PrivateNodeCredentialStore } from '../private/nodeCredentialStore.js';
4
4
  import { loadEnvFileFromEnv } from './envFile.js';
5
+ import { type DegradedStartupBootstrapResult } from '../selfUpdate/bootstrap.js';
5
6
  import { type SelfUpdatePolicy } from '../selfUpdate/policy.js';
6
7
  import { type ReleaseBinaryOptions } from '../selfUpdate/releaseBinary.js';
7
8
  import { rollbackDurableSelfUpdate, type SelfUpdateRecoveryOptions, type SelfUpdateRecoveryResult, type StagedBinaryProbe } from '../selfUpdate/transaction.js';
8
9
  import { maybeRunWindowsUpdaterWorkerFromArgv } from '../selfUpdate/windowsUpdater.js';
9
10
  import { maybeRunUnixRecoveryController } from '../selfUpdate/unixController.js';
10
11
  import { maybeRunWindowsRecoveryController } from '../selfUpdate/windowsController.js';
12
+ import { consumeRecoveryChildStartGate } from '../selfUpdate/recoveryChildStartGate.js';
11
13
  import type { AtpProxyClient, ProxyDaemonDeps, ProxyTickReport } from '../daemon/proxyDaemon.js';
12
14
  import type { HelloLifecycleMode, HelloResult, HeartbeatOptions, HeartbeatResult } from '../lifecycle/manager.js';
13
15
  import type { InboundResult } from '../sync/engine.js';
@@ -15,7 +17,16 @@ import type { InboundResult } from '../sync/engine.js';
15
17
  export interface RunProxyMainOptions {
16
18
  environmentPrepared?: boolean;
17
19
  recoveryPrepared?: SelfUpdateRecoveryResult;
20
+ requireLifecycleBootstrapState?: boolean;
21
+ /** Test-only transport seam. Production always resolves the configured hub runtime. */
22
+ connectRuntime?: typeof connectHubRuntime;
23
+ /** Test-only bounded loop seam. Production always runs the managed proxy loop. */
24
+ runLoop?: Parameters<typeof runManagedProxyLoop>[0]['runLoop'];
18
25
  }
26
+ export declare function writeBootstrapStartupResult(result: DegradedStartupBootstrapResult, writers?: {
27
+ stdout?: (text: string) => void;
28
+ stderr?: (text: string) => void;
29
+ }): 0 | 1 | undefined;
19
30
  export declare function runProxyMain(options?: RunProxyMainOptions): Promise<void>;
20
31
  export declare function recoverBoundDurableSelfUpdate(options: Omit<SelfUpdateRecoveryOptions, 'beforeJournalMutation'>): Promise<SelfUpdateRecoveryResult>;
21
32
  export declare function loadProxyEnvFile(env: NodeJS.ProcessEnv): ReturnType<typeof loadEnvFileFromEnv>;
@@ -52,6 +63,7 @@ export interface RunProxyCliOptions {
52
63
  runUnixRecoveryController?: typeof maybeRunUnixRecoveryController;
53
64
  runWindowsRecoveryController?: typeof maybeRunWindowsRecoveryController;
54
65
  runWindowsUpdaterWorker?: typeof maybeRunWindowsUpdaterWorkerFromArgv;
66
+ consumeChildStartGate?: typeof consumeRecoveryChildStartGate;
55
67
  }
56
68
  export interface ProxyCliPathOptions {
57
69
  home?: string;
@@ -2,9 +2,9 @@
2
2
  import { createHash, randomBytes } from 'node:crypto';
3
3
  import { readFileSync, realpathSync, rmSync } from 'node:fs';
4
4
  import { homedir } from 'node:os';
5
- import { join, resolve, win32 } from 'node:path';
5
+ import { join, resolve } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
- import { daemon, hub as hubNs, mailbox } from '@evomap/evolver-core';
7
+ import { bootstrap as coreBootstrap, daemon, hub as hubNs, mailbox, util, verify } from '@evomap/evolver-core';
8
8
  import { AtpHubClient, connectPublicHub, globalFetchLike, isNodeSecret, parseNodeSecretVersion } from '@evomap/evolver-adapter-public';
9
9
  import { ProxyDaemon } from '../daemon/proxyDaemon.js';
10
10
  import { resolveHubMode, resolveHubUrl } from '../daemon/selectHub.js';
@@ -19,25 +19,47 @@ import { publishProxySettings } from './proxySettings.js';
19
19
  import { expandHomePath, loadEnvFileFromEnv } from './envFile.js';
20
20
  import { readLegacyNodeId, resolveProxyNodeId } from '../lifecycle/legacyNodeId.js';
21
21
  import { createClaimNudge, wrapHelloWithClaimNudge } from '../lifecycle/claimNudge.js';
22
+ import { acquireLifecycleBootstrapOwnerLease, assertSupervisedLifecycleBootstrapState, bootstrapDegradedSelfUpdateStartup, clearRecoveryControllerLifecycleOwnerCapability, lifecycleBootstrapStatePresent, publishRecoveryControllerLifecycleStartupAttestation, RECOVERY_CONTROLLER_LIFECYCLE_OWNER_CAPABILITY_ENV, } from '../selfUpdate/bootstrap.js';
22
23
  import { getCurrentVersion } from '../selfUpdate/version.js';
23
- import { resolveSelfUpdatePolicy } from '../selfUpdate/policy.js';
24
- import { atomicReplaceExecutable, downloadGithubReleaseArtifact, resolveGithubReleaseManifest, resolveSelfUpdateTarget, } from '../selfUpdate/releaseBinary.js';
24
+ import { resolveEffectiveSelfUpdatePolicy, selfUpdateSupervisorAttested, } from '../selfUpdate/policy.js';
25
+ import { resolveSelfUpdatePublicKey } from '../selfUpdate/builtinKey.js';
26
+ import { atomicReplaceExecutable, assertSelfUpdateProcessTargetBound as assertReleaseSelfUpdateProcessTargetBound, downloadGithubReleaseArtifact, resolveGithubReleaseManifest, } from '../selfUpdate/releaseBinary.js';
25
27
  import { beginDurableSelfUpdate, confirmDurableSelfUpdate, recoverDurableSelfUpdate, rollbackDurableSelfUpdate, } from '../selfUpdate/transaction.js';
26
- import { SELF_UPDATE_FAILURE_CODES } from '../selfUpdate/failureCodes.js';
27
- import { maybeRunWindowsUpdaterWorkerFromArgv } from '../selfUpdate/windowsUpdater.js';
28
+ import { SELF_UPDATE_FAILURE_CODES, selfUpdateFailure } from '../selfUpdate/failureCodes.js';
29
+ import { maybeRunWindowsUpdaterWorkerFromArgv, WINDOWS_UPDATER_WORKER_ARG, } from '../selfUpdate/windowsUpdater.js';
28
30
  import { maybeRunUnixRecoveryController } from '../selfUpdate/unixController.js';
29
31
  import { maybeRunWindowsRecoveryController } from '../selfUpdate/windowsController.js';
32
+ import { consumeRecoveryChildStartGate, RECOVERY_CHILD_START_GATE_ENV, } from '../selfUpdate/recoveryChildStartGate.js';
33
+ import { publishLifecycleBootstrapReadiness } from '../selfUpdate/bootstrapReadiness.js';
30
34
  import { finalizeSelfUpdateRecoveryLastUpdate } from '../selfUpdate/lastUpdate.js';
35
+ export function writeBootstrapStartupResult(result, writers = {}) {
36
+ const line = `${result.message}\n`;
37
+ if (result.disposition === 'handoff') {
38
+ (writers.stdout ?? ((text) => { process.stdout.write(text); }))(line);
39
+ return result.exitCode;
40
+ }
41
+ (writers.stderr ?? ((text) => { process.stderr.write(text); }))(line);
42
+ return result.disposition === 'fail_closed' ? result.exitCode : undefined;
43
+ }
31
44
  export async function runProxyMain(options = {}) {
45
+ if (process.env[RECOVERY_CHILD_START_GATE_ENV] !== undefined) {
46
+ throw new Error('self_update_recovery_child_start_gate_unconsumed');
47
+ }
32
48
  if (process.argv.includes('--help') || process.argv.includes('-h')) {
33
49
  process.stdout.write(proxyUsage());
34
50
  return;
35
51
  }
36
52
  // Recovery must run before any hub/store/runtime initialization. In particular,
37
53
  // a broken store or env-file pointer must not prevent a pending-health update from restoring the old binary.
54
+ const requireLifecycleBootstrapState = options.requireLifecycleBootstrapState
55
+ ?? unpinnedLegacySupervisorRequiresLifecycleState(process.env);
56
+ const recoveryEnvironment = proxyRecoveryEnvironment(process.env);
57
+ assertSupervisedLifecycleBootstrapState(recoveryEnvironment, {
58
+ requireLifecycleState: requireLifecycleBootstrapState,
59
+ });
38
60
  const recovery = options.recoveryPrepared
39
61
  ?? await recoverBoundDurableSelfUpdate({
40
- env: proxyRecoveryEnvironment(process.env),
62
+ env: recoveryEnvironment,
41
63
  processExecPath: process.execPath,
42
64
  });
43
65
  if (recovery.outcome === 'blocked') {
@@ -50,6 +72,15 @@ export async function runProxyMain(options = {}) {
50
72
  if (envFile.error)
51
73
  throw new Error(`failed to load EVOLVER_ENV_FILE: ${safeLoopMessage(envFile.error)}`);
52
74
  }
75
+ assertSupervisedLifecycleBootstrapState(process.env, {
76
+ requireLifecycleState: requireLifecycleBootstrapState,
77
+ });
78
+ try {
79
+ publishRecoveryControllerLifecycleStartupAttestation(process.env);
80
+ }
81
+ finally {
82
+ clearRecoveryControllerLifecycleOwnerCapability(process.env);
83
+ }
53
84
  let storePath;
54
85
  let store;
55
86
  let proxyDaemon;
@@ -83,7 +114,18 @@ export async function runProxyMain(options = {}) {
83
114
  // same owner. See lifecycle/legacyNodeId.ts for the duplicate-node rationale.
84
115
  const senderId = () => resolveProxyNodeId({ storedNodeId: store.getState('node_id'), configuredNodeId });
85
116
  evolverVersion = getCurrentVersion();
86
- selfUpdatePolicy = resolveSelfUpdatePolicy(process.env);
117
+ // Default (unset) 'auto' without a durable supervisor attestation degrades to 'off' so
118
+ // unsupervised foreground runs keep starting; explicit 'auto' stays fail-closed at assembly below.
119
+ const effectiveSelfUpdate = resolveEffectiveSelfUpdatePolicy(process.env);
120
+ selfUpdatePolicy = effectiveSelfUpdate.policy;
121
+ const recoverLifecycleBootstrap = !selfUpdateSupervisorAttested(process.env)
122
+ && lifecycleBootstrapStatePresent(process.env);
123
+ if (effectiveSelfUpdate.degraded || recoverLifecycleBootstrap) {
124
+ const bootstrap = await bootstrapDegradedSelfUpdateStartup(process.env, process.platform);
125
+ const bootstrapExitCode = writeBootstrapStartupResult(bootstrap);
126
+ if (bootstrapExitCode !== undefined)
127
+ process.exit(bootstrapExitCode);
128
+ }
87
129
  const proxyStartedAt = new Date().toISOString();
88
130
  publishLocalProxySettings = () => {
89
131
  if (!proxySettingsState.url)
@@ -102,7 +144,7 @@ export async function runProxyMain(options = {}) {
102
144
  const privateNodeCredentialStore = mode === 'private'
103
145
  ? new PrivateNodeCredentialStore(storePath)
104
146
  : undefined;
105
- const runtime = await connectHubRuntime({
147
+ const runtime = await (options.connectRuntime ?? connectHubRuntime)({
106
148
  mode,
107
149
  hubUrl,
108
150
  senderId,
@@ -134,6 +176,11 @@ export async function runProxyMain(options = {}) {
134
176
  throw new Error(`self_update_confirmation_failed:${confirmation.outcome}`);
135
177
  }
136
178
  }
179
+ publishLifecycleBootstrapReadiness({
180
+ env: process.env,
181
+ startedAt: proxyStartedAt,
182
+ ipcUrl: `http://127.0.0.1:${port}`,
183
+ });
137
184
  }
138
185
  catch (error) {
139
186
  if (recovery.outcome === 'pending_health') {
@@ -164,6 +211,7 @@ export async function runProxyMain(options = {}) {
164
211
  store: store,
165
212
  notifier: systemdNotifier,
166
213
  logger: process.stderr,
214
+ ...(options.runLoop ? { runLoop: options.runLoop } : {}),
167
215
  });
168
216
  }
169
217
  export async function recoverBoundDurableSelfUpdate(options) {
@@ -176,8 +224,12 @@ export async function recoverBoundDurableSelfUpdate(options) {
176
224
  }
177
225
  export function loadProxyEnvFile(env) {
178
226
  const supervisor = env['EVOLVER_SELF_UPDATE_SUPERVISOR'];
227
+ const lifecycleStateDir = env['EVOLVER_LIFECYCLE_STATE_DIR'];
179
228
  const stateDir = env['EVOLVER_SELF_UPDATE_STATE_DIR'];
180
229
  const targetPath = env['EVOLVER_SELF_UPDATE_TARGET_PATH'];
230
+ const bootstrapTransactionId = env[coreBootstrap.LIFECYCLE_BOOTSTRAP_TRANSACTION_ENV];
231
+ const recoveryControllerOwnerCapability = env[RECOVERY_CONTROLLER_LIFECYCLE_OWNER_CAPABILITY_ENV];
232
+ const recoveryChildStartGate = env[RECOVERY_CHILD_START_GATE_ENV];
181
233
  const systemRootBindings = Object.entries(env)
182
234
  .filter(([key]) => key.toLowerCase() === 'systemroot');
183
235
  const result = loadEnvFileFromEnv(env);
@@ -189,15 +241,37 @@ export function loadProxyEnvFile(env) {
189
241
  if (value !== undefined)
190
242
  env[key] = value;
191
243
  }
244
+ if (recoveryControllerOwnerCapability === undefined) {
245
+ delete env[RECOVERY_CONTROLLER_LIFECYCLE_OWNER_CAPABILITY_ENV];
246
+ }
247
+ else {
248
+ env[RECOVERY_CONTROLLER_LIFECYCLE_OWNER_CAPABILITY_ENV] =
249
+ recoveryControllerOwnerCapability;
250
+ }
251
+ if (recoveryChildStartGate === undefined) {
252
+ delete env[RECOVERY_CHILD_START_GATE_ENV];
253
+ }
254
+ else {
255
+ env[RECOVERY_CHILD_START_GATE_ENV] = recoveryChildStartGate;
256
+ }
192
257
  if (supervisor === undefined) {
193
258
  delete env['EVOLVER_SELF_UPDATE_SUPERVISOR'];
259
+ delete env[coreBootstrap.LIFECYCLE_BOOTSTRAP_TRANSACTION_ENV];
194
260
  }
195
261
  else {
196
262
  env['EVOLVER_SELF_UPDATE_SUPERVISOR'] = supervisor;
263
+ if (lifecycleStateDir !== undefined)
264
+ env['EVOLVER_LIFECYCLE_STATE_DIR'] = lifecycleStateDir;
197
265
  if (stateDir !== undefined)
198
266
  env['EVOLVER_SELF_UPDATE_STATE_DIR'] = stateDir;
199
267
  if (targetPath !== undefined)
200
268
  env['EVOLVER_SELF_UPDATE_TARGET_PATH'] = targetPath;
269
+ if (bootstrapTransactionId !== undefined) {
270
+ env[coreBootstrap.LIFECYCLE_BOOTSTRAP_TRANSACTION_ENV] = bootstrapTransactionId;
271
+ }
272
+ else {
273
+ delete env[coreBootstrap.LIFECYCLE_BOOTSTRAP_TRANSACTION_ENV];
274
+ }
201
275
  }
202
276
  return result;
203
277
  }
@@ -206,6 +280,11 @@ function proxyRecoveryEnvironment(env) {
206
280
  loadProxyEnvFile(recoveryEnv);
207
281
  return recoveryEnv;
208
282
  }
283
+ function unpinnedLegacySupervisorRequiresLifecycleState(env) {
284
+ return selfUpdateSupervisorAttested(env)
285
+ && !env['EVOLVER_LIFECYCLE_STATE_DIR']?.trim()
286
+ && Boolean(env['EVOLVER_ENV_FILE']?.trim());
287
+ }
209
288
  function finalizeRecoveryTelemetry(store, recovery) {
210
289
  try {
211
290
  finalizeSelfUpdateRecoveryLastUpdate(store, recovery);
@@ -303,6 +382,7 @@ export function proxyUsage(command = 'evolver-proxy') {
303
382
  '',
304
383
  'Useful options are configured through env or EVOLVER_ENV_FILE:',
305
384
  ' EVOLVER_IPC_PORT, EVOLVER_IPC_TOKEN, EVOLVER_PROXY_SETTINGS_FILE',
385
+ ' EVOLVER_NATIVE_PUBLISH_VERIFIER=1, EVOLVER_PUBLISH_VALIDATION_ROOT=<dir> (必须显式设置)',
306
386
  ' EVOLVER_SELF_UPDATE, EVOLVER_LLM_TRACE_CAPTURE_BODIES',
307
387
  '',
308
388
  ].join('\n');
@@ -369,27 +449,44 @@ function applyProxyCliPathOptions(options, env) {
369
449
  export async function runProxyCli(options = {}) {
370
450
  const argv = options.argv ?? process.argv.slice(2);
371
451
  const env = options.env ?? process.env;
452
+ const platform = options.platform ?? process.platform;
453
+ const processExecPath = options.processExecPath ?? process.execPath;
454
+ const startupGateRole = recoveryChildStartGateRole(argv);
455
+ let startupGateConsumed = false;
456
+ try {
457
+ startupGateConsumed = await (options.consumeChildStartGate ?? consumeRecoveryChildStartGate)(env, startupGateRole);
458
+ if (!startupGateConsumed
459
+ && (startupGateRole === 'windows-updater'
460
+ || env[RECOVERY_CONTROLLER_LIFECYCLE_OWNER_CAPABILITY_ENV] !== undefined)) {
461
+ throw new Error('self_update_recovery_child_start_gate_required');
462
+ }
463
+ }
464
+ catch (error) {
465
+ process.stderr.write(`[evolver-proxy] fatal: ${safeLoopErrorMessage(error)}\n`);
466
+ return 1;
467
+ }
372
468
  const unixControllerExitCode = await (options.runUnixRecoveryController ?? maybeRunUnixRecoveryController)({
373
469
  argv,
374
470
  env,
375
- platform: options.platform ?? process.platform,
376
- processExecPath: options.processExecPath ?? process.execPath,
471
+ platform,
472
+ processExecPath,
377
473
  });
378
474
  if (unixControllerExitCode !== undefined)
379
475
  return unixControllerExitCode;
380
476
  const windowsControllerExitCode = await (options.runWindowsRecoveryController ?? maybeRunWindowsRecoveryController)({
381
477
  argv,
382
478
  env,
383
- platform: options.platform ?? process.platform,
384
- processExecPath: options.processExecPath ?? process.execPath,
479
+ platform,
480
+ processExecPath,
385
481
  });
386
482
  if (windowsControllerExitCode !== undefined)
387
483
  return windowsControllerExitCode;
388
484
  const workerExitCode = await (options.runWindowsUpdaterWorker ?? maybeRunWindowsUpdaterWorkerFromArgv)({
389
485
  argv,
390
486
  env,
391
- platform: options.platform ?? process.platform,
392
- processExecPath: options.processExecPath ?? process.execPath,
487
+ platform,
488
+ processExecPath,
489
+ startupGateConsumed,
393
490
  });
394
491
  if (workerExitCode !== undefined)
395
492
  return workerExitCode;
@@ -403,9 +500,14 @@ export async function runProxyCli(options = {}) {
403
500
  if (cliOptions.envFile)
404
501
  env['EVOLVER_ENV_FILE'] = cliOptions.envFile;
405
502
  applyProxyCliPathOptions(cliOptions, env);
503
+ const requireLifecycleBootstrapState = unpinnedLegacySupervisorRequiresLifecycleState(env);
504
+ const recoveryEnvironment = proxyRecoveryEnvironment(env);
505
+ assertSupervisedLifecycleBootstrapState(recoveryEnvironment, {
506
+ requireLifecycleState: requireLifecycleBootstrapState,
507
+ });
406
508
  const recovery = options.recoverStartup || !options.runMain
407
509
  ? await (options.recoverStartup ?? recoverBoundDurableSelfUpdate)({
408
- env: proxyRecoveryEnvironment(env),
510
+ env: recoveryEnvironment,
409
511
  processExecPath: options.processExecPath ?? process.execPath,
410
512
  })
411
513
  : undefined;
@@ -420,6 +522,7 @@ export async function runProxyCli(options = {}) {
420
522
  }
421
523
  await (options.runMain ?? runProxyMain)({
422
524
  environmentPrepared: true,
525
+ requireLifecycleBootstrapState,
423
526
  ...(recovery ? { recoveryPrepared: recovery } : {}),
424
527
  });
425
528
  return 0;
@@ -432,6 +535,16 @@ export async function runProxyCli(options = {}) {
432
535
  uninstallUnhandledRejectionGuard();
433
536
  }
434
537
  }
538
+ function recoveryChildStartGateRole(argv) {
539
+ if (argv.length === 1 && argv[0] === 'proxy')
540
+ return 'proxy-target';
541
+ if (argv.length === 2
542
+ && argv[0] === 'proxy'
543
+ && argv[1] === WINDOWS_UPDATER_WORKER_ARG) {
544
+ return 'windows-updater';
545
+ }
546
+ return undefined;
547
+ }
435
548
  if (isDirectRun(import.meta.url, process.argv[1])) {
436
549
  void runProxyCli().then((exitCode) => {
437
550
  process.exitCode = exitCode;
@@ -544,6 +657,7 @@ export function createProxyDaemonDeps(options) {
544
657
  const env = options.env ?? process.env;
545
658
  const traceBackfill = resolveTraceBackfillConfig(env);
546
659
  const heartbeatIntervalMs = positiveIntegerEnv(env['HEARTBEAT_INTERVAL_MS']);
660
+ const publishExecutionVerifier = resolveNativePublishExecutionVerifier(env);
547
661
  return {
548
662
  hub: options.runtime.hub,
549
663
  ...(options.hubMode ? { hubMode: options.hubMode } : {}),
@@ -561,6 +675,57 @@ export function createProxyDaemonDeps(options) {
561
675
  ...(options.runtime.helloMode ? { helloMode: options.runtime.helloMode } : {}),
562
676
  ...(selfUpdate ? { selfUpdate } : {}),
563
677
  ...(traceBackfill ? { traceBackfill } : {}),
678
+ ...(publishExecutionVerifier ? { publishExecutionVerifier } : {}),
679
+ };
680
+ }
681
+ /**
682
+ * 仅在显式启用且具备完整 OS 隔离时接入本地发布验证器;默认仍保持 draft-only。
683
+ * 这样不会把普通桌面进程意外变成可发布执行器,也不会在 Windows/macOS 上静默降级为弱隔离。
684
+ */
685
+ function resolveNativePublishExecutionVerifier(env) {
686
+ if (env['EVOLVER_NATIVE_PUBLISH_VERIFIER']?.trim() !== '1')
687
+ return undefined;
688
+ // 原生发布验证器只能在完整的 OS 隔离可用时装配。Windows/macOS 目前没有
689
+ // 与 Linux namespace、只读文件系统和 cgroup 等价的实现,必须保持 draft-only,
690
+ // 不能先暴露一个运行时必然失败的“验证器”能力。
691
+ try {
692
+ if (!verify.readOnlyIsolationAvailable())
693
+ return undefined;
694
+ }
695
+ catch {
696
+ // 隔离探测本身失败也必须保持 fail-closed,而不能阻止代理启动。
697
+ return undefined;
698
+ }
699
+ // 发布验证必须绑定到调用方明确配置的项目根目录;回退到 daemon 当前目录会让验证对象与发布对象脱钩。
700
+ const configuredRoot = env['EVOLVER_PUBLISH_VALIDATION_ROOT']?.trim();
701
+ if (!configuredRoot)
702
+ return undefined;
703
+ const validationRoot = resolve(configuredRoot);
704
+ return async (input, signal) => {
705
+ const commands = Array.isArray(input.validation)
706
+ ? input.validation.filter((value) => typeof value === 'string').map((value) => value.trim()).filter(Boolean)
707
+ : [];
708
+ if (commands.length === 0
709
+ || commands.length > 8
710
+ || !Array.isArray(input.validation)
711
+ || input.validation.length !== commands.length
712
+ || commands.some((command) => command.length > 180 || !verify.isValidationCommandAllowed(command))
713
+ || signal.aborted)
714
+ return null;
715
+ const result = await verify.runSandboxedValidation(commands, validationRoot, {
716
+ requireIsolation: true,
717
+ signal,
718
+ });
719
+ if (signal.aborted || !result.passed || result.results.length !== commands.length)
720
+ return null;
721
+ return {
722
+ validation: commands,
723
+ trace: result.results.map((row) => ({
724
+ command: row.cmd,
725
+ exit: row.exitCode ?? 1,
726
+ ...(row.stdoutSummary ? { summary: row.stdoutSummary } : {}),
727
+ })),
728
+ };
564
729
  };
565
730
  }
566
731
  function positiveIntegerEnv(value) {
@@ -698,7 +863,7 @@ export function createSelfUpdateDeps(policy, currentVersion, env = process.env,
698
863
  if (!supervisorAttested && !selfUpdateSupervisorAttested(env)) {
699
864
  throw new Error('self_update_supervisor_required');
700
865
  }
701
- const publicKey = env['EVOLVER_SELF_UPDATE_PUBLIC_KEY']?.trim();
866
+ const publicKey = resolveSelfUpdatePublicKey(env);
702
867
  if (!publicKey)
703
868
  throw new Error('self_update_public_key_required');
704
869
  const releaseOpts = {
@@ -708,24 +873,77 @@ export function createSelfUpdateDeps(policy, currentVersion, env = process.env,
708
873
  requireSignedManifest: true,
709
874
  };
710
875
  const assertBound = () => assertSelfUpdateProcessTargetBound(releaseOpts);
876
+ let activeLifecycleLease;
877
+ const acquireLifecycleLease = () => {
878
+ assertBound();
879
+ if (activeLifecycleLease) {
880
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UPDATE_LOCKED, 'self_update_lifecycle_owner_lease_already_active');
881
+ }
882
+ let acquired;
883
+ try {
884
+ acquired = acquireLifecycleBootstrapOwnerLease(env);
885
+ }
886
+ catch (error) {
887
+ if (error instanceof util.LockTimeoutError) {
888
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UPDATE_LOCKED, 'self_update_lifecycle_owner_lock_busy', { cause: error });
889
+ }
890
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.RECOVERY_REQUIRED, `self_update_lifecycle_owner_lease_acquire_failed:${safeLoopErrorMessage(error)}`, { cause: error });
891
+ }
892
+ let released = false;
893
+ const lease = {
894
+ assertOwned: () => {
895
+ if (released || activeLifecycleLease !== lease) {
896
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.RECOVERY_REQUIRED, 'self_update_lifecycle_owner_lease_not_active');
897
+ }
898
+ assertBound();
899
+ try {
900
+ acquired.assertOwned();
901
+ }
902
+ catch (error) {
903
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.RECOVERY_REQUIRED, `self_update_lifecycle_owner_lease_lost:${safeLoopErrorMessage(error)}`, { cause: error });
904
+ }
905
+ },
906
+ release: () => {
907
+ if (released)
908
+ return;
909
+ try {
910
+ acquired.release();
911
+ }
912
+ finally {
913
+ released = true;
914
+ if (activeLifecycleLease === lease)
915
+ activeLifecycleLease = undefined;
916
+ }
917
+ },
918
+ };
919
+ activeLifecycleLease = lease;
920
+ return lease;
921
+ };
922
+ const assertOperationAllowed = () => {
923
+ if (!activeLifecycleLease) {
924
+ throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.RECOVERY_REQUIRED, 'self_update_lifecycle_owner_lease_required');
925
+ }
926
+ activeLifecycleLease.assertOwned();
927
+ };
711
928
  assertBound();
712
929
  return {
713
930
  policy,
714
931
  currentVersion,
932
+ acquireLifecycleLease,
715
933
  resolveManifest: (directive) => {
716
- assertBound();
934
+ assertOperationAllowed();
717
935
  return resolveGithubReleaseManifest(directive, releaseOpts);
718
936
  },
719
937
  download: (targetVersion, directive) => {
720
- assertBound();
938
+ assertOperationAllowed();
721
939
  return downloadGithubReleaseArtifact(targetVersion, directive, releaseOpts);
722
940
  },
723
941
  atomicReplace: (stagedPath) => {
724
- assertBound();
942
+ assertOperationAllowed();
725
943
  return atomicReplaceExecutable(stagedPath, releaseOpts);
726
944
  },
727
945
  beginTransaction: async (targetVersion) => {
728
- assertBound();
946
+ assertOperationAllowed();
729
947
  const transaction = await beginDurableSelfUpdate(targetVersion, {
730
948
  ...releaseOpts,
731
949
  currentVersion,
@@ -733,50 +951,33 @@ export function createSelfUpdateDeps(policy, currentVersion, env = process.env,
733
951
  });
734
952
  return {
735
953
  ...transaction,
954
+ adoptDownloaded: async (download) => {
955
+ assertOperationAllowed();
956
+ return transaction.adoptDownloaded(download);
957
+ },
958
+ markVerified: async (artifacts) => {
959
+ assertOperationAllowed();
960
+ await transaction.markVerified(artifacts);
961
+ },
736
962
  install: async () => {
737
- assertBound();
963
+ assertOperationAllowed();
738
964
  await transaction.install();
739
965
  },
966
+ markRestartRequested: async () => {
967
+ assertOperationAllowed();
968
+ await transaction.markRestartRequested();
969
+ },
740
970
  };
741
971
  },
742
- restart: restart ?? (() => { process.exit(78); }),
972
+ restart: () => {
973
+ assertOperationAllowed();
974
+ (restart ?? (() => { process.exit(78); }))();
975
+ },
743
976
  publicKey,
744
977
  };
745
978
  }
746
979
  export function assertSelfUpdateProcessTargetBound(options, allowUnresolvedTarget = false) {
747
- let targetPath;
748
- try {
749
- targetPath = resolveSelfUpdateTarget(options).path;
750
- }
751
- catch {
752
- if (allowUnresolvedTarget)
753
- return;
754
- throw new Error('self_update_process_target_mismatch');
755
- }
756
- const processExecPath = options.processExecPath ?? process.execPath;
757
- try {
758
- if (canonicalExecutablePath(processExecPath) !== canonicalExecutablePath(targetPath)) {
759
- throw new Error('self_update_process_target_mismatch');
760
- }
761
- }
762
- catch {
763
- throw new Error('self_update_process_target_mismatch');
764
- }
765
- }
766
- function canonicalExecutablePath(path) {
767
- const canonical = realpathSync.native(path);
768
- if (process.platform !== 'win32')
769
- return resolve(canonical);
770
- const withoutNamespace = canonical
771
- .replace(/^\\\\\?\\UNC\\/i, '\\\\')
772
- .replace(/^\\\\\?\\/i, '');
773
- return win32.normalize(withoutNamespace).toLowerCase();
774
- }
775
- function selfUpdateSupervisorAttested(env) {
776
- const supervisor = env['EVOLVER_SELF_UPDATE_SUPERVISOR']?.trim();
777
- return supervisor === 'systemd'
778
- || supervisor === 'launchd'
779
- || supervisor === 'windows-scheduled-task';
980
+ assertReleaseSelfUpdateProcessTargetBound(options, allowUnresolvedTarget);
780
981
  }
781
982
  export function resolvePublicNodeSecret(deps) {
782
983
  const explicit = resolveExplicitPublicNodeCredentials(process.env);
@@ -6,6 +6,7 @@ import type { AtpOrderConsentGate } from './atpConsent.js';
6
6
  import { type PublishRecallVerifierPort } from './publishRecallVerifier.js';
7
7
  type HubCapability = hubNs.HubCapability;
8
8
  type AssetStoreProvider = assetstore.AssetStoreProvider;
9
+ type ConversationDistillExecutionVerifier = (input: hubNs.ConversationDistillInput, signal: AbortSignal) => Promise<hubNs.ConversationDistillVerifiedExecution | null>;
9
10
  export declare const DEFAULT_IPC_PORT = 19820;
10
11
  export interface ProxyDaemonDeps {
11
12
  hub: HubCapability;
@@ -75,6 +76,12 @@ export interface ProxyDaemonDeps {
75
76
  publishRecallVerifier?: PublishRecallVerifierPort;
76
77
  /** Optional deterministic sanitizer environment for composition tests. Production omits this to scan process.env. */
77
78
  publishSanitizeEnv?: Record<string, string | undefined>;
79
+ /**
80
+ * 宿主专属的真实执行验证器。缺失时,conversation distill 只能返回草稿,绝不持久化或排队发布。
81
+ */
82
+ publishExecutionVerifier?: ConversationDistillExecutionVerifier;
83
+ /** 验证器响应的硬超时,防止发布请求因宿主执行器挂起而永久占用连接。 */
84
+ publishExecutionVerifierTimeoutMs?: number;
78
85
  }
79
86
  export interface ProxyTickReport {
80
87
  outbound: OutboundResult;
@@ -144,6 +151,8 @@ export declare class ProxyDaemon {
144
151
  private readonly collaborationFacade;
145
152
  private readonly publishRecallVerifier;
146
153
  private readonly proxyHandler;
154
+ private readonly hub;
155
+ private readonly recipeComposeStarted;
147
156
  private ipc;
148
157
  private readonly now;
149
158
  private readonly random;
@@ -178,6 +187,8 @@ export declare class ProxyDaemon {
178
187
  private scheduledForceUpdateKey;
179
188
  private traceBackfillDraining;
180
189
  private loopWakeHandler;
190
+ /** 守护进程停止时取消宿主验证。 */
191
+ private publishAbortController;
181
192
  constructor(deps: ProxyDaemonDeps);
182
193
  /**
183
194
  * core handler(确定性, 不经 agent): 目前只接 force_update(#108). 其他 core 类型(asset_publish_result/
@@ -231,6 +242,7 @@ export declare class ProxyDaemon {
231
242
  private cacheRemoteAssetSearch;
232
243
  private publishAssetSubmitSynchronously;
233
244
  private createSynchronousAssetSubmitEnvelope;
245
+ private composeRecipeAfterAcceptedSubmit;
234
246
  private currentHubMode;
235
247
  private handleHubModeBoundOutbound;
236
248
  private publishSynchronousBundle;