@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
|
@@ -4,15 +4,16 @@
|
|
|
4
4
|
// shells out or touches a real install tree by accident.
|
|
5
5
|
//
|
|
6
6
|
// THE HARD GATE (verify-before-apply, non-negotiable): after download and BEFORE any filesystem write or restart,
|
|
7
|
-
// the executor calls the PURE core
|
|
7
|
+
// the executor calls the PURE core verifySelectedManifestArtifact. If verification fails — bad sha256,
|
|
8
|
+
// missing/invalid signature
|
|
8
9
|
// when a key is configured, anything — the executor writes NOTHING, restarts NOTHING, and returns a structured
|
|
9
10
|
// failure. The old version stays intact and runnable. A compromised hub cannot turn this channel into fleet RCE.
|
|
10
11
|
//
|
|
11
12
|
// Concurrency: a process-level mutex guarantees that concurrent force_update messages execute the update EXACTLY
|
|
12
13
|
// once. The second caller short-circuits with `already_in_progress` and performs no I/O.
|
|
13
14
|
import { ops } from '@evomap/evolver-core';
|
|
14
|
-
import { SELF_UPDATE_FAILURE_CODES, classifySelfUpdateError, codeForDecisionReject, } from './failureCodes.js';
|
|
15
|
-
const { decideUpdate,
|
|
15
|
+
import { SELF_UPDATE_FAILURE_CODES, classifySelfUpdateError, codeForDecisionReject, selfUpdateFailure, } from './failureCodes.js';
|
|
16
|
+
const { decideUpdate, verifySelectedManifestArtifact } = ops;
|
|
16
17
|
// Process-level mutex. Module scope is correct: there is one daemon per process, and v1's _forceUpdateInFlight had
|
|
17
18
|
// the same lifetime. Guards against two force_update envelopes (or a heartbeat-driven + mailbox-driven trigger)
|
|
18
19
|
// racing the same upgrade and replacing files twice / double-restarting.
|
|
@@ -31,22 +32,28 @@ function report(deps, result) {
|
|
|
31
32
|
return result;
|
|
32
33
|
}
|
|
33
34
|
/**
|
|
34
|
-
* Execute a force_update directive end to end:
|
|
35
|
+
* Execute a force_update directive end to end: fast exits → lease → resolve/decide → download → VERIFY → replace → restart.
|
|
35
36
|
*
|
|
36
37
|
* Order is load-bearing:
|
|
37
|
-
* 1. policy off → do nothing (
|
|
38
|
-
* 2.
|
|
39
|
-
* 3. mutex: exactly one
|
|
40
|
-
* 4.
|
|
41
|
-
* 5.
|
|
42
|
-
* 6.
|
|
38
|
+
* 1. policy off → do nothing (explicit opt-out; auto is hard-gated upstream by supervisor + public key).
|
|
39
|
+
* 2. reject malformed/already-satisfied required versions without acquiring the lifecycle owner lease.
|
|
40
|
+
* 3. mutex + lifecycle owner lease: exactly one executor may resolve or mutate release state.
|
|
41
|
+
* 4. resolve and decideUpdate (pure): reject bad manifests or NOOP under the held lease.
|
|
42
|
+
* 5. download the staged release.
|
|
43
|
+
* 6. verifySelectedManifestArtifact (pure) — THE GATE. Fail → no write, no restart.
|
|
44
|
+
* 7. atomicReplace, then restart(). Only reached after verification passed.
|
|
43
45
|
*
|
|
44
46
|
* Never throws: every failure becomes a structured SelfUpdateResult so the daemon can report it and keep running
|
|
45
47
|
* on the old (intact) version.
|
|
46
48
|
*/
|
|
47
49
|
export async function executeForceUpdate(directive, deps) {
|
|
50
|
+
let selectedResult;
|
|
51
|
+
const finish = (result) => {
|
|
52
|
+
selectedResult = result;
|
|
53
|
+
return report(deps, result);
|
|
54
|
+
};
|
|
48
55
|
if (deps.policy !== 'auto') {
|
|
49
|
-
return
|
|
56
|
+
return finish({
|
|
50
57
|
outcome: 'disabled',
|
|
51
58
|
reason: deps.policy === 'prompt' ? 'self_update_policy_prompt_requires_approval' : 'self_update_policy_off',
|
|
52
59
|
});
|
|
@@ -55,87 +62,202 @@ export async function executeForceUpdate(directive, deps) {
|
|
|
55
62
|
? ops.normalizeRequiredVersion(directive.required_version)
|
|
56
63
|
: undefined;
|
|
57
64
|
if (directive.required_version !== undefined && !requiredFloor) {
|
|
58
|
-
return
|
|
65
|
+
return finish({
|
|
59
66
|
outcome: 'rejected_decision',
|
|
60
67
|
reason: 'required_version_invalid',
|
|
61
68
|
failureCode: SELF_UPDATE_FAILURE_CODES.BAD_REQUIRED_VERSION,
|
|
62
69
|
});
|
|
63
70
|
}
|
|
64
71
|
if (requiredFloor && ops.currentSatisfiesRequiredVersion(deps.currentVersion, requiredFloor)) {
|
|
65
|
-
return
|
|
72
|
+
return finish({ outcome: 'noop', reason: 'already_satisfied', targetVersion: requiredFloor });
|
|
66
73
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
manifest = await deps.resolveManifest(directive, deps.currentVersion);
|
|
71
|
-
}
|
|
72
|
-
catch (err) {
|
|
73
|
-
const targetVersion = ops.normalizeRequiredVersion(directive.required_version);
|
|
74
|
-
const classified = classifySelfUpdateError(err, SELF_UPDATE_FAILURE_CODES.DOWNLOAD_FAILED);
|
|
75
|
-
return report(deps, {
|
|
76
|
-
outcome: 'download_failed',
|
|
77
|
-
reason: `manifest_resolve_failed: ${classified.detail}`,
|
|
78
|
-
failureCode: classified.failureCode,
|
|
79
|
-
...(targetVersion ? { targetVersion } : {}),
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
const effectiveDirective = { ...directive, manifest };
|
|
84
|
-
const decision = decideUpdate({
|
|
85
|
-
current: deps.currentVersion,
|
|
86
|
-
...(requiredFloor ? { required: requiredFloor } : {}),
|
|
87
|
-
manifest,
|
|
88
|
-
});
|
|
89
|
-
if (decision.action === 'reject') {
|
|
90
|
-
return report(deps, {
|
|
91
|
-
outcome: 'rejected_decision',
|
|
92
|
-
reason: decision.reason,
|
|
93
|
-
failureCode: codeForDecisionReject(decision.reason),
|
|
94
|
-
...(decision.targetVersion ? { targetVersion: decision.targetVersion } : {}),
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
if (decision.action === 'noop') {
|
|
98
|
-
return report(deps, { outcome: 'noop', reason: decision.reason, ...(decision.targetVersion ? { targetVersion: decision.targetVersion } : {}) });
|
|
99
|
-
}
|
|
100
|
-
// action === 'proceed'. Take the mutex; a concurrent force_update short-circuits here with NO I/O.
|
|
74
|
+
// The mutex and lifecycle lease precede manifest resolution, which is release I/O too.
|
|
75
|
+
// Policy, malformed required-version, and already-satisfied fast exits remain lock-free.
|
|
101
76
|
if (inFlight) {
|
|
102
|
-
return
|
|
77
|
+
return finish({ outcome: 'already_in_progress', reason: 'another_update_in_flight' });
|
|
103
78
|
}
|
|
104
79
|
inFlight = true;
|
|
105
|
-
|
|
80
|
+
let targetVersion = requiredFloor ?? '';
|
|
81
|
+
let lifecycleLease;
|
|
82
|
+
let transaction;
|
|
83
|
+
const assertLifecycleLease = async () => {
|
|
84
|
+
try {
|
|
85
|
+
await lifecycleLease?.assertOwned();
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
const classified = classifySelfUpdateError(error, SELF_UPDATE_FAILURE_CODES.RECOVERY_REQUIRED);
|
|
89
|
+
throw selfUpdateFailure(SELF_UPDATE_FAILURE_CODES.RECOVERY_REQUIRED, `self_update_lifecycle_owner_lease_lost:${classified.detail}`, { cause: error });
|
|
90
|
+
}
|
|
91
|
+
};
|
|
106
92
|
try {
|
|
93
|
+
if (deps.acquireLifecycleLease) {
|
|
94
|
+
try {
|
|
95
|
+
lifecycleLease = await deps.acquireLifecycleLease();
|
|
96
|
+
await assertLifecycleLease();
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
const classified = classifySelfUpdateError(err, SELF_UPDATE_FAILURE_CODES.UPDATE_LOCKED);
|
|
100
|
+
return finish({
|
|
101
|
+
outcome: classified.failureCode === SELF_UPDATE_FAILURE_CODES.UPDATE_LOCKED
|
|
102
|
+
? 'already_in_progress'
|
|
103
|
+
: 'replace_failed',
|
|
104
|
+
reason: classified.detail,
|
|
105
|
+
failureCode: classified.failureCode,
|
|
106
|
+
...(targetVersion ? { targetVersion } : {}),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
let manifest = directive.manifest;
|
|
111
|
+
if (deps.resolveManifest && shouldResolveManifest(directive, manifest, Boolean(deps.publicKey))) {
|
|
112
|
+
try {
|
|
113
|
+
await assertLifecycleLease();
|
|
114
|
+
manifest = await deps.resolveManifest(directive, deps.currentVersion);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
const classified = classifySelfUpdateError(err, SELF_UPDATE_FAILURE_CODES.DOWNLOAD_FAILED);
|
|
118
|
+
if (classified.failureCode === SELF_UPDATE_FAILURE_CODES.RECOVERY_REQUIRED) {
|
|
119
|
+
return finish({
|
|
120
|
+
outcome: 'replace_failed',
|
|
121
|
+
reason: classified.detail,
|
|
122
|
+
failureCode: classified.failureCode,
|
|
123
|
+
...(targetVersion ? { targetVersion } : {}),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return finish({
|
|
127
|
+
outcome: 'download_failed',
|
|
128
|
+
reason: `manifest_resolve_failed: ${classified.detail}`,
|
|
129
|
+
failureCode: classified.failureCode,
|
|
130
|
+
...(targetVersion ? { targetVersion } : {}),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const effectiveDirective = { ...directive, manifest };
|
|
135
|
+
const decision = decideUpdate({
|
|
136
|
+
current: deps.currentVersion,
|
|
137
|
+
...(requiredFloor ? { required: requiredFloor } : {}),
|
|
138
|
+
manifest,
|
|
139
|
+
});
|
|
140
|
+
if (decision.action === 'reject') {
|
|
141
|
+
return finish({
|
|
142
|
+
outcome: 'rejected_decision',
|
|
143
|
+
reason: decision.reason,
|
|
144
|
+
failureCode: codeForDecisionReject(decision.reason),
|
|
145
|
+
...(decision.targetVersion ? { targetVersion: decision.targetVersion } : {}),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (decision.action === 'noop') {
|
|
149
|
+
return finish({
|
|
150
|
+
outcome: 'noop',
|
|
151
|
+
reason: decision.reason,
|
|
152
|
+
...(decision.targetVersion ? { targetVersion: decision.targetVersion } : {}),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
targetVersion = decision.targetVersion ?? manifest?.version ?? '';
|
|
156
|
+
if (deps.beginTransaction) {
|
|
157
|
+
try {
|
|
158
|
+
await assertLifecycleLease();
|
|
159
|
+
transaction = await deps.beginTransaction(targetVersion);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
const classified = classifySelfUpdateError(err, SELF_UPDATE_FAILURE_CODES.UPDATE_LOCKED);
|
|
163
|
+
return finish({
|
|
164
|
+
outcome: classified.failureCode === SELF_UPDATE_FAILURE_CODES.UPDATE_LOCKED ? 'already_in_progress' : 'replace_failed',
|
|
165
|
+
reason: classified.detail,
|
|
166
|
+
failureCode: classified.failureCode,
|
|
167
|
+
targetVersion,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
107
171
|
// 4. Download the staged release.
|
|
108
172
|
let dl;
|
|
109
173
|
try {
|
|
174
|
+
await assertLifecycleLease();
|
|
110
175
|
dl = await deps.download(targetVersion, effectiveDirective);
|
|
111
176
|
}
|
|
112
177
|
catch (err) {
|
|
113
178
|
const classified = classifySelfUpdateError(err, SELF_UPDATE_FAILURE_CODES.DOWNLOAD_FAILED);
|
|
114
|
-
|
|
179
|
+
await transaction?.abort(classified.failureCode).catch(() => { });
|
|
180
|
+
if (classified.failureCode === SELF_UPDATE_FAILURE_CODES.RECOVERY_REQUIRED) {
|
|
181
|
+
return finish({
|
|
182
|
+
outcome: 'replace_failed',
|
|
183
|
+
reason: classified.detail,
|
|
184
|
+
failureCode: classified.failureCode,
|
|
185
|
+
targetVersion,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return finish({
|
|
115
189
|
outcome: 'download_failed',
|
|
116
190
|
reason: classified.detail,
|
|
117
191
|
failureCode: classified.failureCode,
|
|
118
192
|
targetVersion,
|
|
119
193
|
});
|
|
120
194
|
}
|
|
195
|
+
if (transaction) {
|
|
196
|
+
try {
|
|
197
|
+
await assertLifecycleLease();
|
|
198
|
+
dl = await transaction.adoptDownloaded(dl);
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
await transaction.abort(SELF_UPDATE_FAILURE_CODES.REPLACE_FAILED).catch(() => { });
|
|
202
|
+
const classified = classifySelfUpdateError(err, SELF_UPDATE_FAILURE_CODES.REPLACE_FAILED);
|
|
203
|
+
return finish({
|
|
204
|
+
outcome: 'replace_failed',
|
|
205
|
+
reason: classified.detail,
|
|
206
|
+
failureCode: classified.failureCode,
|
|
207
|
+
targetVersion,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
121
211
|
// 5. THE GATE: verify the downloaded bytes against the (optionally signed) manifest BEFORE any write.
|
|
122
|
-
|
|
212
|
+
try {
|
|
213
|
+
await assertLifecycleLease();
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
await transaction?.abort(SELF_UPDATE_FAILURE_CODES.RECOVERY_REQUIRED).catch(() => { });
|
|
217
|
+
const classified = classifySelfUpdateError(err, SELF_UPDATE_FAILURE_CODES.RECOVERY_REQUIRED);
|
|
218
|
+
return finish({
|
|
219
|
+
outcome: 'replace_failed',
|
|
220
|
+
reason: classified.detail,
|
|
221
|
+
failureCode: classified.failureCode,
|
|
222
|
+
targetVersion,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
const verification = verifySelectedManifestArtifact(manifest, dl.artifacts, ...(deps.publicKey ? [deps.publicKey] : []));
|
|
123
226
|
if (!verification.ok) {
|
|
227
|
+
await transaction?.abort(SELF_UPDATE_FAILURE_CODES.REJECTED_VERIFICATION).catch(() => { });
|
|
124
228
|
// Verification failed → write NOTHING, restart NOTHING. Old version stays intact and runnable.
|
|
125
|
-
return
|
|
229
|
+
return finish({
|
|
126
230
|
outcome: 'rejected_verification',
|
|
127
231
|
reason: verification.reason,
|
|
128
232
|
failureCode: SELF_UPDATE_FAILURE_CODES.REJECTED_VERIFICATION,
|
|
129
233
|
targetVersion,
|
|
130
234
|
});
|
|
131
235
|
}
|
|
236
|
+
try {
|
|
237
|
+
await assertLifecycleLease();
|
|
238
|
+
await transaction?.markVerified(dl.artifacts);
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
await transaction?.abort(SELF_UPDATE_FAILURE_CODES.REPLACE_FAILED).catch(() => { });
|
|
242
|
+
const classified = classifySelfUpdateError(err, SELF_UPDATE_FAILURE_CODES.REPLACE_FAILED);
|
|
243
|
+
return finish({
|
|
244
|
+
outcome: 'replace_failed',
|
|
245
|
+
reason: classified.detail,
|
|
246
|
+
failureCode: classified.failureCode,
|
|
247
|
+
targetVersion,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
132
250
|
// 6. Verified. Atomic replace, then signal restart. A replace failure leaves the old version intact.
|
|
133
251
|
try {
|
|
134
|
-
await
|
|
252
|
+
await assertLifecycleLease();
|
|
253
|
+
if (transaction)
|
|
254
|
+
await transaction.install();
|
|
255
|
+
else
|
|
256
|
+
await deps.atomicReplace(dl.stagedPath);
|
|
135
257
|
}
|
|
136
258
|
catch (err) {
|
|
137
259
|
const classified = classifySelfUpdateError(err, SELF_UPDATE_FAILURE_CODES.COPY_FAILED);
|
|
138
|
-
return
|
|
260
|
+
return finish({
|
|
139
261
|
outcome: 'replace_failed',
|
|
140
262
|
reason: classified.detail,
|
|
141
263
|
failureCode: classified.failureCode,
|
|
@@ -147,17 +269,70 @@ export async function executeForceUpdate(directive, deps) {
|
|
|
147
269
|
reason: 'verified_and_replaced',
|
|
148
270
|
targetVersion,
|
|
149
271
|
appliedVia: dl.appliedVia ?? 'binary',
|
|
272
|
+
...(transaction ? { confirmationPending: true } : {}),
|
|
150
273
|
};
|
|
151
|
-
|
|
152
|
-
|
|
274
|
+
finish(result);
|
|
275
|
+
try {
|
|
276
|
+
await assertLifecycleLease();
|
|
277
|
+
await transaction?.markRestartRequested();
|
|
278
|
+
await assertLifecycleLease();
|
|
279
|
+
await deps.restart(); // v1 convention: exit(78) → supervisor relaunches the new version.
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
const classified = classifySelfUpdateError(err, SELF_UPDATE_FAILURE_CODES.RESTART_FAILED);
|
|
283
|
+
try {
|
|
284
|
+
await transaction?.rollback(classified.failureCode);
|
|
285
|
+
}
|
|
286
|
+
catch (rollbackError) {
|
|
287
|
+
const rollback = classifySelfUpdateError(rollbackError, SELF_UPDATE_FAILURE_CODES.ROLLBACK_FAILED);
|
|
288
|
+
return finish({
|
|
289
|
+
outcome: 'rollback_failed',
|
|
290
|
+
reason: rollback.detail,
|
|
291
|
+
failureCode: rollback.failureCode,
|
|
292
|
+
targetVersion,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
return finish({
|
|
296
|
+
outcome: 'restart_failed',
|
|
297
|
+
reason: classified.detail,
|
|
298
|
+
failureCode: classified.failureCode,
|
|
299
|
+
targetVersion,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
153
302
|
return result;
|
|
154
303
|
}
|
|
155
304
|
finally {
|
|
156
|
-
|
|
157
|
-
|
|
305
|
+
await transaction?.release().catch(() => { });
|
|
306
|
+
if (lifecycleLease) {
|
|
307
|
+
try {
|
|
308
|
+
await lifecycleLease.release();
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
if (selectedResult) {
|
|
312
|
+
selectedResult.cleanupWarning = boundedCleanupWarning(error);
|
|
313
|
+
if (selectedResult.outcome === 'applied') {
|
|
314
|
+
selectedResult.reason = 'verified_and_replaced_lifecycle_lease_release_unconfirmed';
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
deps.onCleanupWarning?.(selectedResult.cleanupWarning, selectedResult);
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
// Cleanup reporting must not replace the primary update outcome.
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
158
325
|
inFlight = false;
|
|
159
326
|
}
|
|
160
327
|
}
|
|
328
|
+
function boundedCleanupWarning(error) {
|
|
329
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
330
|
+
const normalized = raw
|
|
331
|
+
.replace(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]+/gu, ' ')
|
|
332
|
+
.replace(/\s+/g, ' ')
|
|
333
|
+
.trim();
|
|
334
|
+
return (normalized || 'self_update_lifecycle_owner_lease_release_failed').slice(0, 512);
|
|
335
|
+
}
|
|
161
336
|
function shouldResolveManifest(directive, manifest, signatureRequired) {
|
|
162
337
|
if (manifest === undefined)
|
|
163
338
|
return true;
|
|
@@ -17,6 +17,16 @@ export declare const SELF_UPDATE_FAILURE_CODES: Readonly<{
|
|
|
17
17
|
readonly FALLBACK_DOWNLOAD_FAILED: "fallback_download_failed";
|
|
18
18
|
readonly FALLBACK_EXTRACT_FAILED: "fallback_extract_failed";
|
|
19
19
|
readonly FALLBACK_MISSING_BINARY: "fallback_missing_binary";
|
|
20
|
+
readonly UPDATE_LOCKED: "update_locked";
|
|
21
|
+
readonly RECOVERY_REQUIRED: "recovery_required";
|
|
22
|
+
readonly UNSAFE_UPDATE_PATH: "unsafe_update_path";
|
|
23
|
+
readonly RESTART_FAILED: "restart_failed";
|
|
24
|
+
readonly READ_BACK_FAILED: "read_back_failed";
|
|
25
|
+
readonly ROLLBACK_FAILED: "rollback_failed";
|
|
26
|
+
readonly MIGRATION_DOWNLOAD_FAILED: "migration_download_failed";
|
|
27
|
+
readonly MIGRATION_VERIFY_FAILED: "migration_verify_failed";
|
|
28
|
+
readonly MIGRATION_INSTALL_FAILED: "migration_install_failed";
|
|
29
|
+
readonly MIGRATION_REGISTER_FAILED: "migration_register_failed";
|
|
20
30
|
}>;
|
|
21
31
|
export type SelfUpdateFailureCode = typeof SELF_UPDATE_FAILURE_CODES[keyof typeof SELF_UPDATE_FAILURE_CODES];
|
|
22
32
|
export interface ClassifiedSelfUpdateError {
|
|
@@ -22,6 +22,19 @@ export const SELF_UPDATE_FAILURE_CODES = Object.freeze({
|
|
|
22
22
|
FALLBACK_DOWNLOAD_FAILED: 'fallback_download_failed',
|
|
23
23
|
FALLBACK_EXTRACT_FAILED: 'fallback_extract_failed',
|
|
24
24
|
FALLBACK_MISSING_BINARY: 'fallback_missing_binary',
|
|
25
|
+
UPDATE_LOCKED: 'update_locked',
|
|
26
|
+
RECOVERY_REQUIRED: 'recovery_required',
|
|
27
|
+
UNSAFE_UPDATE_PATH: 'unsafe_update_path',
|
|
28
|
+
RESTART_FAILED: 'restart_failed',
|
|
29
|
+
READ_BACK_FAILED: 'read_back_failed',
|
|
30
|
+
ROLLBACK_FAILED: 'rollback_failed',
|
|
31
|
+
// One-time npm/JS → standalone binary migration (migration.ts). Append-only:
|
|
32
|
+
// these surface in attempt-marker detail strings and operator messages so
|
|
33
|
+
// telemetry can attribute first-run migration failures by phase.
|
|
34
|
+
MIGRATION_DOWNLOAD_FAILED: 'migration_download_failed',
|
|
35
|
+
MIGRATION_VERIFY_FAILED: 'migration_verify_failed',
|
|
36
|
+
MIGRATION_INSTALL_FAILED: 'migration_install_failed',
|
|
37
|
+
MIGRATION_REGISTER_FAILED: 'migration_register_failed',
|
|
25
38
|
});
|
|
26
39
|
export class SelfUpdateFailureError extends Error {
|
|
27
40
|
failureCode;
|
|
@@ -2,4 +2,8 @@ export * from './executor.js';
|
|
|
2
2
|
export * from './version.js';
|
|
3
3
|
export * from './policy.js';
|
|
4
4
|
export * from './releaseBinary.js';
|
|
5
|
-
export * from './
|
|
5
|
+
export * from './transaction.js';
|
|
6
|
+
export * from './failureCodes.js';
|
|
7
|
+
export * from './unixController.js';
|
|
8
|
+
export * from './windowsController.js';
|
|
9
|
+
export * from './bootstrapReadiness.js';
|
package/dist/selfUpdate/index.js
CHANGED
|
@@ -2,4 +2,8 @@ export * from './executor.js';
|
|
|
2
2
|
export * from './version.js';
|
|
3
3
|
export * from './policy.js';
|
|
4
4
|
export * from './releaseBinary.js';
|
|
5
|
-
export * from './
|
|
5
|
+
export * from './transaction.js';
|
|
6
|
+
export * from './failureCodes.js';
|
|
7
|
+
export * from './unixController.js';
|
|
8
|
+
export * from './windowsController.js';
|
|
9
|
+
export * from './bootstrapReadiness.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mailbox } from '@evomap/evolver-core';
|
|
2
2
|
import type { ForceUpdateDirective, SelfUpdateResult } from './executor.js';
|
|
3
|
+
import type { SelfUpdateRecoveryResult } from './transaction.js';
|
|
3
4
|
type MailboxStore = mailbox.MailboxStore;
|
|
4
5
|
type LastUpdateStatus = 'success' | 'failed' | 'skipped' | 'pending';
|
|
5
6
|
export interface LastUpdatePayload {
|
|
@@ -12,7 +13,7 @@ export interface LastUpdatePayload {
|
|
|
12
13
|
/**
|
|
13
14
|
* Which download channel produced the bytes that got applied: `'binary'` is
|
|
14
15
|
* the precompiled-asset happy path, `'tarball'` means Channel 1b fallback
|
|
15
|
-
* (release `.tar.gz`) was used. Persisted on success so the hub can see
|
|
16
|
+
* (release `.tar.gz`) was used. Persisted while confirmation is pending and on success so the hub can see
|
|
16
17
|
* "primary CDN is degraded — fallback carrying production" without having
|
|
17
18
|
* to mine telemetry. Absent on non-success or when the executor predates
|
|
18
19
|
* the appliedVia field.
|
|
@@ -36,6 +37,7 @@ export declare function reportPendingSelfUpdateLastUpdate(store: MailboxStore, d
|
|
|
36
37
|
fromVersion: string;
|
|
37
38
|
now?: number;
|
|
38
39
|
}): boolean;
|
|
40
|
+
export declare function finalizeSelfUpdateRecoveryLastUpdate(store: MailboxStore, recovery: SelfUpdateRecoveryResult, now?: number): boolean;
|
|
39
41
|
export declare function lastUpdateFromSelfUpdateResult(directive: ForceUpdateDirective, result: SelfUpdateResult, opts: {
|
|
40
42
|
fromVersion: string;
|
|
41
43
|
now: number;
|
|
@@ -86,6 +86,37 @@ export function reportPendingSelfUpdateLastUpdate(store, directive, opts = { fro
|
|
|
86
86
|
...(directive.directive_id ? { directive_id: String(directive.directive_id) } : {}),
|
|
87
87
|
}, now);
|
|
88
88
|
}
|
|
89
|
+
export function finalizeSelfUpdateRecoveryLastUpdate(store, recovery, now = Date.now()) {
|
|
90
|
+
if (recovery.outcome !== 'confirmed'
|
|
91
|
+
&& recovery.outcome !== 'rolled_back'
|
|
92
|
+
&& recovery.outcome !== 'blocked')
|
|
93
|
+
return false;
|
|
94
|
+
const toVersion = concreteVersion(recovery.targetVersion);
|
|
95
|
+
const fromVersion = concreteVersion(recovery.fromVersion);
|
|
96
|
+
if (!toVersion)
|
|
97
|
+
return false;
|
|
98
|
+
const current = readPendingLastUpdate(store, now);
|
|
99
|
+
if (current && current.to_version !== toVersion)
|
|
100
|
+
return false;
|
|
101
|
+
const common = {
|
|
102
|
+
to_version: toVersion,
|
|
103
|
+
finished_at: Math.max(now, FINISHED_AT_MIN_MS),
|
|
104
|
+
...(current?.directive_id ? { directive_id: current.directive_id } : {}),
|
|
105
|
+
...(current?.from_version ? { from_version: current.from_version } : fromVersion ? { from_version: fromVersion } : {}),
|
|
106
|
+
};
|
|
107
|
+
if (recovery.outcome === 'confirmed') {
|
|
108
|
+
return writeLastUpdate(store, {
|
|
109
|
+
...common,
|
|
110
|
+
status: 'success',
|
|
111
|
+
...(current?.applied_via ? { applied_via: current.applied_via } : {}),
|
|
112
|
+
}, now);
|
|
113
|
+
}
|
|
114
|
+
return writeLastUpdate(store, {
|
|
115
|
+
...common,
|
|
116
|
+
status: 'failed',
|
|
117
|
+
error: clampString(hubNs.redactString(`${recovery.failureCode ?? 'self_update_recovery_failed'}: ${recovery.outcome}`), LAST_UPDATE_ERROR_MAX),
|
|
118
|
+
}, now);
|
|
119
|
+
}
|
|
89
120
|
export function lastUpdateFromSelfUpdateResult(directive, result, opts) {
|
|
90
121
|
if (result.outcome === 'already_in_progress' || result.outcome === 'disabled')
|
|
91
122
|
return undefined;
|
|
@@ -94,11 +125,11 @@ export function lastUpdateFromSelfUpdateResult(directive, result, opts) {
|
|
|
94
125
|
return undefined;
|
|
95
126
|
const base = {
|
|
96
127
|
to_version: toVersion,
|
|
97
|
-
status:
|
|
128
|
+
status: statusForResult(result),
|
|
98
129
|
finished_at: Math.max(opts.now, FINISHED_AT_MIN_MS),
|
|
99
130
|
...(directive.directive_id ? { directive_id: String(directive.directive_id) } : {}),
|
|
100
131
|
};
|
|
101
|
-
if (base.status === 'success') {
|
|
132
|
+
if (base.status === 'success' || base.status === 'pending') {
|
|
102
133
|
return {
|
|
103
134
|
...base,
|
|
104
135
|
from_version: clampString(opts.fromVersion, LAST_UPDATE_FROM_VERSION_MAX),
|
|
@@ -114,10 +145,10 @@ export function lastUpdateFromSelfUpdateResult(directive, result, opts) {
|
|
|
114
145
|
}
|
|
115
146
|
return base;
|
|
116
147
|
}
|
|
117
|
-
function
|
|
118
|
-
if (outcome === 'applied')
|
|
119
|
-
return 'success';
|
|
120
|
-
if (outcome === 'noop')
|
|
148
|
+
function statusForResult(result) {
|
|
149
|
+
if (result.outcome === 'applied')
|
|
150
|
+
return result.confirmationPending ? 'pending' : 'success';
|
|
151
|
+
if (result.outcome === 'noop')
|
|
121
152
|
return 'skipped';
|
|
122
153
|
return 'failed';
|
|
123
154
|
}
|