@evomap/evolver-proxy 2.0.0-beta.2 → 2.0.0-beta.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/evolver-llm-proxy.js +0 -0
- package/dist/bin/evolver-proxy.d.ts +105 -7
- package/dist/bin/evolver-proxy.js +877 -121
- package/dist/daemon/atpConsent.js +5 -2
- package/dist/daemon/collaborationFacade.js +26 -16
- package/dist/daemon/proxyDaemon.d.ts +65 -0
- package/dist/daemon/proxyDaemon.js +1384 -29
- package/dist/daemon/publishRecallVerifier.d.ts +114 -0
- package/dist/daemon/publishRecallVerifier.js +495 -0
- package/dist/daemon/selectHub.js +5 -3
- package/dist/daemon/systemdNotifier.d.ts +48 -0
- package/dist/daemon/systemdNotifier.js +163 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/lifecycle/claimNudge.d.ts +20 -0
- package/dist/lifecycle/claimNudge.js +124 -0
- package/dist/lifecycle/legacyNodeId.d.ts +11 -13
- package/dist/lifecycle/legacyNodeId.js +35 -20
- package/dist/lifecycle/manager.d.ts +4 -0
- package/dist/lifecycle/manager.js +15 -2
- package/dist/llm/server.js +24 -4
- package/dist/llm/traceControl.js +1 -1
- package/dist/llm/upstream.d.ts +5 -1
- package/dist/llm/upstream.js +72 -2
- package/dist/private/accountAssetCompatibility.d.ts +29 -0
- package/dist/private/accountAssetCompatibility.js +196 -0
- package/dist/private/adapterLoader.d.ts +21 -1
- package/dist/private/adapterLoader.js +242 -7
- package/dist/private/nodeCredentialStore.d.ts +23 -0
- package/dist/private/nodeCredentialStore.js +210 -0
- 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 +27 -11
- package/dist/selfUpdate/executor.js +233 -58
- package/dist/selfUpdate/failureCodes.d.ts +10 -0
- package/dist/selfUpdate/failureCodes.js +13 -0
- package/dist/selfUpdate/index.d.ts +5 -1
- package/dist/selfUpdate/index.js +5 -1
- package/dist/selfUpdate/lastUpdate.d.ts +3 -1
- package/dist/selfUpdate/lastUpdate.js +37 -6
- 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 +13 -0
- package/dist/selfUpdate/releaseBinary.js +93 -10
- package/dist/selfUpdate/transaction.d.ts +117 -0
- package/dist/selfUpdate/transaction.js +1322 -0
- package/dist/selfUpdate/unixController.d.ts +23 -0
- package/dist/selfUpdate/unixController.js +514 -0
- package/dist/selfUpdate/version.d.ts +6 -2
- package/dist/selfUpdate/version.js +5 -3
- package/dist/selfUpdate/windowsController.d.ts +35 -0
- package/dist/selfUpdate/windowsController.js +655 -0
- package/dist/selfUpdate/windowsUpdater.d.ts +104 -0
- package/dist/selfUpdate/windowsUpdater.js +882 -0
- package/dist/sync/engine.d.ts +12 -0
- package/dist/sync/engine.js +255 -64
- package/package.json +10 -3
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { util } from '@evomap/evolver-core';
|
|
2
|
+
export declare function publishLifecycleBootstrapReadiness(input: {
|
|
3
|
+
env: NodeJS.ProcessEnv;
|
|
4
|
+
pid?: number;
|
|
5
|
+
supervisorPid?: number;
|
|
6
|
+
readProcessStartIdentity?: typeof util.readFileLockProcessStartIdentity;
|
|
7
|
+
startedAt: string;
|
|
8
|
+
ipcUrl: string;
|
|
9
|
+
}): void;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { closeSync, constants, fchmodSync, fstatSync, fsyncSync, lstatSync, openSync, readSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
|
|
3
|
+
import { basename, dirname, join } from 'node:path';
|
|
4
|
+
import { bootstrap as coreBootstrap, util } from '@evomap/evolver-core';
|
|
5
|
+
import { resolveBootstrapStateDir } from './bootstrap.js';
|
|
6
|
+
const MAX_READINESS_BYTES = 16 * 1024;
|
|
7
|
+
function isErrno(error, code) {
|
|
8
|
+
return typeof error === 'object' && error !== null && error.code === code;
|
|
9
|
+
}
|
|
10
|
+
function syncDirectory(path) {
|
|
11
|
+
let descriptor;
|
|
12
|
+
try {
|
|
13
|
+
descriptor = openSync(path, constants.O_RDONLY);
|
|
14
|
+
fsyncSync(descriptor);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (isErrno(error, 'EINVAL'))
|
|
18
|
+
return;
|
|
19
|
+
if (process.platform === 'win32' && (isErrno(error, 'EPERM') || isErrno(error, 'EACCES')))
|
|
20
|
+
return;
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
if (descriptor !== undefined)
|
|
25
|
+
closeSync(descriptor);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function readExistingReadiness(path) {
|
|
29
|
+
try {
|
|
30
|
+
const before = lstatSync(path, { bigint: true });
|
|
31
|
+
if (!before.isFile() || before.isSymbolicLink() || before.size > BigInt(MAX_READINESS_BYTES)
|
|
32
|
+
|| before.dev <= 0n || before.ino <= 0n) {
|
|
33
|
+
throw new Error('bootstrap readiness path is not a bounded regular file');
|
|
34
|
+
}
|
|
35
|
+
const descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
36
|
+
try {
|
|
37
|
+
const opened = fstatSync(descriptor, { bigint: true });
|
|
38
|
+
if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino) {
|
|
39
|
+
throw new Error('bootstrap readiness path changed while opening');
|
|
40
|
+
}
|
|
41
|
+
const bytes = Buffer.alloc(MAX_READINESS_BYTES + 1);
|
|
42
|
+
let offset = 0;
|
|
43
|
+
while (offset < bytes.length) {
|
|
44
|
+
const count = readSync(descriptor, bytes, offset, bytes.length - offset, null);
|
|
45
|
+
if (count === 0)
|
|
46
|
+
break;
|
|
47
|
+
offset += count;
|
|
48
|
+
}
|
|
49
|
+
if (offset > MAX_READINESS_BYTES) {
|
|
50
|
+
throw new Error('bootstrap readiness path is not a bounded regular file');
|
|
51
|
+
}
|
|
52
|
+
const raw = bytes.subarray(0, offset).toString('utf8');
|
|
53
|
+
const after = fstatSync(descriptor, { bigint: true });
|
|
54
|
+
if (after.dev !== opened.dev || after.ino !== opened.ino || after.size !== opened.size
|
|
55
|
+
|| after.mtimeNs !== opened.mtimeNs || after.ctimeNs !== opened.ctimeNs) {
|
|
56
|
+
throw new Error('bootstrap readiness path changed while reading');
|
|
57
|
+
}
|
|
58
|
+
const parsed = coreBootstrap.parseLifecycleBootstrapReadinessJson(raw);
|
|
59
|
+
if (!parsed)
|
|
60
|
+
throw new Error('bootstrap readiness receipt is corrupt');
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
closeSync(descriptor);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (isErrno(error, 'ENOENT'))
|
|
69
|
+
return undefined;
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function publishLifecycleBootstrapReadiness(input) {
|
|
74
|
+
const transactionId = input.env[coreBootstrap.LIFECYCLE_BOOTSTRAP_TRANSACTION_ENV]?.trim();
|
|
75
|
+
if (!transactionId)
|
|
76
|
+
return;
|
|
77
|
+
const pid = input.pid ?? process.pid;
|
|
78
|
+
const supervisorPid = input.supervisorPid ?? process.ppid;
|
|
79
|
+
const readProcessStartIdentity = input.readProcessStartIdentity
|
|
80
|
+
?? util.readFileLockProcessStartIdentity;
|
|
81
|
+
const pidProcessStartIdentity = readProcessStartIdentity(pid);
|
|
82
|
+
const supervisorProcessStartIdentity = readProcessStartIdentity(supervisorPid);
|
|
83
|
+
if (!pidProcessStartIdentity || !supervisorProcessStartIdentity) {
|
|
84
|
+
throw new Error('lifecycle bootstrap readiness process identity is unavailable');
|
|
85
|
+
}
|
|
86
|
+
const readiness = {
|
|
87
|
+
schema: coreBootstrap.LIFECYCLE_BOOTSTRAP_READINESS_SCHEMA,
|
|
88
|
+
transactionId,
|
|
89
|
+
pid,
|
|
90
|
+
pidProcessStartIdentity,
|
|
91
|
+
supervisorPid,
|
|
92
|
+
supervisorProcessStartIdentity,
|
|
93
|
+
startedAt: input.startedAt,
|
|
94
|
+
ipcUrl: input.ipcUrl,
|
|
95
|
+
};
|
|
96
|
+
if (!coreBootstrap.parseLifecycleBootstrapReadiness(readiness)) {
|
|
97
|
+
throw new Error('invalid lifecycle bootstrap readiness receipt');
|
|
98
|
+
}
|
|
99
|
+
const stateDir = resolveBootstrapStateDir(input.env);
|
|
100
|
+
const state = lstatSync(stateDir);
|
|
101
|
+
if (!state.isDirectory() || state.isSymbolicLink()) {
|
|
102
|
+
throw new Error('lifecycle bootstrap state directory is not trusted');
|
|
103
|
+
}
|
|
104
|
+
if (process.platform !== 'win32') {
|
|
105
|
+
const uid = typeof process.getuid === 'function' ? process.getuid() : undefined;
|
|
106
|
+
if ((uid !== undefined && state.uid !== uid) || (state.mode & 0o077) !== 0) {
|
|
107
|
+
throw new Error('lifecycle bootstrap state directory is not owner-only');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const path = join(stateDir, coreBootstrap.LIFECYCLE_BOOTSTRAP_READINESS_FILE);
|
|
111
|
+
const lockPath = join(stateDir, coreBootstrap.LIFECYCLE_BOOTSTRAP_READINESS_LOCK_FILE);
|
|
112
|
+
util.acquireLock(lockPath, { maxTries: 500, waitMs: 10 });
|
|
113
|
+
let publicationError;
|
|
114
|
+
let publicationFailed = false;
|
|
115
|
+
try {
|
|
116
|
+
const existing = readExistingReadiness(path);
|
|
117
|
+
if (existing && existing.transactionId !== transactionId) {
|
|
118
|
+
throw new Error('lifecycle bootstrap readiness is owned by another transaction');
|
|
119
|
+
}
|
|
120
|
+
const temporary = join(dirname(path), `.${basename(path)}.${process.pid}.${randomUUID()}.tmp`);
|
|
121
|
+
let descriptor;
|
|
122
|
+
try {
|
|
123
|
+
descriptor = openSync(temporary, 'wx', 0o600);
|
|
124
|
+
writeFileSync(descriptor, `${JSON.stringify(readiness)}\n`, { encoding: 'utf8' });
|
|
125
|
+
fchmodSync(descriptor, 0o600);
|
|
126
|
+
fsyncSync(descriptor);
|
|
127
|
+
closeSync(descriptor);
|
|
128
|
+
descriptor = undefined;
|
|
129
|
+
renameSync(temporary, path);
|
|
130
|
+
syncDirectory(stateDir);
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
if (descriptor !== undefined)
|
|
134
|
+
closeSync(descriptor);
|
|
135
|
+
rmSync(temporary, { force: true });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
publicationError = error;
|
|
140
|
+
publicationFailed = true;
|
|
141
|
+
}
|
|
142
|
+
const released = util.releaseLock(lockPath);
|
|
143
|
+
if (!released.released
|
|
144
|
+
|| (released.reason !== 'released' && released.reason !== 'released_with_cleanup_error')) {
|
|
145
|
+
throw new Error(`lifecycle bootstrap readiness lock release failed: ${released.reason}`, {
|
|
146
|
+
cause: publicationFailed
|
|
147
|
+
? new AggregateError([publicationError, new Error(released.reason)])
|
|
148
|
+
: undefined,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (publicationFailed)
|
|
152
|
+
throw publicationError;
|
|
153
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { util } from '@evomap/evolver-core';
|
|
2
|
+
import { type PreparedRecoveryChildStartGate, type RecoveryChildStartGateRole } from './recoveryChildStartGate.js';
|
|
3
|
+
interface RecoveryControllerLifecycleLease {
|
|
4
|
+
readonly owner: util.FileLockOwnerRecord;
|
|
5
|
+
assertOwned(): void;
|
|
6
|
+
armProcess(pid: number): util.FileLockOwnerRecord;
|
|
7
|
+
disarmProcess(): void;
|
|
8
|
+
retainProcess(): void;
|
|
9
|
+
transferToProcess(pid: number): util.FileLockOwnerRecord;
|
|
10
|
+
release(): void;
|
|
11
|
+
}
|
|
12
|
+
type RecoveryControllerLifecycleLeaseAcquirer = (env: NodeJS.ProcessEnv, options: {
|
|
13
|
+
maxTries: number;
|
|
14
|
+
waitMs: number;
|
|
15
|
+
}) => RecoveryControllerLifecycleLease;
|
|
16
|
+
type SupervisedActivationDelegationAssertion = (env: NodeJS.ProcessEnv) => void;
|
|
17
|
+
interface RecoveryControllerPreparedOwnerCapability {
|
|
18
|
+
env: NodeJS.ProcessEnv;
|
|
19
|
+
startupAckToken: string;
|
|
20
|
+
}
|
|
21
|
+
interface RecoveryControllerPreparedChild {
|
|
22
|
+
env: NodeJS.ProcessEnv;
|
|
23
|
+
startupAckToken?: string;
|
|
24
|
+
startupGateToken: string;
|
|
25
|
+
}
|
|
26
|
+
type RecoveryControllerChildCapabilityFactory = (env: NodeJS.ProcessEnv, owner: util.FileLockOwnerRecord) => RecoveryControllerPreparedOwnerCapability;
|
|
27
|
+
type RecoveryControllerStartGateFactory = (env: NodeJS.ProcessEnv, role: RecoveryChildStartGateRole, expectedParent?: Pick<util.FileLockOwnerRecord, 'pid' | 'processStartIdentity'>) => PreparedRecoveryChildStartGate;
|
|
28
|
+
export interface RecoveryControllerAuthorityDependencies {
|
|
29
|
+
acquireOwnerLease?: RecoveryControllerLifecycleLeaseAcquirer;
|
|
30
|
+
assertActivationDelegation?: SupervisedActivationDelegationAssertion;
|
|
31
|
+
prepareOwnerCapability?: RecoveryControllerChildCapabilityFactory;
|
|
32
|
+
prepareStartGate?: RecoveryControllerStartGateFactory;
|
|
33
|
+
}
|
|
34
|
+
export interface RecoveryControllerAuthority {
|
|
35
|
+
readonly kind: 'owned' | 'delegated';
|
|
36
|
+
assertAuthorized(): void;
|
|
37
|
+
prepareTarget(env: NodeJS.ProcessEnv): RecoveryControllerPreparedChild;
|
|
38
|
+
prepareWorker(env: NodeJS.ProcessEnv): PreparedRecoveryChildStartGate;
|
|
39
|
+
armProcess(pid: number): void;
|
|
40
|
+
disarmProcess(): void;
|
|
41
|
+
retainProcess(): void;
|
|
42
|
+
release(): void;
|
|
43
|
+
}
|
|
44
|
+
export declare function resolveRecoveryControllerAuthority(env: NodeJS.ProcessEnv, dependencies?: RecoveryControllerAuthorityDependencies): Promise<RecoveryControllerAuthority>;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { prepareRecoveryChildStartGate, } from './recoveryChildStartGate.js';
|
|
2
|
+
export async function resolveRecoveryControllerAuthority(env, dependencies = {}) {
|
|
3
|
+
const bootstrap = dependencies.acquireOwnerLease
|
|
4
|
+
&& dependencies.assertActivationDelegation
|
|
5
|
+
&& dependencies.prepareOwnerCapability
|
|
6
|
+
? undefined
|
|
7
|
+
: await import('./bootstrap.js');
|
|
8
|
+
const acquireOwnerLease = dependencies.acquireOwnerLease
|
|
9
|
+
?? bootstrap.acquireLifecycleBootstrapOwnerLease;
|
|
10
|
+
const assertActivationDelegation = dependencies.assertActivationDelegation
|
|
11
|
+
?? bootstrap.assertActiveSupervisedLifecycleBootstrapDelegation;
|
|
12
|
+
const prepareOwnerCapability = dependencies.prepareOwnerCapability
|
|
13
|
+
?? bootstrap.prepareRecoveryControllerLifecycleOwnerCapability;
|
|
14
|
+
const prepareStartGate = dependencies.prepareStartGate ?? prepareRecoveryChildStartGate;
|
|
15
|
+
try {
|
|
16
|
+
const lease = acquireOwnerLease(env, { maxTries: 2, waitMs: 0 });
|
|
17
|
+
return {
|
|
18
|
+
kind: 'owned',
|
|
19
|
+
assertAuthorized: () => { lease.assertOwned(); },
|
|
20
|
+
prepareTarget: (childEnv) => {
|
|
21
|
+
lease.assertOwned();
|
|
22
|
+
const ownerCapability = prepareOwnerCapability(childEnv, lease.owner);
|
|
23
|
+
const startGate = prepareStartGate(ownerCapability.env, 'proxy-target', lease.owner);
|
|
24
|
+
return { ...ownerCapability, ...startGate };
|
|
25
|
+
},
|
|
26
|
+
prepareWorker: (childEnv) => {
|
|
27
|
+
lease.assertOwned();
|
|
28
|
+
return prepareStartGate(childEnv, 'windows-updater', lease.owner);
|
|
29
|
+
},
|
|
30
|
+
armProcess: (pid) => { lease.armProcess(pid); },
|
|
31
|
+
disarmProcess: () => { lease.disarmProcess(); },
|
|
32
|
+
retainProcess: () => { lease.retainProcess(); },
|
|
33
|
+
release: () => { lease.release(); },
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch (leaseError) {
|
|
37
|
+
try {
|
|
38
|
+
assertActivationDelegation(env);
|
|
39
|
+
}
|
|
40
|
+
catch (delegationError) {
|
|
41
|
+
throw new AggregateError([leaseError, delegationError], 'self_update_recovery_controller_authority_unavailable');
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
kind: 'delegated',
|
|
45
|
+
assertAuthorized: () => { assertActivationDelegation(env); },
|
|
46
|
+
prepareTarget: (childEnv) => {
|
|
47
|
+
assertActivationDelegation(env);
|
|
48
|
+
return prepareStartGate(childEnv, 'proxy-target');
|
|
49
|
+
},
|
|
50
|
+
prepareWorker: () => {
|
|
51
|
+
throw new Error('self_update_recovery_controller_owner_lease_required');
|
|
52
|
+
},
|
|
53
|
+
armProcess: () => {
|
|
54
|
+
throw new Error('self_update_recovery_controller_delegated_guardian_unavailable');
|
|
55
|
+
},
|
|
56
|
+
disarmProcess: () => { },
|
|
57
|
+
retainProcess: () => { },
|
|
58
|
+
release: () => { },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { ops } from '@evomap/evolver-core';
|
|
2
2
|
import { type SelfUpdateFailureCode } from './failureCodes.js';
|
|
3
|
+
import type { DurableSelfUpdateSession } from './transaction.js';
|
|
3
4
|
type DownloadedArtifact = ops.DownloadedArtifact;
|
|
4
5
|
/** Structured outcome codes — reported to telemetry; the hub sees WHY an update did/didn't apply. */
|
|
5
|
-
export type SelfUpdateOutcome = 'applied' | 'noop' | 'rejected_decision' | 'rejected_verification' | 'download_failed' | 'replace_failed' | 'already_in_progress' | 'disabled';
|
|
6
|
+
export type SelfUpdateOutcome = 'applied' | 'noop' | 'rejected_decision' | 'rejected_verification' | 'download_failed' | 'replace_failed' | 'restart_failed' | 'rollback_failed' | 'already_in_progress' | 'disabled';
|
|
6
7
|
export interface SelfUpdateResult {
|
|
7
8
|
outcome: SelfUpdateOutcome;
|
|
8
9
|
reason: string;
|
|
@@ -13,13 +14,17 @@ export interface SelfUpdateResult {
|
|
|
13
14
|
/**
|
|
14
15
|
* Which download path produced the staged binary: `'binary'` is the normal
|
|
15
16
|
* precompiled-asset happy path, `'tarball'` means the binary download failed
|
|
16
|
-
* and Channel 1b (release `.tar.gz`) fallback was used. The
|
|
17
|
+
* and Channel 1b (release `.tar.gz`) fallback was used. The selected-artifact
|
|
17
18
|
* gate ran in BOTH cases, so apply-semantics are identical, but "tarball
|
|
18
19
|
* used in production" is a useful CDN/rate-limit signal for the hub.
|
|
19
20
|
* Persisted into `last_update.json` (lastUpdate.LastUpdatePayload.applied_via)
|
|
20
21
|
* on success so the hub can observe the channel directly.
|
|
21
22
|
*/
|
|
22
23
|
appliedVia?: 'binary' | 'tarball';
|
|
24
|
+
/** Durable installs are not successful until the relaunched daemon completes startup health checks. */
|
|
25
|
+
confirmationPending?: true;
|
|
26
|
+
/** A bounded integrity warning from cleanup that did not change the primary update outcome. */
|
|
27
|
+
cleanupWarning?: string;
|
|
23
28
|
}
|
|
24
29
|
/** The hub's force_update directive (inbound message payload). */
|
|
25
30
|
export interface ForceUpdateDirective {
|
|
@@ -36,7 +41,7 @@ export interface ForceUpdateDirective {
|
|
|
36
41
|
export interface DownloadResult {
|
|
37
42
|
/** Where the new version was staged (e.g. a tmp dir). Passed to atomicReplace on success. */
|
|
38
43
|
stagedPath: string;
|
|
39
|
-
/**
|
|
44
|
+
/** Exactly one selected artifact (bytes or precomputed sha256) for verification. */
|
|
40
45
|
artifacts: readonly DownloadedArtifact[];
|
|
41
46
|
/**
|
|
42
47
|
* Which channel actually produced the staged bytes — defaults to `'binary'`
|
|
@@ -57,31 +62,42 @@ export interface SelfUpdateDeps {
|
|
|
57
62
|
policy: 'off' | 'prompt' | 'auto';
|
|
58
63
|
/** Current installed version (read from package.json by the caller). */
|
|
59
64
|
currentVersion: string;
|
|
65
|
+
/** Lazy cross-process lease that serializes self-update with lifecycle mutations. */
|
|
66
|
+
acquireLifecycleLease?: () => Promise<SelfUpdateLifecycleLease> | SelfUpdateLifecycleLease;
|
|
60
67
|
/** Resolve a trusted manifest when the hub directive does not carry one. */
|
|
61
68
|
resolveManifest?: (directive: ForceUpdateDirective, currentVersion: string) => Promise<unknown> | unknown;
|
|
62
69
|
/** Download the staged release for the target version. Throws/rejects on failure. */
|
|
63
70
|
download: (targetVersion: string, directive: ForceUpdateDirective) => Promise<DownloadResult>;
|
|
64
71
|
/** Atomically replace the install tree with the staged path (preserving node_modules/.env/etc). Throws on fail. */
|
|
65
72
|
atomicReplace: (stagedPath: string) => Promise<void>;
|
|
73
|
+
/** Optional durable transaction: cross-process lock + journal + backup + recovery-aware install. */
|
|
74
|
+
beginTransaction?: (targetVersion: string) => Promise<DurableSelfUpdateSession>;
|
|
66
75
|
/** Signal a restart so the supervisor relaunches the new version. v1 convention: process.exit(78). */
|
|
67
|
-
restart: () => void
|
|
76
|
+
restart: () => void | Promise<void>;
|
|
68
77
|
/** Optional Ed25519 public key (PEM / raw base64). When set, an unsigned/badly-signed manifest is REJECTED. */
|
|
69
78
|
publicKey?: string;
|
|
70
79
|
/** Best-effort telemetry sink for the structured outcome (never throws into the update path). */
|
|
71
80
|
onTelemetry?: (result: SelfUpdateResult) => void;
|
|
81
|
+
/** One-shot operator warning sink for a lifecycle lease cleanup integrity failure. */
|
|
82
|
+
onCleanupWarning?: (warning: string, result: SelfUpdateResult) => void;
|
|
83
|
+
}
|
|
84
|
+
export interface SelfUpdateLifecycleLease {
|
|
85
|
+
assertOwned(): Promise<void> | void;
|
|
86
|
+
release(): Promise<void> | void;
|
|
72
87
|
}
|
|
73
88
|
/** Test-only: reset the mutex between cases. Not part of the public update path. */
|
|
74
89
|
export declare function _resetSelfUpdateMutex(): void;
|
|
75
90
|
/**
|
|
76
|
-
* Execute a force_update directive end to end:
|
|
91
|
+
* Execute a force_update directive end to end: fast exits → lease → resolve/decide → download → VERIFY → replace → restart.
|
|
77
92
|
*
|
|
78
93
|
* Order is load-bearing:
|
|
79
|
-
* 1. policy off → do nothing (
|
|
80
|
-
* 2.
|
|
81
|
-
* 3. mutex: exactly one
|
|
82
|
-
* 4.
|
|
83
|
-
* 5.
|
|
84
|
-
* 6.
|
|
94
|
+
* 1. policy off → do nothing (explicit opt-out; auto is hard-gated upstream by supervisor + public key).
|
|
95
|
+
* 2. reject malformed/already-satisfied required versions without acquiring the lifecycle owner lease.
|
|
96
|
+
* 3. mutex + lifecycle owner lease: exactly one executor may resolve or mutate release state.
|
|
97
|
+
* 4. resolve and decideUpdate (pure): reject bad manifests or NOOP under the held lease.
|
|
98
|
+
* 5. download the staged release.
|
|
99
|
+
* 6. verifySelectedManifestArtifact (pure) — THE GATE. Fail → no write, no restart.
|
|
100
|
+
* 7. atomicReplace, then restart(). Only reached after verification passed.
|
|
85
101
|
*
|
|
86
102
|
* Never throws: every failure becomes a structured SelfUpdateResult so the daemon can report it and keep running
|
|
87
103
|
* on the old (intact) version.
|