@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,23 @@
|
|
|
1
|
+
export interface WindowsSecretProtector {
|
|
2
|
+
protect(secret: string): string;
|
|
3
|
+
unprotect(protectedValue: string): string;
|
|
4
|
+
preflight?(): void;
|
|
5
|
+
}
|
|
6
|
+
export interface PrivateNodeCredentialStoreOptions {
|
|
7
|
+
platform?: NodeJS.Platform;
|
|
8
|
+
windowsProtector?: WindowsSecretProtector;
|
|
9
|
+
}
|
|
10
|
+
export declare class PrivateNodeCredentialReadError extends Error {
|
|
11
|
+
constructor();
|
|
12
|
+
}
|
|
13
|
+
export declare class PrivateNodeCredentialStore {
|
|
14
|
+
private readonly directory;
|
|
15
|
+
private readonly path;
|
|
16
|
+
private readonly platform;
|
|
17
|
+
private readonly windowsProtector;
|
|
18
|
+
constructor(proxyStorePath: string, options?: PrivateNodeCredentialStoreOptions);
|
|
19
|
+
read(): string | undefined;
|
|
20
|
+
write(nodeSecret: string): void;
|
|
21
|
+
private prepareDirectory;
|
|
22
|
+
private assertRegularFile;
|
|
23
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { chmodSync, closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
4
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
5
|
+
const DIRECTORY_MODE = 0o700;
|
|
6
|
+
const FILE_MODE = 0o600;
|
|
7
|
+
const NODE_SECRET_RE = /^[a-f0-9]{64}$/i;
|
|
8
|
+
const WINDOWS_PROTECTED_VALUE_RE = /^[a-z0-9+/]+={0,2}$/i;
|
|
9
|
+
const MAX_PROTECTED_VALUE_LENGTH = 16_384;
|
|
10
|
+
export class PrivateNodeCredentialReadError extends Error {
|
|
11
|
+
constructor() {
|
|
12
|
+
super('stored private node credential is unreadable');
|
|
13
|
+
this.name = 'PrivateNodeCredentialReadError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export class PrivateNodeCredentialStore {
|
|
17
|
+
directory;
|
|
18
|
+
path;
|
|
19
|
+
platform;
|
|
20
|
+
windowsProtector;
|
|
21
|
+
constructor(proxyStorePath, options = {}) {
|
|
22
|
+
this.platform = options.platform ?? process.platform;
|
|
23
|
+
this.windowsProtector = this.platform === 'win32'
|
|
24
|
+
? options.windowsProtector ?? createWindowsDpapiProtector()
|
|
25
|
+
: undefined;
|
|
26
|
+
if (this.windowsProtector)
|
|
27
|
+
verifyWindowsProtector(this.windowsProtector);
|
|
28
|
+
this.directory = join(dirname(resolve(proxyStorePath)), '.private-credentials');
|
|
29
|
+
this.path = join(this.directory, this.platform === 'win32' ? 'node-secret.dpapi' : 'node-secret');
|
|
30
|
+
this.prepareDirectory();
|
|
31
|
+
}
|
|
32
|
+
read() {
|
|
33
|
+
if (!existsSync(this.path))
|
|
34
|
+
return undefined;
|
|
35
|
+
this.assertRegularFile(this.path);
|
|
36
|
+
if (this.platform !== 'win32')
|
|
37
|
+
chmodSync(this.path, FILE_MODE);
|
|
38
|
+
const storedValue = readFileSync(this.path, 'utf8').trim();
|
|
39
|
+
try {
|
|
40
|
+
const value = this.windowsProtector
|
|
41
|
+
? this.windowsProtector.unprotect(assertWindowsProtectedValue(storedValue))
|
|
42
|
+
: storedValue;
|
|
43
|
+
if (!NODE_SECRET_RE.test(value))
|
|
44
|
+
throw new PrivateNodeCredentialReadError();
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
throw new PrivateNodeCredentialReadError();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
write(nodeSecret) {
|
|
52
|
+
if (!NODE_SECRET_RE.test(nodeSecret))
|
|
53
|
+
throw new Error('private node credential is invalid');
|
|
54
|
+
const storedValue = this.windowsProtector
|
|
55
|
+
? assertWindowsProtectedValue(this.windowsProtector.protect(nodeSecret))
|
|
56
|
+
: nodeSecret;
|
|
57
|
+
this.prepareDirectory();
|
|
58
|
+
if (existsSync(this.path))
|
|
59
|
+
this.assertRegularFile(this.path);
|
|
60
|
+
const temporaryPath = join(this.directory, `.node-secret.${process.pid}.${randomBytes(8).toString('hex')}.tmp`);
|
|
61
|
+
let descriptor;
|
|
62
|
+
try {
|
|
63
|
+
descriptor = openSync(temporaryPath, 'wx', FILE_MODE);
|
|
64
|
+
writeFileSync(descriptor, storedValue, 'utf8');
|
|
65
|
+
fsyncSync(descriptor);
|
|
66
|
+
closeSync(descriptor);
|
|
67
|
+
descriptor = undefined;
|
|
68
|
+
renameSync(temporaryPath, this.path);
|
|
69
|
+
if (this.platform !== 'win32')
|
|
70
|
+
chmodSync(this.path, FILE_MODE);
|
|
71
|
+
if (this.platform !== 'win32')
|
|
72
|
+
syncDirectory(this.directory);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (descriptor !== undefined)
|
|
76
|
+
closeSync(descriptor);
|
|
77
|
+
if (existsSync(temporaryPath))
|
|
78
|
+
unlinkSync(temporaryPath);
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
prepareDirectory() {
|
|
83
|
+
const parent = dirname(this.directory);
|
|
84
|
+
const parentStat = lstatSync(parent);
|
|
85
|
+
if (!parentStat.isDirectory() || parentStat.isSymbolicLink()) {
|
|
86
|
+
throw new Error(`private credential parent must be a real directory: ${parent}`);
|
|
87
|
+
}
|
|
88
|
+
if (this.platform !== 'win32' && (parentStat.mode & 0o022) !== 0) {
|
|
89
|
+
throw new Error(`private credential parent must not be group/world-writable: ${parent}`);
|
|
90
|
+
}
|
|
91
|
+
const directoryExisted = existsSync(this.directory);
|
|
92
|
+
mkdirSync(this.directory, { recursive: true, mode: DIRECTORY_MODE });
|
|
93
|
+
const stat = lstatSync(this.directory);
|
|
94
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
95
|
+
throw new Error(`private credential path must be a real directory: ${this.directory}`);
|
|
96
|
+
}
|
|
97
|
+
if (this.platform !== 'win32')
|
|
98
|
+
chmodSync(this.directory, DIRECTORY_MODE);
|
|
99
|
+
if (!directoryExisted && this.platform !== 'win32')
|
|
100
|
+
syncDirectory(parent);
|
|
101
|
+
}
|
|
102
|
+
assertRegularFile(path) {
|
|
103
|
+
const stat = lstatSync(path);
|
|
104
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
105
|
+
throw new Error(`private credential must be a regular file: ${path}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function assertWindowsProtectedValue(value) {
|
|
110
|
+
const trimmed = value.trim();
|
|
111
|
+
if (!trimmed || trimmed.length > MAX_PROTECTED_VALUE_LENGTH || !WINDOWS_PROTECTED_VALUE_RE.test(trimmed)) {
|
|
112
|
+
throw new Error('stored private node credential ciphertext is invalid');
|
|
113
|
+
}
|
|
114
|
+
return trimmed;
|
|
115
|
+
}
|
|
116
|
+
function createWindowsDpapiProtector() {
|
|
117
|
+
const systemRoot = process.env['SystemRoot']?.trim();
|
|
118
|
+
if (!systemRoot || !isAbsolute(systemRoot)) {
|
|
119
|
+
throw new Error('Windows private credential persistence requires an absolute SystemRoot');
|
|
120
|
+
}
|
|
121
|
+
const executable = join(systemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
122
|
+
let executableIsSafe = false;
|
|
123
|
+
try {
|
|
124
|
+
const executableStat = lstatSync(executable);
|
|
125
|
+
executableIsSafe = executableStat.isFile() && !executableStat.isSymbolicLink();
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Normalize filesystem errors so local paths are not included in startup logs.
|
|
129
|
+
}
|
|
130
|
+
if (!executableIsSafe) {
|
|
131
|
+
throw new Error('Windows private credential persistence requires Windows PowerShell');
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
preflight: () => {
|
|
135
|
+
const canary = randomBytes(32).toString('hex');
|
|
136
|
+
if (runWindowsPowerShell(executable, WINDOWS_DPAPI_PREFLIGHT_SCRIPT, canary) !== canary) {
|
|
137
|
+
throw new Error('Windows private credential protection preflight failed');
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
protect: (secret) => runWindowsPowerShell(executable, WINDOWS_DPAPI_PROTECT_SCRIPT, secret),
|
|
141
|
+
unprotect: (protectedValue) => runWindowsPowerShell(executable, WINDOWS_DPAPI_UNPROTECT_SCRIPT, protectedValue),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function verifyWindowsProtector(protector) {
|
|
145
|
+
try {
|
|
146
|
+
if (protector.preflight) {
|
|
147
|
+
protector.preflight();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const canary = randomBytes(32).toString('hex');
|
|
151
|
+
const protectedCanary = assertWindowsProtectedValue(protector.protect(canary));
|
|
152
|
+
if (protector.unprotect(protectedCanary) !== canary) {
|
|
153
|
+
throw new Error('round trip mismatch');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
throw new Error('Windows private credential protection preflight failed');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function runWindowsPowerShell(executable, script, input) {
|
|
161
|
+
try {
|
|
162
|
+
return execFileSync(executable, ['-NoLogo', '-NoProfile', '-NonInteractive', '-Command', script], {
|
|
163
|
+
encoding: 'utf8',
|
|
164
|
+
input,
|
|
165
|
+
maxBuffer: 64 * 1024,
|
|
166
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
167
|
+
timeout: 15_000,
|
|
168
|
+
windowsHide: true,
|
|
169
|
+
}).trim();
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
throw new Error('Windows private credential protection failed');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const WINDOWS_DPAPI_PREFLIGHT_SCRIPT = [
|
|
176
|
+
"$ErrorActionPreference = 'Stop'",
|
|
177
|
+
'Add-Type -AssemblyName System.Security',
|
|
178
|
+
'$plain = [Console]::In.ReadToEnd()',
|
|
179
|
+
'$bytes = [Text.Encoding]::UTF8.GetBytes($plain)',
|
|
180
|
+
'$scope = [Security.Cryptography.DataProtectionScope]::CurrentUser',
|
|
181
|
+
'$protected = [Security.Cryptography.ProtectedData]::Protect($bytes, $null, $scope)',
|
|
182
|
+
'$restored = [Security.Cryptography.ProtectedData]::Unprotect($protected, $null, $scope)',
|
|
183
|
+
'[Console]::Out.Write([Text.Encoding]::UTF8.GetString($restored))',
|
|
184
|
+
].join('; ');
|
|
185
|
+
const WINDOWS_DPAPI_PROTECT_SCRIPT = [
|
|
186
|
+
"$ErrorActionPreference = 'Stop'",
|
|
187
|
+
'Add-Type -AssemblyName System.Security',
|
|
188
|
+
'$plain = [Console]::In.ReadToEnd()',
|
|
189
|
+
'$bytes = [Text.Encoding]::UTF8.GetBytes($plain)',
|
|
190
|
+
'$scope = [Security.Cryptography.DataProtectionScope]::CurrentUser',
|
|
191
|
+
'$protected = [Security.Cryptography.ProtectedData]::Protect($bytes, $null, $scope)',
|
|
192
|
+
'[Console]::Out.Write([Convert]::ToBase64String($protected))',
|
|
193
|
+
].join('; ');
|
|
194
|
+
const WINDOWS_DPAPI_UNPROTECT_SCRIPT = [
|
|
195
|
+
"$ErrorActionPreference = 'Stop'",
|
|
196
|
+
'Add-Type -AssemblyName System.Security',
|
|
197
|
+
'$protected = [Convert]::FromBase64String([Console]::In.ReadToEnd())',
|
|
198
|
+
'$scope = [Security.Cryptography.DataProtectionScope]::CurrentUser',
|
|
199
|
+
'$bytes = [Security.Cryptography.ProtectedData]::Unprotect($protected, $null, $scope)',
|
|
200
|
+
'[Console]::Out.Write([Text.Encoding]::UTF8.GetString($bytes))',
|
|
201
|
+
].join('; ');
|
|
202
|
+
function syncDirectory(path) {
|
|
203
|
+
const descriptor = openSync(path, 'r');
|
|
204
|
+
try {
|
|
205
|
+
fsyncSync(descriptor);
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
closeSync(descriptor);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -235,9 +235,15 @@ function sessionIdFromUserField(value) {
|
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
237
|
if (parsed && typeof parsed === 'object') {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
238
|
+
// Accept both spellings. The archive-side session key (agentic-trace-pipeline
|
|
239
|
+
// derive_session_key) reads `session_id ?? sessionId` from this same field, so a
|
|
240
|
+
// camelCase producer yields a `cc::<sid>` key upstream while we recorded null —
|
|
241
|
+
// the two datasets then silently fail to join. Both spellings still pass through
|
|
242
|
+
// safePlainSessionId; this widens the accepted key name, never the value guard.
|
|
243
|
+
const raw = parsed['session_id']
|
|
244
|
+
?? parsed['sessionId'];
|
|
245
|
+
if (typeof raw === 'string' && raw.length > 0)
|
|
246
|
+
return safePlainSessionId(raw);
|
|
241
247
|
}
|
|
242
248
|
return '';
|
|
243
249
|
}
|
|
@@ -998,9 +998,13 @@ function sessionIdFromUserField(value) {
|
|
|
998
998
|
}
|
|
999
999
|
}
|
|
1000
1000
|
if (parsed && typeof parsed === 'object') {
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1001
|
+
// Accept both spellings — see the matching note in messagesRoute.ts. The
|
|
1002
|
+
// archive-side session key reads `session_id ?? sessionId` from this field, so
|
|
1003
|
+
// dropping camelCase here costs a join, not just a field.
|
|
1004
|
+
const raw = parsed['session_id']
|
|
1005
|
+
?? parsed['sessionId'];
|
|
1006
|
+
if (typeof raw === 'string' && raw.length > 0)
|
|
1007
|
+
return safePlainSessionId(raw);
|
|
1004
1008
|
}
|
|
1005
1009
|
return '';
|
|
1006
1010
|
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { util } from '@evomap/evolver-core';
|
|
3
|
+
import { type MigrationOptions } from './migration.js';
|
|
4
|
+
export declare const RECOVERY_CONTROLLER_LIFECYCLE_OWNER_CAPABILITY_ENV = "EVOLVER_INTERNAL_RECOVERY_CONTROLLER_LIFECYCLE_OWNER";
|
|
5
|
+
type BootstrapProcessKill = (pid: number, signal: NodeJS.Signals | 0) => boolean;
|
|
6
|
+
type BootstrapSkipReason = 'already_supervised' | 'already_bootstrapped' | 'unsupported_install_shape' | 'policy_not_auto' | 'bootstrap_disabled' | 'ci_environment' | 'container_environment' | 'recent_failure' | 'migration_ambiguous' | 'bootstrap_attempt_invalid' | 'bootstrap_intent_pending' | 'bootstrap_attempt_pending';
|
|
7
|
+
export interface BootstrapDecision {
|
|
8
|
+
proceed: boolean;
|
|
9
|
+
reason?: BootstrapSkipReason;
|
|
10
|
+
}
|
|
11
|
+
export interface BootstrapOutcome {
|
|
12
|
+
ok: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* 'bootstrapped' / 'bootstrapped_lock_release_unconfirmed' / 'migrated' on success;
|
|
15
|
+
* failure/skip reason otherwise. Migration failures record 'migration_failed' /
|
|
16
|
+
* 'migration_timeout' (both cooldown-worthy).
|
|
17
|
+
*/
|
|
18
|
+
reason: string;
|
|
19
|
+
detail?: string;
|
|
20
|
+
/** The child may own manager or IPC state, so the foreground proxy must exit. */
|
|
21
|
+
requiresForegroundExit?: true;
|
|
22
|
+
}
|
|
23
|
+
export interface BootstrapRunOptions {
|
|
24
|
+
env: NodeJS.ProcessEnv;
|
|
25
|
+
platform?: NodeJS.Platform;
|
|
26
|
+
execPath?: string;
|
|
27
|
+
argv1?: string;
|
|
28
|
+
/** Parent force-timeout; it must exceed the child's transaction budget. */
|
|
29
|
+
timeoutMs?: number;
|
|
30
|
+
/** Absolute child deadline offset. Production uses the complete transaction budget. */
|
|
31
|
+
transactionBudgetMs?: number;
|
|
32
|
+
/** Bounded wait for OS confirmation after force-terminating the process tree. */
|
|
33
|
+
terminationGraceMs?: number;
|
|
34
|
+
now?: number;
|
|
35
|
+
exists?: (path: string) => boolean;
|
|
36
|
+
readFile?: (path: string) => string;
|
|
37
|
+
writeFile?: (path: string, content: string) => void;
|
|
38
|
+
spawnFn?: typeof spawn;
|
|
39
|
+
treeKillSpawnFn?: typeof spawn;
|
|
40
|
+
processKill?: BootstrapProcessKill;
|
|
41
|
+
/** Test-only failure seam; production publication always uses the strict exclusive writer. */
|
|
42
|
+
beforeIntentPublish?: () => void;
|
|
43
|
+
/** Test-only crash seam for each durable initial-publication boundary. */
|
|
44
|
+
afterIntentPublicationStep?: (step: 'create' | 'partial_write' | 'file_fsync' | 'link' | 'directory_fsync', path: string) => void;
|
|
45
|
+
/** Test-only crash seams around the atomic terminal/clear state transitions. */
|
|
46
|
+
beforeIntentTerminalFsync?: (path: string) => void;
|
|
47
|
+
afterIntentTerminalPublish?: () => void;
|
|
48
|
+
afterIntentClearRename?: () => void;
|
|
49
|
+
/** Test-only trust seams. Production always uses native owner/DACL validation. */
|
|
50
|
+
assertIntentDirectoryTrust?: (directory: string) => void;
|
|
51
|
+
assertIntentFileTrust?: (path: string) => void;
|
|
52
|
+
assertLegacyProofDirectoryTrust?: (directory: string) => void;
|
|
53
|
+
assertLegacyProofFileTrust?: (path: string) => void;
|
|
54
|
+
afterLegacyProofRead?: (path: string) => void;
|
|
55
|
+
/** Test-only process identity seams; production uses fresh native core observations. */
|
|
56
|
+
readRegistrationProcessStartIdentity?: (pid: number) => util.FileLockProcessStartIdentity | null;
|
|
57
|
+
registrationOwnerProcessStatus?: (owner: Pick<util.FileLockOwnerRecord, 'pid' | 'processStartIdentity'>) => util.FileLockOwnerProcessStatus;
|
|
58
|
+
registrationPublisherProcessStatus?: (publisher: Readonly<{
|
|
59
|
+
pid: number;
|
|
60
|
+
token: string;
|
|
61
|
+
processIdentityDigest: string;
|
|
62
|
+
}>) => util.FileLockOwnerProcessStatus;
|
|
63
|
+
/**
|
|
64
|
+
* Extra seams forwarded to the one-time npm/JS → standalone migration (migration.ts).
|
|
65
|
+
* Bootstrap-level seams (exists/readFile/writeFile/spawnFn/now/execPath) win when both
|
|
66
|
+
* are supplied, so the decision and the migration observe the same injected world.
|
|
67
|
+
*/
|
|
68
|
+
migration?: MigrationOptions;
|
|
69
|
+
}
|
|
70
|
+
/** Lifecycle state dir mirror of evolver-cli lifecyclePaths (kept dependency-free across packages). */
|
|
71
|
+
export declare function resolveBootstrapStateDir(env: NodeJS.ProcessEnv): string;
|
|
72
|
+
export declare function withRecoveryControllerLifecycleOwnerCapability(env: NodeJS.ProcessEnv, owner: util.FileLockOwnerRecord): NodeJS.ProcessEnv;
|
|
73
|
+
export interface PreparedRecoveryControllerLifecycleOwnerCapability {
|
|
74
|
+
env: NodeJS.ProcessEnv;
|
|
75
|
+
startupAckToken: string;
|
|
76
|
+
}
|
|
77
|
+
export declare function prepareRecoveryControllerLifecycleOwnerCapability(env: NodeJS.ProcessEnv, owner: util.FileLockOwnerRecord): PreparedRecoveryControllerLifecycleOwnerCapability;
|
|
78
|
+
export declare function clearRecoveryControllerLifecycleOwnerCapability(env: NodeJS.ProcessEnv): void;
|
|
79
|
+
export declare function publishRecoveryControllerLifecycleStartupAttestation(env: NodeJS.ProcessEnv, descriptor?: number): boolean;
|
|
80
|
+
export declare function lifecycleBootstrapStatePresent(env: NodeJS.ProcessEnv, exists?: (path: string) => boolean): boolean;
|
|
81
|
+
export type BootstrapDurableStateOptions = Pick<BootstrapRunOptions, 'exists' | 'readFile' | 'assertIntentFileTrust' | 'assertLegacyProofDirectoryTrust' | 'assertLegacyProofFileTrust' | 'afterLegacyProofRead' | 'registrationOwnerProcessStatus' | 'now'> & {
|
|
82
|
+
/** Exact owner currently holding the shared lifecycle mutation lock. */
|
|
83
|
+
expectedRecoveryOwner?: util.FileLockOwnerRecord;
|
|
84
|
+
};
|
|
85
|
+
export interface LifecycleBootstrapOwnerLease {
|
|
86
|
+
readonly path: string;
|
|
87
|
+
readonly owner: util.FileLockOwnerRecord;
|
|
88
|
+
assertOwned(): void;
|
|
89
|
+
armProcess(pid: number): util.FileLockOwnerRecord;
|
|
90
|
+
disarmProcess(): void;
|
|
91
|
+
retainProcess(): void;
|
|
92
|
+
transferToProcess(pid: number): util.FileLockOwnerRecord;
|
|
93
|
+
release(): void;
|
|
94
|
+
}
|
|
95
|
+
export declare function assertSupervisedLifecycleBootstrapState(env: NodeJS.ProcessEnv, options?: BootstrapDurableStateOptions & {
|
|
96
|
+
requireLifecycleState?: boolean;
|
|
97
|
+
/** Test-only parent identity seam; production always binds to process.ppid. */
|
|
98
|
+
recoveryControllerParentPid?: number;
|
|
99
|
+
}): void;
|
|
100
|
+
/**
|
|
101
|
+
* Revalidate the narrow parent-owned activation window used by a newly launched recovery
|
|
102
|
+
* controller. This deliberately rejects committed supervision: callers use it only as delegated
|
|
103
|
+
* authority while the lifecycle parent still owns the mutation lock and is waiting for readiness.
|
|
104
|
+
*/
|
|
105
|
+
export declare function assertActiveSupervisedLifecycleBootstrapDelegation(env: NodeJS.ProcessEnv, options?: BootstrapDurableStateOptions): void;
|
|
106
|
+
/**
|
|
107
|
+
* Revalidate a transaction-bound launcher before any self-update operation. Unlike the startup
|
|
108
|
+
* assertion above, this never accepts the narrow activating window used to publish readiness.
|
|
109
|
+
*/
|
|
110
|
+
export declare function assertCommittedLifecycleBootstrapState(env: NodeJS.ProcessEnv, options?: BootstrapDurableStateOptions): void;
|
|
111
|
+
/**
|
|
112
|
+
* Serialize self-update with every lifecycle bootstrap, recovery, and manual-transition writer.
|
|
113
|
+
* Acquisition is deliberately fail-fast: a heartbeat must not block the daemon while another
|
|
114
|
+
* lifecycle owner is active. Transaction-bound launchers revalidate the committed receipt under
|
|
115
|
+
* the exact acquired generation; legacy launchers still hold and recheck the shared owner lock.
|
|
116
|
+
*/
|
|
117
|
+
export declare function acquireLifecycleBootstrapOwnerLease(env: NodeJS.ProcessEnv, lockOptions?: {
|
|
118
|
+
maxTries?: number;
|
|
119
|
+
waitMs?: number;
|
|
120
|
+
}): LifecycleBootstrapOwnerLease;
|
|
121
|
+
export declare function looksLikeContainer(exists: (path: string) => boolean, readFile: (path: string) => string): boolean;
|
|
122
|
+
/** True when a recent bootstrap/migration attempt failed within the cooldown window. */
|
|
123
|
+
export declare function recentBootstrapFailure(env: NodeJS.ProcessEnv, optionsOrReadFile: Pick<BootstrapRunOptions, 'exists' | 'readFile' | 'assertIntentFileTrust'> | ((path: string) => string), now: number): boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Decide whether an unsupervised (degraded) startup should attempt first-run bootstrap.
|
|
126
|
+
* Pure — filesystem access is injectable for tests.
|
|
127
|
+
*/
|
|
128
|
+
export declare function shouldBootstrap(env: NodeJS.ProcessEnv, platform?: NodeJS.Platform, options?: Pick<BootstrapRunOptions, 'exists' | 'readFile' | 'now' | 'execPath' | 'assertIntentFileTrust'>): BootstrapDecision;
|
|
129
|
+
/**
|
|
130
|
+
* Resolve the `lifecycle bootstrap` invocation for the current install shape: standalone binary,
|
|
131
|
+
* CLI entry through node, or the npm-installed @evomap/evolver-cli sibling. Returns undefined when
|
|
132
|
+
* no CLI can be located. The caller may continue only after confirming durable state is clean.
|
|
133
|
+
*/
|
|
134
|
+
export declare function resolveBootstrapCliInvocation(options?: Pick<BootstrapRunOptions, 'execPath' | 'argv1' | 'exists'>): {
|
|
135
|
+
command: string;
|
|
136
|
+
args: string[];
|
|
137
|
+
} | undefined;
|
|
138
|
+
/** Best-effort attempt marker; never throws — bootstrap bookkeeping must not break startup. */
|
|
139
|
+
export declare function recordBootstrapAttempt(env: NodeJS.ProcessEnv, outcome: BootstrapOutcome, options?: Pick<BootstrapRunOptions, 'writeFile' | 'now'>): void;
|
|
140
|
+
/** Spawn `evolver lifecycle bootstrap` and await its result within a bounded timeout. */
|
|
141
|
+
export declare function runBootstrap(options: BootstrapRunOptions): Promise<BootstrapOutcome>;
|
|
142
|
+
export type DegradedStartupBootstrapResult = {
|
|
143
|
+
disposition: 'continue';
|
|
144
|
+
handedOver: false;
|
|
145
|
+
message: string;
|
|
146
|
+
} | {
|
|
147
|
+
disposition: 'handoff';
|
|
148
|
+
handedOver: true;
|
|
149
|
+
exitCode: 0;
|
|
150
|
+
message: string;
|
|
151
|
+
} | {
|
|
152
|
+
disposition: 'fail_closed';
|
|
153
|
+
handedOver: false;
|
|
154
|
+
exitCode: 1;
|
|
155
|
+
message: string;
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Orchestrate bootstrap for a degraded (default-auto, unsupervised) startup: decide, attempt,
|
|
159
|
+
* record, and produce the operator message. Never throws.
|
|
160
|
+
*/
|
|
161
|
+
export declare function bootstrapDegradedSelfUpdateStartup(env: NodeJS.ProcessEnv, platform?: NodeJS.Platform, options?: Omit<BootstrapRunOptions, 'env' | 'platform'>): Promise<DegradedStartupBootstrapResult>;
|
|
162
|
+
export {};
|