@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import { execFile } from 'node:child_process';
|
|
3
3
|
import { constants } from 'node:fs';
|
|
4
|
-
import { chmod, lstat, mkdir, open, realpath, rename, rm, writeFile, } from 'node:fs/promises';
|
|
4
|
+
import { chmod, link, lstat, mkdir, open, realpath, rename, rm, writeFile, } from 'node:fs/promises';
|
|
5
5
|
import { basename, dirname, isAbsolute, join, resolve, win32 } from 'node:path';
|
|
6
6
|
import { promisify } from 'node:util';
|
|
7
7
|
import { ops } from '@evomap/evolver-core';
|
|
@@ -17,6 +17,32 @@ const UNIX_CONTROLLER_DIRECTORY = 'unix-controller';
|
|
|
17
17
|
const UNIX_CONTROLLER_NAME = 'evolver-recovery-controller';
|
|
18
18
|
const WINDOWS_CONTROLLER_DIRECTORY = 'windows-controller';
|
|
19
19
|
const WINDOWS_CONTROLLER_NAME = 'evolver-recovery-controller.exe';
|
|
20
|
+
function bootstrapClaimOwnershipReceiptPath(claimPath) {
|
|
21
|
+
const canonical = resolve(claimPath);
|
|
22
|
+
const claimName = basename(canonical);
|
|
23
|
+
if (!/\.bootstrap-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.claim$/i
|
|
24
|
+
.test(claimName)) {
|
|
25
|
+
throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, 'bootstrap_controller_claim_has_no_transaction_owner');
|
|
26
|
+
}
|
|
27
|
+
return join(dirname(canonical), `${claimName.slice(0, -'.claim'.length)}.rollback`);
|
|
28
|
+
}
|
|
29
|
+
async function syncDirectoryDurably(path) {
|
|
30
|
+
let handle;
|
|
31
|
+
try {
|
|
32
|
+
handle = await open(path, constants.O_RDONLY);
|
|
33
|
+
await handle.sync();
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (isErrno(error, 'EINVAL'))
|
|
37
|
+
return;
|
|
38
|
+
if (process.platform === 'win32' && (isErrno(error, 'EPERM') || isErrno(error, 'EACCES')))
|
|
39
|
+
return;
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
await handle?.close().catch(() => { });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
20
46
|
export async function inspectDurableSelfUpdate(options) {
|
|
21
47
|
const paths = await resolveTransactionPaths(options, false);
|
|
22
48
|
if (!paths)
|
|
@@ -52,7 +78,18 @@ export async function provisionStableUnixRecoveryController(options) {
|
|
|
52
78
|
const owner = await acquireLock(paths.lock, options.pid ?? process.pid);
|
|
53
79
|
const controllerDirectory = join(paths.root, UNIX_CONTROLLER_DIRECTORY);
|
|
54
80
|
const controllerPath = join(controllerDirectory, UNIX_CONTROLLER_NAME);
|
|
55
|
-
const temporaryPath =
|
|
81
|
+
const temporaryPath = options.artifactClaimPath
|
|
82
|
+
?? join(controllerDirectory, `.${UNIX_CONTROLLER_NAME}.${randomBytes(8).toString('hex')}.tmp`);
|
|
83
|
+
if (dirname(resolve(temporaryPath)) !== resolve(controllerDirectory)) {
|
|
84
|
+
throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, 'unix_controller_claim_outside_directory');
|
|
85
|
+
}
|
|
86
|
+
const ownershipReceiptPath = options.replaceExisting === false && options.artifactClaimPath !== undefined
|
|
87
|
+
? bootstrapClaimOwnershipReceiptPath(temporaryPath)
|
|
88
|
+
: undefined;
|
|
89
|
+
if (ownershipReceiptPath && !options.onArtifactPublished) {
|
|
90
|
+
throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, 'bootstrap_controller_claim_has_no_durable_owner_callback');
|
|
91
|
+
}
|
|
92
|
+
let retainClaimOnFailure = options.replaceExisting === false && options.artifactClaimPath !== undefined;
|
|
56
93
|
try {
|
|
57
94
|
const loadedJournal = await readJournal(paths.journal);
|
|
58
95
|
if (loadedJournal) {
|
|
@@ -63,16 +100,50 @@ export async function provisionStableUnixRecoveryController(options) {
|
|
|
63
100
|
}
|
|
64
101
|
await ensureSecureDirectory(paths.root);
|
|
65
102
|
await ensureSecureDirectory(controllerDirectory);
|
|
103
|
+
if (options.replaceExisting === false) {
|
|
104
|
+
try {
|
|
105
|
+
await lstat(controllerPath);
|
|
106
|
+
throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, `bootstrap_controller_already_exists:${controllerPath}`);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
if (!isErrno(error, 'ENOENT'))
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
66
113
|
const targetIdentity = await assertRegularNonSymlink(paths.targetPath, 'target');
|
|
67
114
|
const targetBytes = await readRegularFile(paths.targetPath);
|
|
68
|
-
await writeExclusiveFile(temporaryPath, targetBytes, 0o700
|
|
115
|
+
await writeExclusiveFile(temporaryPath, targetBytes, 0o700, ownershipReceiptPath
|
|
116
|
+
? {
|
|
117
|
+
ownershipReceiptPath,
|
|
118
|
+
onClaimCreated: () => options.onArtifactPublished(controllerPath, temporaryPath),
|
|
119
|
+
}
|
|
120
|
+
: undefined);
|
|
69
121
|
await assertSameFileIdentity(paths.targetPath, targetIdentity);
|
|
70
|
-
|
|
122
|
+
if (options.replaceExisting === false) {
|
|
123
|
+
await link(temporaryPath, controllerPath);
|
|
124
|
+
await syncDirectoryDurably(controllerDirectory);
|
|
125
|
+
await options.onArtifactPublished?.(controllerPath, temporaryPath);
|
|
126
|
+
if (ownershipReceiptPath) {
|
|
127
|
+
await rm(ownershipReceiptPath, { force: true });
|
|
128
|
+
await syncDirectoryDurably(controllerDirectory);
|
|
129
|
+
}
|
|
130
|
+
await rm(temporaryPath, { force: true });
|
|
131
|
+
await syncDirectoryDurably(controllerDirectory);
|
|
132
|
+
retainClaimOnFailure = false;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
await rename(temporaryPath, controllerPath);
|
|
136
|
+
await syncDirectoryDurably(controllerDirectory);
|
|
137
|
+
}
|
|
71
138
|
await chmod(controllerPath, 0o700);
|
|
72
139
|
return controllerPath;
|
|
73
140
|
}
|
|
74
141
|
finally {
|
|
75
|
-
|
|
142
|
+
if (!retainClaimOnFailure) {
|
|
143
|
+
if (ownershipReceiptPath)
|
|
144
|
+
await rm(ownershipReceiptPath, { force: true }).catch(() => { });
|
|
145
|
+
await rm(temporaryPath, { force: true }).catch(() => { });
|
|
146
|
+
}
|
|
76
147
|
await releaseLock(paths.lock, owner).catch(() => { });
|
|
77
148
|
}
|
|
78
149
|
}
|
|
@@ -132,7 +203,17 @@ export async function provisionStableWindowsRecoveryController(options, processE
|
|
|
132
203
|
const owner = await acquireLock(paths.lock, options.pid ?? process.pid);
|
|
133
204
|
const controllerDirectory = join(paths.root, WINDOWS_CONTROLLER_DIRECTORY);
|
|
134
205
|
const controllerPath = stableWindowsRecoveryControllerPathForStateDir(paths.root);
|
|
135
|
-
const temporaryPath = join(controllerDirectory, `.${WINDOWS_CONTROLLER_NAME}.${randomBytes(8).toString('hex')}.tmp`);
|
|
206
|
+
const temporaryPath = options.artifactClaimPath ?? join(controllerDirectory, `.${WINDOWS_CONTROLLER_NAME}.${randomBytes(8).toString('hex')}.tmp`);
|
|
207
|
+
if (dirname(resolve(temporaryPath)) !== resolve(controllerDirectory)) {
|
|
208
|
+
throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, 'windows_controller_claim_outside_directory');
|
|
209
|
+
}
|
|
210
|
+
const ownershipReceiptPath = options.replaceExisting === false && options.artifactClaimPath !== undefined
|
|
211
|
+
? bootstrapClaimOwnershipReceiptPath(temporaryPath)
|
|
212
|
+
: undefined;
|
|
213
|
+
if (ownershipReceiptPath && !options.onArtifactPublished) {
|
|
214
|
+
throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, 'bootstrap_windows_controller_claim_has_no_durable_owner_callback');
|
|
215
|
+
}
|
|
216
|
+
let retainClaimOnFailure = options.replaceExisting === false && options.artifactClaimPath !== undefined;
|
|
136
217
|
try {
|
|
137
218
|
const loadedJournal = await readJournal(paths.journal);
|
|
138
219
|
if (loadedJournal) {
|
|
@@ -151,24 +232,60 @@ export async function provisionStableWindowsRecoveryController(options, processE
|
|
|
151
232
|
}
|
|
152
233
|
await ensureSecureDirectory(paths.root);
|
|
153
234
|
await ensureSecureDirectory(controllerDirectory);
|
|
235
|
+
if (options.replaceExisting === false) {
|
|
236
|
+
try {
|
|
237
|
+
await lstat(controllerPath);
|
|
238
|
+
throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, `bootstrap_windows_controller_already_exists:${controllerPath}`);
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
if (!isErrno(error, 'ENOENT'))
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
154
245
|
const targetIdentity = await assertRegularNonSymlink(paths.targetPath, 'target');
|
|
155
246
|
const targetBytes = await readRegularFile(paths.targetPath);
|
|
156
|
-
await writeExclusiveFile(temporaryPath, targetBytes, 0o700
|
|
247
|
+
await writeExclusiveFile(temporaryPath, targetBytes, 0o700, ownershipReceiptPath
|
|
248
|
+
? {
|
|
249
|
+
ownershipReceiptPath,
|
|
250
|
+
onClaimCreated: () => options.onArtifactPublished(controllerPath, temporaryPath),
|
|
251
|
+
}
|
|
252
|
+
: undefined);
|
|
157
253
|
await assertSameFileIdentity(paths.targetPath, targetIdentity);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
254
|
+
if (options.replaceExisting !== false) {
|
|
255
|
+
try {
|
|
256
|
+
await assertRegularNonSymlink(controllerPath, 'windows_controller');
|
|
257
|
+
await rm(controllerPath);
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
if (!isErrno(error, 'ENOENT'))
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
161
263
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
264
|
+
if (options.replaceExisting === false) {
|
|
265
|
+
await link(temporaryPath, controllerPath);
|
|
266
|
+
await syncDirectoryDurably(controllerDirectory);
|
|
267
|
+
await options.onArtifactPublished?.(controllerPath, temporaryPath);
|
|
268
|
+
if (ownershipReceiptPath) {
|
|
269
|
+
await rm(ownershipReceiptPath, { force: true });
|
|
270
|
+
await syncDirectoryDurably(controllerDirectory);
|
|
271
|
+
}
|
|
272
|
+
await rm(temporaryPath, { force: true });
|
|
273
|
+
await syncDirectoryDurably(controllerDirectory);
|
|
274
|
+
retainClaimOnFailure = false;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
await rename(temporaryPath, controllerPath);
|
|
278
|
+
await syncDirectoryDurably(controllerDirectory);
|
|
165
279
|
}
|
|
166
|
-
await rename(temporaryPath, controllerPath);
|
|
167
280
|
await chmod(controllerPath, 0o700);
|
|
168
281
|
return controllerPath;
|
|
169
282
|
}
|
|
170
283
|
finally {
|
|
171
|
-
|
|
284
|
+
if (!retainClaimOnFailure) {
|
|
285
|
+
if (ownershipReceiptPath)
|
|
286
|
+
await rm(ownershipReceiptPath, { force: true }).catch(() => { });
|
|
287
|
+
await rm(temporaryPath, { force: true }).catch(() => { });
|
|
288
|
+
}
|
|
172
289
|
await releaseLock(paths.lock, owner).catch(() => { });
|
|
173
290
|
}
|
|
174
291
|
}
|
|
@@ -787,15 +904,46 @@ async function readRegularFile(source) {
|
|
|
787
904
|
await sourceHandle.close().catch(() => { });
|
|
788
905
|
}
|
|
789
906
|
}
|
|
790
|
-
async function writeExclusiveFile(destination, bytes, mode) {
|
|
791
|
-
const
|
|
907
|
+
async function writeExclusiveFile(destination, bytes, mode, publication) {
|
|
908
|
+
const stagingPath = publication?.ownershipReceiptPath ?? destination;
|
|
909
|
+
if (publication
|
|
910
|
+
&& dirname(resolve(publication.ownershipReceiptPath)) !== dirname(resolve(destination))) {
|
|
911
|
+
throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, 'bootstrap_controller_ownership_receipt_outside_directory');
|
|
912
|
+
}
|
|
913
|
+
let stagingCreated = false;
|
|
914
|
+
let claimPublished = false;
|
|
915
|
+
const destinationHandle = await open(stagingPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, mode);
|
|
916
|
+
stagingCreated = true;
|
|
792
917
|
try {
|
|
793
918
|
await destinationHandle.writeFile(bytes);
|
|
794
|
-
await destinationHandle.sync();
|
|
795
919
|
await destinationHandle.chmod(mode);
|
|
920
|
+
await destinationHandle.sync();
|
|
921
|
+
if (publication) {
|
|
922
|
+
await syncDirectoryDurably(dirname(destination));
|
|
923
|
+
const [opened, receipt] = await Promise.all([
|
|
924
|
+
destinationHandle.stat({ bigint: true }),
|
|
925
|
+
lstat(publication.ownershipReceiptPath, { bigint: true }),
|
|
926
|
+
]);
|
|
927
|
+
if (!opened.isFile() || opened.dev <= 0n || opened.ino <= 0n
|
|
928
|
+
|| receipt.dev !== opened.dev || receipt.ino !== opened.ino) {
|
|
929
|
+
throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, 'bootstrap_controller_ownership_receipt_mismatch');
|
|
930
|
+
}
|
|
931
|
+
await link(publication.ownershipReceiptPath, destination);
|
|
932
|
+
await syncDirectoryDurably(dirname(destination));
|
|
933
|
+
claimPublished = true;
|
|
934
|
+
const claim = await lstat(destination, { bigint: true });
|
|
935
|
+
if (claim.dev !== opened.dev || claim.ino !== opened.ino) {
|
|
936
|
+
throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.UNSAFE_UPDATE_PATH, 'bootstrap_controller_claim_receipt_mismatch');
|
|
937
|
+
}
|
|
938
|
+
await publication.onClaimCreated();
|
|
939
|
+
}
|
|
796
940
|
}
|
|
797
941
|
finally {
|
|
798
942
|
await destinationHandle.close().catch(() => { });
|
|
943
|
+
if (publication && stagingCreated && !claimPublished) {
|
|
944
|
+
await rm(stagingPath, { force: true }).catch(() => { });
|
|
945
|
+
await syncDirectoryDurably(dirname(stagingPath)).catch(() => { });
|
|
946
|
+
}
|
|
799
947
|
}
|
|
800
948
|
}
|
|
801
949
|
async function restoreBackup(paths, journal, force = false) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { type ChildProcess } from 'node:child_process';
|
|
1
2
|
import { type SelfUpdateRecoveryOptions } from './transaction.js';
|
|
3
|
+
import { type RecoveryControllerAuthorityDependencies } from './controllerLifecycleAuthority.js';
|
|
2
4
|
export declare const UNIX_RECOVERY_CONTROLLER_ARG = "--evolver-unix-recovery-controller";
|
|
3
5
|
export interface UnixRecoveryControllerOptions extends SelfUpdateRecoveryOptions {
|
|
4
6
|
argv?: readonly string[];
|
|
@@ -7,9 +9,15 @@ export interface UnixRecoveryControllerOptions extends SelfUpdateRecoveryOptions
|
|
|
7
9
|
confirmationTimeoutMs?: number;
|
|
8
10
|
pollIntervalMs?: number;
|
|
9
11
|
stopTimeoutMs?: number;
|
|
12
|
+
startupGateTimeoutMs?: number;
|
|
13
|
+
startupAttestationTimeoutMs?: number;
|
|
10
14
|
logger?: {
|
|
11
15
|
write(chunk: string): unknown;
|
|
12
16
|
};
|
|
17
|
+
/** Test-only process adapter; production always executes the bound target with the fixed argv. */
|
|
18
|
+
spawnTarget?: (targetPath: string, env: NodeJS.ProcessEnv, startupAttestation: boolean) => ChildProcess;
|
|
19
|
+
/** Test seam for exact lifecycle-owner and supervised-activation authority. */
|
|
20
|
+
lifecycleAuthority?: RecoveryControllerAuthorityDependencies;
|
|
13
21
|
}
|
|
14
22
|
export declare function maybeRunUnixRecoveryController(options?: UnixRecoveryControllerOptions): Promise<number | undefined>;
|
|
15
23
|
export declare function runUnixRecoveryController(options?: UnixRecoveryControllerOptions): Promise<number>;
|