@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.
- package/dist/bin/evolver-proxy.d.ts +12 -0
- package/dist/bin/evolver-proxy.js +257 -56
- package/dist/daemon/proxyDaemon.d.ts +12 -0
- package/dist/daemon/proxyDaemon.js +313 -18
- package/dist/daemon/systemdNotifier.d.ts +2 -0
- package/dist/daemon/systemdNotifier.js +11 -1
- package/dist/llm/upstream.js +54 -7
- package/dist/private/accountAssetCompatibility.d.ts +1 -0
- package/dist/private/accountAssetCompatibility.js +3 -3
- package/dist/private/adapterLoader.js +4 -3
- package/dist/router/messagesRoute.js +9 -3
- package/dist/router/providerRoutes.js +7 -3
- package/dist/selfUpdate/bootstrap.d.ts +162 -0
- package/dist/selfUpdate/bootstrap.js +3524 -0
- package/dist/selfUpdate/bootstrapReadiness.d.ts +9 -0
- package/dist/selfUpdate/bootstrapReadiness.js +153 -0
- package/dist/selfUpdate/builtinKey.d.ts +4 -0
- package/dist/selfUpdate/builtinKey.js +16 -0
- package/dist/selfUpdate/controllerLifecycleAuthority.d.ts +45 -0
- package/dist/selfUpdate/controllerLifecycleAuthority.js +61 -0
- package/dist/selfUpdate/executor.d.ts +18 -7
- package/dist/selfUpdate/executor.js +159 -59
- package/dist/selfUpdate/failureCodes.d.ts +4 -0
- package/dist/selfUpdate/failureCodes.js +7 -0
- package/dist/selfUpdate/index.d.ts +2 -1
- package/dist/selfUpdate/index.js +2 -1
- package/dist/selfUpdate/migration.d.ts +158 -0
- package/dist/selfUpdate/migration.js +2672 -0
- package/dist/selfUpdate/policy.d.ts +19 -2
- package/dist/selfUpdate/policy.js +76 -2
- package/dist/selfUpdate/recoveryChildStartGate.d.ts +29 -0
- package/dist/selfUpdate/recoveryChildStartGate.js +319 -0
- package/dist/selfUpdate/releaseBinary.d.ts +3 -0
- package/dist/selfUpdate/releaseBinary.js +50 -4
- package/dist/selfUpdate/transaction.d.ts +8 -0
- package/dist/selfUpdate/transaction.js +166 -18
- package/dist/selfUpdate/unixController.d.ts +8 -0
- package/dist/selfUpdate/unixController.js +366 -38
- package/dist/selfUpdate/windowsController.d.ts +14 -2
- package/dist/selfUpdate/windowsController.js +484 -103
- package/dist/selfUpdate/windowsUpdater.d.ts +25 -0
- package/dist/selfUpdate/windowsUpdater.js +174 -7
- 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
|
|
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 {
|
|
24
|
-
import {
|
|
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:
|
|
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
|
-
|
|
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
|
|
376
|
-
processExecPath
|
|
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
|
|
384
|
-
processExecPath
|
|
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
|
|
392
|
-
processExecPath
|
|
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:
|
|
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
|
|
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
|
-
|
|
934
|
+
assertOperationAllowed();
|
|
717
935
|
return resolveGithubReleaseManifest(directive, releaseOpts);
|
|
718
936
|
},
|
|
719
937
|
download: (targetVersion, directive) => {
|
|
720
|
-
|
|
938
|
+
assertOperationAllowed();
|
|
721
939
|
return downloadGithubReleaseArtifact(targetVersion, directive, releaseOpts);
|
|
722
940
|
},
|
|
723
941
|
atomicReplace: (stagedPath) => {
|
|
724
|
-
|
|
942
|
+
assertOperationAllowed();
|
|
725
943
|
return atomicReplaceExecutable(stagedPath, releaseOpts);
|
|
726
944
|
},
|
|
727
945
|
beginTransaction: async (targetVersion) => {
|
|
728
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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;
|