@askexenow/exe-os 0.9.392 → 0.9.394
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/deploy/stack-manifests/v0.9.json +400 -1
- package/dist/.dist-verified.json +3 -3
- package/dist/bin/cli.js +3 -3
- package/dist/bin/registry-proxy.js +1 -1
- package/dist/bin/stack-update.js +69 -17
- package/dist/bin/vps-health-gate.js +1 -1
- package/dist/build-manifest.json +34 -34
- package/dist/{chunk-33CHRORT.js → chunk-5OWHYOIF.js} +1 -1
- package/dist/{chunk-3SXU7KLW.js → chunk-DYFTZWG2.js} +227 -29
- package/dist/{chunk-TVSIS36M.js → chunk-PS77SULS.js} +1 -1
- package/dist/{chunk-PRCJG3PK.js → chunk-RLUDQYOA.js} +1 -1
- package/dist/hooks/manifest.json +1 -1
- package/dist/lib/registry-proxy.js +1 -1
- package/dist/{stack-generate-manifest-QWU7Q4MJ.js → stack-generate-manifest-H6LJQ2HV.js} +2 -2
- package/dist/{stack-promote-26RFMNSB.js → stack-promote-4EF7RNIV.js} +2 -2
- package/dist/{stack-release-7YHSLY7J.js → stack-release-YCNRPK6R.js} +3 -3
- package/dist/{stack-update-DTMW7GMW.js → stack-update-2F464W3N.js} +1 -1
- package/package.json +1 -1
- package/release-notes.json +15 -8
package/dist/build-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 2,
|
|
3
|
-
"generated_at": "2026-09-
|
|
3
|
+
"generated_at": "2026-09-23T23:35:24.804Z",
|
|
4
4
|
"files": [
|
|
5
5
|
{
|
|
6
6
|
"path": "active-agent-KMGOYJTP.js",
|
|
@@ -179,7 +179,7 @@
|
|
|
179
179
|
},
|
|
180
180
|
{
|
|
181
181
|
"path": "bin/cli.js",
|
|
182
|
-
"sha256": "
|
|
182
|
+
"sha256": "407694f935b3d6c4613ca6c24229ac39e53d9022bf4bc62303bc4436920089f5",
|
|
183
183
|
"bytes": 69792
|
|
184
184
|
},
|
|
185
185
|
{
|
|
@@ -524,7 +524,7 @@
|
|
|
524
524
|
},
|
|
525
525
|
{
|
|
526
526
|
"path": "bin/registry-proxy.js",
|
|
527
|
-
"sha256": "
|
|
527
|
+
"sha256": "224728a8c04ed51bd21488f26c521f39776d6d5c67d40e6b05acfa30aaa47eb8",
|
|
528
528
|
"bytes": 1885
|
|
529
529
|
},
|
|
530
530
|
{
|
|
@@ -549,8 +549,8 @@
|
|
|
549
549
|
},
|
|
550
550
|
{
|
|
551
551
|
"path": "bin/stack-update.js",
|
|
552
|
-
"sha256": "
|
|
553
|
-
"bytes":
|
|
552
|
+
"sha256": "39605342987fd37972f577aa1619e9fdc7aef0e339bc5188f820251392c45242",
|
|
553
|
+
"bytes": 131487
|
|
554
554
|
},
|
|
555
555
|
{
|
|
556
556
|
"path": "bin/update.js",
|
|
@@ -569,7 +569,7 @@
|
|
|
569
569
|
},
|
|
570
570
|
{
|
|
571
571
|
"path": "bin/vps-health-gate.js",
|
|
572
|
-
"sha256": "
|
|
572
|
+
"sha256": "386e8e34d613c6ed0511ff3380532e9bc24176de7c6a955770134c6a29853940",
|
|
573
573
|
"bytes": 333
|
|
574
574
|
},
|
|
575
575
|
{
|
|
@@ -672,11 +672,6 @@
|
|
|
672
672
|
"sha256": "0649a4d1eb38a53ced9d539731618da75464e0e191ebcc39b03aa2871071c2d4",
|
|
673
673
|
"bytes": 442
|
|
674
674
|
},
|
|
675
|
-
{
|
|
676
|
-
"path": "chunk-33CHRORT.js",
|
|
677
|
-
"sha256": "0f7fbaa13b4c7522f0b14a45b48ce2dfc004790e2577760afb9bb370e69ff90f",
|
|
678
|
-
"bytes": 7587
|
|
679
|
-
},
|
|
680
675
|
{
|
|
681
676
|
"path": "chunk-37IIJJZ7.js",
|
|
682
677
|
"sha256": "f7f9ea3373fb44a010f78ef8852bd264f8d67bea44c3ae9abc1d84c7d2771513",
|
|
@@ -717,11 +712,6 @@
|
|
|
717
712
|
"sha256": "46303e5db720a3f0b9ed8ecab75b9c6ea3c4a644f3805c6ae465e2fb9bd23fef",
|
|
718
713
|
"bytes": 5705
|
|
719
714
|
},
|
|
720
|
-
{
|
|
721
|
-
"path": "chunk-3SXU7KLW.js",
|
|
722
|
-
"sha256": "9df2f153a553b1270f7a9011d4f081732ea89b9ac2b042cb149b14f4ed44da22",
|
|
723
|
-
"bytes": 252039
|
|
724
|
-
},
|
|
725
715
|
{
|
|
726
716
|
"path": "chunk-3VW5ZP2F.js",
|
|
727
717
|
"sha256": "9eb8516063b9279b0556d589f9d7e3024b554ed2cf60cc84a58c32a193e85744",
|
|
@@ -792,6 +782,11 @@
|
|
|
792
782
|
"sha256": "c2e2367cffa3e57666f0307f2374f6ca8cbf2f07ba45b7361d680e8271be90ec",
|
|
793
783
|
"bytes": 3297
|
|
794
784
|
},
|
|
785
|
+
{
|
|
786
|
+
"path": "chunk-5OWHYOIF.js",
|
|
787
|
+
"sha256": "7321323a90b487e2f95b3f6c6dd72a177e546aa7aace189d3f725f879fa560c5",
|
|
788
|
+
"bytes": 7587
|
|
789
|
+
},
|
|
795
790
|
{
|
|
796
791
|
"path": "chunk-5V7BD7Z4.js",
|
|
797
792
|
"sha256": "ca37d6e8b8481a988ed41264e604ce992de1d8d63fff23e07b3d2367cba5af21",
|
|
@@ -1102,6 +1097,11 @@
|
|
|
1102
1097
|
"sha256": "cf3ce4cf6c9cdba17a8a5e3d9f0c78ae42f1cb72f1a06608cc8da1fb27f06a76",
|
|
1103
1098
|
"bytes": 3292
|
|
1104
1099
|
},
|
|
1100
|
+
{
|
|
1101
|
+
"path": "chunk-DYFTZWG2.js",
|
|
1102
|
+
"sha256": "72d0060a08f4996dfe65b4fe9718b00fc053f2bfd2747d176f7a4539954ec4fc",
|
|
1103
|
+
"bytes": 261313
|
|
1104
|
+
},
|
|
1105
1105
|
{
|
|
1106
1106
|
"path": "chunk-E23LNUWJ.js",
|
|
1107
1107
|
"sha256": "6d692be7ac73bf481e7013c4b948b70acd8282234f645d562457754afaa7a02e",
|
|
@@ -1663,9 +1663,9 @@
|
|
|
1663
1663
|
"bytes": 3317
|
|
1664
1664
|
},
|
|
1665
1665
|
{
|
|
1666
|
-
"path": "chunk-
|
|
1667
|
-
"sha256": "
|
|
1668
|
-
"bytes":
|
|
1666
|
+
"path": "chunk-PS77SULS.js",
|
|
1667
|
+
"sha256": "6c337e635c575f47a50c8587d2bfa2fcdd0ebfdf43a967f38170ce684ff63dbb",
|
|
1668
|
+
"bytes": 3766
|
|
1669
1669
|
},
|
|
1670
1670
|
{
|
|
1671
1671
|
"path": "chunk-PYS43F2X.js",
|
|
@@ -1762,6 +1762,11 @@
|
|
|
1762
1762
|
"sha256": "627baf16bbe786e186075ad51849ac4460738fb2e185216bddefca23d2ae55b2",
|
|
1763
1763
|
"bytes": 11048
|
|
1764
1764
|
},
|
|
1765
|
+
{
|
|
1766
|
+
"path": "chunk-RLUDQYOA.js",
|
|
1767
|
+
"sha256": "5e1fb642dcb8ed9d6fdb95fae00346afafff51fddd4b60d2b11c1c878d7b1c3b",
|
|
1768
|
+
"bytes": 40844
|
|
1769
|
+
},
|
|
1765
1770
|
{
|
|
1766
1771
|
"path": "chunk-RT57D7C4.js",
|
|
1767
1772
|
"sha256": "054f2262b18d6c727b18c7bc34491d1d30f20d4d8fb42eb21704ac9c190e5477",
|
|
@@ -1862,11 +1867,6 @@
|
|
|
1862
1867
|
"sha256": "56262268c24e15325112a9d2d1dd785dea9fe96ac44e0c14ff243edd1fbd7e04",
|
|
1863
1868
|
"bytes": 76651
|
|
1864
1869
|
},
|
|
1865
|
-
{
|
|
1866
|
-
"path": "chunk-TVSIS36M.js",
|
|
1867
|
-
"sha256": "c12d360e3677fc87730097aab294f995d40c3bb8daf837fbe1856f258cc2a742",
|
|
1868
|
-
"bytes": 3766
|
|
1869
|
-
},
|
|
1870
1870
|
{
|
|
1871
1871
|
"path": "chunk-TWMWHDOY.js",
|
|
1872
1872
|
"sha256": "d1a4b120060e1bda1c09919ff863fbb281c71f017d89b31f47e8df07587814c0",
|
|
@@ -2574,7 +2574,7 @@
|
|
|
2574
2574
|
},
|
|
2575
2575
|
{
|
|
2576
2576
|
"path": "hooks/manifest.json",
|
|
2577
|
-
"sha256": "
|
|
2577
|
+
"sha256": "4140c8729b945f17c2a19a259a8aa0a1e3f753237a95e005be4362d4fb90cc4f",
|
|
2578
2578
|
"bytes": 1841
|
|
2579
2579
|
},
|
|
2580
2580
|
{
|
|
@@ -2879,7 +2879,7 @@
|
|
|
2879
2879
|
},
|
|
2880
2880
|
{
|
|
2881
2881
|
"path": "lib/registry-proxy.js",
|
|
2882
|
-
"sha256": "
|
|
2882
|
+
"sha256": "94c2d851ff177536c9e0def91d5d55c740005ae6c96b9ee4b9543852a470d25f",
|
|
2883
2883
|
"bytes": 749
|
|
2884
2884
|
},
|
|
2885
2885
|
{
|
|
@@ -3463,8 +3463,8 @@
|
|
|
3463
3463
|
"bytes": 111
|
|
3464
3464
|
},
|
|
3465
3465
|
{
|
|
3466
|
-
"path": "stack-generate-manifest-
|
|
3467
|
-
"sha256": "
|
|
3466
|
+
"path": "stack-generate-manifest-H6LJQ2HV.js",
|
|
3467
|
+
"sha256": "d790293379cf08251382c3076496f39824ddb800e345e24ef00fff1d21e36335",
|
|
3468
3468
|
"bytes": 56960
|
|
3469
3469
|
},
|
|
3470
3470
|
{
|
|
@@ -3473,18 +3473,18 @@
|
|
|
3473
3473
|
"bytes": 4761
|
|
3474
3474
|
},
|
|
3475
3475
|
{
|
|
3476
|
-
"path": "stack-promote-
|
|
3477
|
-
"sha256": "
|
|
3476
|
+
"path": "stack-promote-4EF7RNIV.js",
|
|
3477
|
+
"sha256": "d9db50f30a0a97cbe56b4e566be8e9c1327d9ecb5c96a6a46967bf57ea76f8a9",
|
|
3478
3478
|
"bytes": 7425
|
|
3479
3479
|
},
|
|
3480
3480
|
{
|
|
3481
|
-
"path": "stack-release-
|
|
3482
|
-
"sha256": "
|
|
3481
|
+
"path": "stack-release-YCNRPK6R.js",
|
|
3482
|
+
"sha256": "bd26b47386fa7849325c313b30e2eb0ee8f0ba77ea8dcaf95256fb0241a824bd",
|
|
3483
3483
|
"bytes": 117550
|
|
3484
3484
|
},
|
|
3485
3485
|
{
|
|
3486
|
-
"path": "stack-update-
|
|
3487
|
-
"sha256": "
|
|
3486
|
+
"path": "stack-update-2F464W3N.js",
|
|
3487
|
+
"sha256": "8be2553c248e2067f8f4a0a0f342513af5080b88b79ce9b22ce07cf7a73bf5ce",
|
|
3488
3488
|
"bytes": 7863
|
|
3489
3489
|
},
|
|
3490
3490
|
{
|
|
@@ -212,7 +212,7 @@ async function main(args) {
|
|
|
212
212
|
console.log("[health-gate] Starting rollback...");
|
|
213
213
|
restorePreDeployBackup();
|
|
214
214
|
try {
|
|
215
|
-
const { rollbackStackUpdate, defaultStackPaths } = await import("./stack-update-
|
|
215
|
+
const { rollbackStackUpdate, defaultStackPaths } = await import("./stack-update-2F464W3N.js");
|
|
216
216
|
const paths = defaultStackPaths();
|
|
217
217
|
await rollbackStackUpdate({
|
|
218
218
|
manifestRef: paths.manifestRef,
|
|
@@ -120,6 +120,163 @@ ${remedy}
|
|
|
120
120
|
return gaps;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
// src/lib/retained-compose-health.ts
|
|
124
|
+
function parseRecords(raw) {
|
|
125
|
+
if (raw === null || !raw.trim())
|
|
126
|
+
throw new Error("--retain-compose health state unavailable");
|
|
127
|
+
const trimmed = raw.trim().replace(/^\ufeff/, "");
|
|
128
|
+
let records;
|
|
129
|
+
try {
|
|
130
|
+
const parsed = JSON.parse(trimmed);
|
|
131
|
+
records = Array.isArray(parsed) ? parsed : [parsed];
|
|
132
|
+
} catch {
|
|
133
|
+
try {
|
|
134
|
+
records = trimmed.split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
135
|
+
} catch {
|
|
136
|
+
throw new Error("--retain-compose health state could not be parsed");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (records.length === 0 || records.some(
|
|
140
|
+
(record) => !record || typeof record !== "object" || Array.isArray(record)
|
|
141
|
+
)) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
"--retain-compose health state contains no usable containers"
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return records;
|
|
147
|
+
}
|
|
148
|
+
function parseRetainedContainerHealth(raw) {
|
|
149
|
+
const containers = /* @__PURE__ */ new Map();
|
|
150
|
+
for (const record of parseRecords(raw)) {
|
|
151
|
+
const service = String(record.Service ?? "").trim();
|
|
152
|
+
if (!service || containers.has(service))
|
|
153
|
+
throw new Error(
|
|
154
|
+
"--retain-compose health state has missing or duplicate service names"
|
|
155
|
+
);
|
|
156
|
+
containers.set(service, {
|
|
157
|
+
service,
|
|
158
|
+
id: String(record.ID ?? "").trim(),
|
|
159
|
+
state: String(record.State ?? "").trim().toLowerCase(),
|
|
160
|
+
health: String(record.Health ?? "").trim().toLowerCase(),
|
|
161
|
+
restartCount: record.RestartCount === void 0 || record.RestartCount === null ? null : Number(record.RestartCount),
|
|
162
|
+
exitCode: record.ExitCode === void 0 || record.ExitCode === null || String(record.ExitCode).trim() === "" ? null : Number.isInteger(Number(record.ExitCode)) ? Number(record.ExitCode) : null
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return containers;
|
|
166
|
+
}
|
|
167
|
+
function parseRenderedServices(renderedCompose) {
|
|
168
|
+
let services;
|
|
169
|
+
try {
|
|
170
|
+
const parsed = JSON.parse(renderedCompose ?? "");
|
|
171
|
+
if (!parsed.services || typeof parsed.services !== "object" || Array.isArray(parsed.services))
|
|
172
|
+
throw new Error();
|
|
173
|
+
services = parsed.services;
|
|
174
|
+
} catch {
|
|
175
|
+
throw new Error("--retain-compose rendered dependency graph unavailable");
|
|
176
|
+
}
|
|
177
|
+
return services;
|
|
178
|
+
}
|
|
179
|
+
function retainedCriticalServices(renderedCompose, selectedService, requiredCore, selectedCompanions = []) {
|
|
180
|
+
const services = parseRenderedServices(renderedCompose);
|
|
181
|
+
const critical = /* @__PURE__ */ new Set();
|
|
182
|
+
const visit = (service) => {
|
|
183
|
+
if (!services[service])
|
|
184
|
+
throw new Error(
|
|
185
|
+
`--retain-compose required service ${service} absent from rendered Compose`
|
|
186
|
+
);
|
|
187
|
+
if (critical.has(service)) return;
|
|
188
|
+
critical.add(service);
|
|
189
|
+
const deps = services[service].depends_on;
|
|
190
|
+
if (deps !== void 0 && !Array.isArray(deps) && (typeof deps !== "object" || deps === null)) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`--retain-compose dependencies for ${service} could not be parsed`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
const names = Array.isArray(deps) ? deps : Object.keys(deps ?? {});
|
|
196
|
+
for (const name of names) {
|
|
197
|
+
if (typeof name !== "string")
|
|
198
|
+
throw new Error(
|
|
199
|
+
`--retain-compose dependencies for ${service} could not be parsed`
|
|
200
|
+
);
|
|
201
|
+
visit(name);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
visit(selectedService);
|
|
205
|
+
for (const service of selectedCompanions) visit(service);
|
|
206
|
+
for (const service of requiredCore) visit(service);
|
|
207
|
+
return critical;
|
|
208
|
+
}
|
|
209
|
+
function retainedCompletedDependencies(renderedCompose, critical, selectedService, requiredCore, selectedCompanions = []) {
|
|
210
|
+
const services = parseRenderedServices(renderedCompose);
|
|
211
|
+
const inbound = /* @__PURE__ */ new Map();
|
|
212
|
+
for (const parent of critical) {
|
|
213
|
+
const dependencies = services[parent]?.depends_on;
|
|
214
|
+
if (!dependencies) continue;
|
|
215
|
+
const entries = Array.isArray(dependencies) ? dependencies.map((name) => [
|
|
216
|
+
String(name),
|
|
217
|
+
{ condition: "service_started" }
|
|
218
|
+
]) : Object.entries(dependencies);
|
|
219
|
+
for (const [name, value] of entries) {
|
|
220
|
+
if (!critical.has(name)) continue;
|
|
221
|
+
const condition = value && typeof value === "object" ? String(
|
|
222
|
+
value.condition ?? "service_started"
|
|
223
|
+
) : "service_started";
|
|
224
|
+
inbound.set(name, [...inbound.get(name) ?? [], condition]);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const roots = /* @__PURE__ */ new Set([selectedService, ...selectedCompanions, ...requiredCore]);
|
|
228
|
+
return new Set(
|
|
229
|
+
[...inbound].filter(
|
|
230
|
+
([name, conditions]) => !roots.has(name) && // Compose's omitted restart policy is "no". An explicit policy
|
|
231
|
+
// that restarts the job is not a completed one-shot dependency.
|
|
232
|
+
(services[name]?.restart === void 0 || services[name]?.restart === "no") && conditions.length > 0 && conditions.every(
|
|
233
|
+
(condition) => condition === "service_completed_successfully"
|
|
234
|
+
)
|
|
235
|
+
).map(([name]) => name)
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
function retainedContainerIsHealthy(container, completedDependency = false) {
|
|
239
|
+
return !!container && !!container.id && (completedDependency ? container.state === "exited" && container.exitCode === 0 : container.state === "running" && (container.health === "" || container.health === "healthy")) && (container.restartCount === null || Number.isFinite(container.restartCount) && container.restartCount <= 2);
|
|
240
|
+
}
|
|
241
|
+
function assessRetainedHealthBefore(witness) {
|
|
242
|
+
const failures = [];
|
|
243
|
+
for (const service of witness.critical) {
|
|
244
|
+
if (!retainedContainerIsHealthy(
|
|
245
|
+
witness.containers.get(service),
|
|
246
|
+
witness.completedDependencies?.has(service)
|
|
247
|
+
))
|
|
248
|
+
failures.push(service);
|
|
249
|
+
}
|
|
250
|
+
for (const [service, container] of witness.containers) {
|
|
251
|
+
if (!witness.critical.has(service) && !retainedContainerIsHealthy(container) && !container.id) {
|
|
252
|
+
failures.push(`${service} (unhealthy container identity unavailable)`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return failures;
|
|
256
|
+
}
|
|
257
|
+
function assessRetainedHealthAfter(before, after) {
|
|
258
|
+
const failures = [];
|
|
259
|
+
for (const service of before.critical) {
|
|
260
|
+
if (!retainedContainerIsHealthy(
|
|
261
|
+
after.get(service),
|
|
262
|
+
before.completedDependencies?.has(service)
|
|
263
|
+
))
|
|
264
|
+
failures.push(service);
|
|
265
|
+
}
|
|
266
|
+
for (const [service, current] of after) {
|
|
267
|
+
if (before.critical.has(service) || retainedContainerIsHealthy(current))
|
|
268
|
+
continue;
|
|
269
|
+
const prior = before.containers.get(service);
|
|
270
|
+
if (!prior || retainedContainerIsHealthy(prior) || !prior.id || prior.id !== current.id || prior.state !== current.state || prior.health !== current.health || prior.restartCount !== current.restartCount) {
|
|
271
|
+
failures.push(service);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
for (const service of before.containers.keys()) {
|
|
275
|
+
if (!after.has(service)) failures.push(`${service} (disappeared)`);
|
|
276
|
+
}
|
|
277
|
+
return [...new Set(failures)];
|
|
278
|
+
}
|
|
279
|
+
|
|
123
280
|
// src/lib/stack-lock-derive.ts
|
|
124
281
|
import { spawnSync } from "child_process";
|
|
125
282
|
var GIT_SHA_LABEL = "org.exe.git_sha";
|
|
@@ -2320,8 +2477,9 @@ function assertRetainedComposePlan(input, plan, liveComposeRaw, envRaw, processE
|
|
|
2320
2477
|
if (service.migrations || plan.release.migrations) {
|
|
2321
2478
|
throw new Error("--retain-compose cannot run service or release migrations");
|
|
2322
2479
|
}
|
|
2323
|
-
|
|
2324
|
-
|
|
2480
|
+
const composeServices = expandComposeServicesWithSidecars([service.composeService ?? name]);
|
|
2481
|
+
if (composeServices.length > 1 && !(name === "crm" && composeServices.length === 2 && composeServices[0] === "exe-crm" && composeServices[1] === "exe-crm-worker")) {
|
|
2482
|
+
throw new Error("--retain-compose requires a service with no unsupported companion sidecars");
|
|
2325
2483
|
}
|
|
2326
2484
|
if (!service.env || plan.changes.length !== 1 || plan.changes[0].service !== name || plan.changes[0].key !== service.env) {
|
|
2327
2485
|
throw new Error("--retain-compose requires exactly one selected image-pin change");
|
|
@@ -2342,16 +2500,17 @@ function assertRetainedComposePlan(input, plan, liveComposeRaw, envRaw, processE
|
|
|
2342
2500
|
} else if (config.composeOverrideSha256) {
|
|
2343
2501
|
throw new Error("--retain-compose refused: signed compose override is absent from the live stack");
|
|
2344
2502
|
}
|
|
2345
|
-
const
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
imageExpression
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2503
|
+
const baseImages = parseComposeServiceImages(liveComposeRaw);
|
|
2504
|
+
const overrideImages = liveOverrideRaw === null ? /* @__PURE__ */ new Map() : parseComposeServiceImages(liveOverrideRaw);
|
|
2505
|
+
const plannedEnv = parseEnv(patchEnv(envRaw, { [service.env]: plan.changes[0].after }));
|
|
2506
|
+
for (const composeService of composeServices) {
|
|
2507
|
+
const imageExpression = overrideImages.get(composeService) ?? baseImages.get(composeService);
|
|
2508
|
+
if (!imageExpression || !envRefsInImageExpression(imageExpression).includes(service.env)) {
|
|
2509
|
+
throw new Error(`--retain-compose refused: selected service ${composeService} compose image does not use its signed image pin`);
|
|
2510
|
+
}
|
|
2511
|
+
if (resolveComposeImageExpression(imageExpression, plannedEnv) !== plan.changes[0].after) {
|
|
2512
|
+
throw new Error(`--retain-compose refused: rendered selected service ${composeService} image differs from signed image pin`);
|
|
2513
|
+
}
|
|
2355
2514
|
}
|
|
2356
2515
|
const env = parseEnv(envRaw);
|
|
2357
2516
|
const monitorKey = env.get("EXE_MONITOR_KEY") ?? "";
|
|
@@ -3791,18 +3950,20 @@ ${lines.join("\n")}`
|
|
|
3791
3950
|
if (options.retainCompose) {
|
|
3792
3951
|
const [name, service] = Object.entries(plan.release.services)[0];
|
|
3793
3952
|
const composeArgs2 = composeArgsForStack(options.composeFile, options.envFile);
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3953
|
+
for (const target of expandComposeServicesWithSidecars([service.composeService ?? name])) {
|
|
3954
|
+
assertRetainedComposeRunningService(
|
|
3955
|
+
composeArgs2,
|
|
3956
|
+
target,
|
|
3957
|
+
options.composePsCapture ?? defaultComposePsCapture
|
|
3958
|
+
);
|
|
3959
|
+
assertRetainedComposeRenderedImage(
|
|
3960
|
+
composeArgs2,
|
|
3961
|
+
target,
|
|
3962
|
+
service.env,
|
|
3963
|
+
plan.changes[0].after,
|
|
3964
|
+
options.composeConfigCapture ?? defaultComposeConfigCapture
|
|
3965
|
+
);
|
|
3966
|
+
}
|
|
3806
3967
|
}
|
|
3807
3968
|
const hostState = listHostComposeServices(
|
|
3808
3969
|
composeArgsForStack(options.composeFile, options.envFile),
|
|
@@ -3953,6 +4114,25 @@ ${lines.join("\n")}`
|
|
|
3953
4114
|
options.preDeployBackup(plan.targetVersion);
|
|
3954
4115
|
} catch {
|
|
3955
4116
|
}
|
|
4117
|
+
let retainedHealthBaseline;
|
|
4118
|
+
if (options.retainCompose) {
|
|
4119
|
+
const [selectedName, selectedEntry] = Object.entries(plan.release.services)[0];
|
|
4120
|
+
const selected = selectedEntry.composeService ?? selectedName;
|
|
4121
|
+
const selectedCompanions = expandComposeServicesWithSidecars([selected]).slice(1);
|
|
4122
|
+
const requiredCore = Object.entries(targetRelease.services).filter(([, service]) => service.required === true && service.category === "core").map(([name, service]) => service.composeService ?? name);
|
|
4123
|
+
const args = composeArgsForStack(options.composeFile, options.envFile);
|
|
4124
|
+
const rendered = (options.composeConfigCapture ?? defaultComposeConfigCapture)(args, process.env);
|
|
4125
|
+
const critical = retainedCriticalServices(rendered, selected, requiredCore, selectedCompanions);
|
|
4126
|
+
const completedDependencies = retainedCompletedDependencies(rendered, critical, selected, requiredCore, selectedCompanions);
|
|
4127
|
+
const rawHealth = (options.composePsCapture ?? defaultComposePsCapture)(args);
|
|
4128
|
+
retainedHealthBaseline = { critical, completedDependencies, containers: parseRetainedContainerHealth(rawHealth) };
|
|
4129
|
+
const failures = assessRetainedHealthBefore(retainedHealthBaseline);
|
|
4130
|
+
if (failures.length > 0) throw new Error(`--retain-compose health gate failed before update: ${failures.join(", ")}`);
|
|
4131
|
+
const preexisting = [...retainedHealthBaseline.containers.values()].filter((container) => !critical.has(container.service) && (container.state !== "running" || container.health === "unhealthy" || container.restartCount !== null && container.restartCount > 2));
|
|
4132
|
+
for (const container of preexisting) {
|
|
4133
|
+
console.warn(`[stack-update] Pre-existing unrelated unhealthy container (unchanged identity required): ${container.service}`);
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
3956
4136
|
const stackDir = path.dirname(options.envFile);
|
|
3957
4137
|
const configBackup = preBootstrapBackup ?? backupStackConfig(stackDir, options.composeFile, envRaw, now);
|
|
3958
4138
|
const updateBackupDir = configBackup.updateBackupDir;
|
|
@@ -4296,7 +4476,7 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
|
|
|
4296
4476
|
for (const service of restartOrder) {
|
|
4297
4477
|
restartIndex++;
|
|
4298
4478
|
const endRestart = startPhase(`restart ${service}`);
|
|
4299
|
-
const manifestServiceName =
|
|
4479
|
+
const manifestServiceName = resolveOwningManifestService(service, plan.release.services);
|
|
4300
4480
|
if (bundledIsolation && (failedComposeServices.has(service) || failedManifestServices.has(resolveOwningManifestService(service, plan.release.services)))) {
|
|
4301
4481
|
continue;
|
|
4302
4482
|
}
|
|
@@ -4310,12 +4490,13 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
|
|
|
4310
4490
|
continue;
|
|
4311
4491
|
}
|
|
4312
4492
|
if (options.retainCompose) {
|
|
4493
|
+
const owner = plan.release.services[manifestServiceName];
|
|
4313
4494
|
assertRetainedComposeFilesUnchanged(options.composeFile, composeRaw, retainedOverrideHash);
|
|
4314
4495
|
assertRetainedComposeRunningService(composeArgs, service, options.composePsCapture ?? defaultComposePsCapture);
|
|
4315
4496
|
assertRetainedComposeRenderedImage(
|
|
4316
4497
|
composeArgs,
|
|
4317
4498
|
service,
|
|
4318
|
-
|
|
4499
|
+
owner.env,
|
|
4319
4500
|
plan.changes[0].after,
|
|
4320
4501
|
options.composeConfigCapture ?? defaultComposeConfigCapture
|
|
4321
4502
|
);
|
|
@@ -4426,6 +4607,15 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
|
|
|
4426
4607
|
endPhase3();
|
|
4427
4608
|
}
|
|
4428
4609
|
}
|
|
4610
|
+
if (retainedHealthBaseline) {
|
|
4611
|
+
const after = parseRetainedContainerHealth(
|
|
4612
|
+
(options.composePsCapture ?? defaultComposePsCapture)(composeArgs)
|
|
4613
|
+
);
|
|
4614
|
+
const regressions = assessRetainedHealthAfter(retainedHealthBaseline, after);
|
|
4615
|
+
if (regressions.length > 0) {
|
|
4616
|
+
throw new Error(`--retain-compose health regression after update: ${regressions.join(", ")}`);
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4429
4619
|
{
|
|
4430
4620
|
const cfIngressConfigPath = path.join(path.dirname(options.composeFile), "cloudflared", "config.yml");
|
|
4431
4621
|
const cfIngressConfigYml = existsSync(cfIngressConfigPath) ? readFileSync(cfIngressConfigPath, "utf8") : null;
|
|
@@ -4673,9 +4863,12 @@ Fix these before the stack is safe to operate.`);
|
|
|
4673
4863
|
const composeService = service.composeService ?? name;
|
|
4674
4864
|
try {
|
|
4675
4865
|
syncImagePinsWithProcessEnv(envRaw, [service.env]);
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4866
|
+
const restored = expandComposeServicesWithSidecars([composeService]);
|
|
4867
|
+
for (const target of restored) {
|
|
4868
|
+
exec("docker", [...composeArgs, "up", "-d", "--no-deps", target]);
|
|
4869
|
+
}
|
|
4870
|
+
console.error(`[stack-update] Scoped fallback restored ${restored.join(", ")} after rollback error: ${rbReason}`);
|
|
4871
|
+
rollbackDetail += `; scoped fallback restored ${restored.join(", ")}`;
|
|
4679
4872
|
} catch (scopedError) {
|
|
4680
4873
|
console.error(`[stack-update] Scoped fallback failed for ${composeService}: ${errorMessage(scopedError)}`);
|
|
4681
4874
|
rollbackDetail += `; scoped fallback failed for ${composeService}: ${errorMessage(scopedError)}`;
|
|
@@ -5484,6 +5677,11 @@ function loadDefaultPublicKey() {
|
|
|
5484
5677
|
}
|
|
5485
5678
|
|
|
5486
5679
|
export {
|
|
5680
|
+
parseRetainedContainerHealth,
|
|
5681
|
+
retainedCriticalServices,
|
|
5682
|
+
retainedCompletedDependencies,
|
|
5683
|
+
retainedContainerIsHealthy,
|
|
5684
|
+
assessRetainedHealthBefore,
|
|
5487
5685
|
resolveServiceHealthTimeout,
|
|
5488
5686
|
serviceSelectionPath,
|
|
5489
5687
|
loadServiceSelection,
|
|
@@ -138,7 +138,7 @@ async function assertManifestSignature(manifest, publicKeyPem) {
|
|
|
138
138
|
"manifest carries no `signature`. Fleets that enforce EXE_STACK_PUBLIC_KEY reject unsigned manifests, so publishing one would ship a release no host can install. Re-sign with the custodied key: node scripts/sign-stack-manifest.mjs deploy/stack-manifests/v0.9.json"
|
|
139
139
|
);
|
|
140
140
|
}
|
|
141
|
-
const { verifyStackManifestSignature } = await import("./stack-update-
|
|
141
|
+
const { verifyStackManifestSignature } = await import("./stack-update-2F464W3N.js");
|
|
142
142
|
try {
|
|
143
143
|
verifyStackManifestSignature(manifest, publicKeyPem);
|
|
144
144
|
} catch (err) {
|
package/dist/hooks/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"generatedAt": "2026-09-23T23:35:24.596Z",
|
|
4
4
|
"hashes": {
|
|
5
5
|
"bug-report-worker.js": "7c5f5951fe30192e374d9047c3a7f2975a879a5951457aafea5db95d5d6be6af",
|
|
6
6
|
"codex-stop-task-finalizer.js": "f963449525e068ebf3f04a2950adf21464de71774b60165a84d69621a5eca30a",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
writeSignedStackManifest
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-PS77SULS.js";
|
|
5
|
+
import "./chunk-DYFTZWG2.js";
|
|
6
6
|
import "./chunk-EC5H53YD.js";
|
|
7
7
|
import "./chunk-YYRI2AGK.js";
|
|
8
8
|
import "./chunk-L25OAFOU.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
writeSignedStackManifest
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-PS77SULS.js";
|
|
5
|
+
import "./chunk-DYFTZWG2.js";
|
|
6
6
|
import "./chunk-EC5H53YD.js";
|
|
7
7
|
import "./chunk-YYRI2AGK.js";
|
|
8
8
|
import "./chunk-L25OAFOU.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
writeSignedStackManifest
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-PS77SULS.js";
|
|
5
|
+
import "./chunk-DYFTZWG2.js";
|
|
6
6
|
import "./chunk-EC5H53YD.js";
|
|
7
7
|
import {
|
|
8
8
|
assertPreflightGate,
|
|
@@ -2028,7 +2028,7 @@ async function warmProxyCache(imageRef) {
|
|
|
2028
2028
|
async function resolveImageGitShaLabel(ref, dryRun) {
|
|
2029
2029
|
if (!ref || dryRun) return void 0;
|
|
2030
2030
|
try {
|
|
2031
|
-
const { readProvenanceLabels } = await import("./stack-generate-manifest-
|
|
2031
|
+
const { readProvenanceLabels } = await import("./stack-generate-manifest-H6LJQ2HV.js");
|
|
2032
2032
|
const labels = await readProvenanceLabels(ref);
|
|
2033
2033
|
return labels["org.exe.git_sha"] ?? labels["org.opencontainers.image.revision"];
|
|
2034
2034
|
} catch (err) {
|
|
@@ -132,7 +132,7 @@ import {
|
|
|
132
132
|
waitForHttpOk,
|
|
133
133
|
warnUnmanagedManifestServices,
|
|
134
134
|
withStackUpdateOperationLock
|
|
135
|
-
} from "./chunk-
|
|
135
|
+
} from "./chunk-DYFTZWG2.js";
|
|
136
136
|
import "./chunk-EC5H53YD.js";
|
|
137
137
|
import "./chunk-YYRI2AGK.js";
|
|
138
138
|
import "./chunk-L25OAFOU.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askexenow/exe-os",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.394",
|
|
4
4
|
"description": "AI employee operating system \u2014 persistent memory, task management, and multi-agent coordination for Claude Code.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|