@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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 2,
3
- "generated_at": "2026-09-23T21:11:44.524Z",
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": "57b256c77dad69f65194abc99fe8b50ce2bfe8d48d211eec73e563c128036147",
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": "3ee14439bf46ca5a72a2a72a6130b9248eabfdd670df0da054c0123c1e954054",
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": "ee70fb51e77796565a2d1864764e4865cf24a5671c9753fe73ac198517fdb7ed",
553
- "bytes": 128550
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": "17c6d47cae46484f15223cfd9cd9e5b07db13506c0579bf932aff4eed897e252",
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-PRCJG3PK.js",
1667
- "sha256": "cd4c509a50c7fa4c72cb7a1d62c216f49024b671c65c8b032d4ef664b7da49e7",
1668
- "bytes": 40844
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": "ba4ff8f10ee0055e5a4c3ea79bc8ea9c7f0154d0d7b53cdac2a7f72e901cdd03",
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": "6b15ba1cbdc4ee922891ad866daf9cc80cce74e01ccbc924e75960a52da8f9c0",
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-QWU7Q4MJ.js",
3467
- "sha256": "24720f91471c9be72ceb98ad60530cc267b389b7f6124cddb84070be1a7367b0",
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-26RFMNSB.js",
3477
- "sha256": "74b8353d0871bb8fd1b64c449498920d8ecfbe623ad76f65fdec038de34575de",
3476
+ "path": "stack-promote-4EF7RNIV.js",
3477
+ "sha256": "d9db50f30a0a97cbe56b4e566be8e9c1327d9ecb5c96a6a46967bf57ea76f8a9",
3478
3478
  "bytes": 7425
3479
3479
  },
3480
3480
  {
3481
- "path": "stack-release-7YHSLY7J.js",
3482
- "sha256": "27806d515012b3af2d4c85f87f6dfd439ba46e01dac917cbf95e60cf0fcece2c",
3481
+ "path": "stack-release-YCNRPK6R.js",
3482
+ "sha256": "bd26b47386fa7849325c313b30e2eb0ee8f0ba77ea8dcaf95256fb0241a824bd",
3483
3483
  "bytes": 117550
3484
3484
  },
3485
3485
  {
3486
- "path": "stack-update-DTMW7GMW.js",
3487
- "sha256": "d05d7f8051ba0503d7ffa650eb5d85b08e841d94c08263c07eea5b0874770d99",
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-DTMW7GMW.js");
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
- if (expandComposeServicesWithSidecars([service.composeService ?? name]).length !== 1) {
2324
- throw new Error("--retain-compose requires a service with no companion sidecars");
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 imageExpression = liveOverrideRaw === null ? parseComposeServiceImages(liveComposeRaw).get(service.composeService ?? name) : parseComposeServiceImages(liveOverrideRaw).get(service.composeService ?? name) ?? parseComposeServiceImages(liveComposeRaw).get(service.composeService ?? name);
2346
- if (!imageExpression || !envRefsInImageExpression(imageExpression).includes(service.env)) {
2347
- throw new Error("--retain-compose refused: selected service compose image does not use its signed image pin");
2348
- }
2349
- const plannedImage = resolveComposeImageExpression(
2350
- imageExpression,
2351
- parseEnv(patchEnv(envRaw, { [service.env]: plan.changes[0].after }))
2352
- );
2353
- if (plannedImage !== plan.changes[0].after) {
2354
- throw new Error("--retain-compose refused: rendered selected service image differs from signed image pin");
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
- assertRetainedComposeRunningService(
3795
- composeArgs2,
3796
- service.composeService ?? name,
3797
- options.composePsCapture ?? defaultComposePsCapture
3798
- );
3799
- assertRetainedComposeRenderedImage(
3800
- composeArgs2,
3801
- service.composeService ?? name,
3802
- service.env,
3803
- plan.changes[0].after,
3804
- options.composeConfigCapture ?? defaultComposeConfigCapture
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 = Object.entries(plan.release.services).find(([name, svc]) => (svc.composeService ?? name) === service)?.[0] ?? service;
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
- plan.release.services[manifestServiceName].env,
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
- exec("docker", [...composeArgs, "up", "-d", "--no-deps", composeService]);
4677
- console.error(`[stack-update] Scoped fallback restored ${composeService} after rollback error: ${rbReason}`);
4678
- rollbackDetail += `; scoped fallback restored ${composeService}`;
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,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  canonicalizeStackManifest
3
- } from "./chunk-3SXU7KLW.js";
3
+ } from "./chunk-DYFTZWG2.js";
4
4
 
5
5
  // src/lib/stack-manifest-write.ts
6
6
  import { readFileSync, statSync, writeFileSync } from "fs";
@@ -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-DTMW7GMW.js");
141
+ const { verifyStackManifestSignature } = await import("./stack-update-2F464W3N.js");
142
142
  try {
143
143
  verifyStackManifestSignature(manifest, publicKeyPem);
144
144
  } catch (err) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "generatedAt": "2026-09-23T21:11:44.253Z",
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",
@@ -11,7 +11,7 @@ import {
11
11
  resolveRegistryProxyBuildIdentity,
12
12
  resolveStackManifestChannel,
13
13
  runRegistryProxy
14
- } from "../chunk-PRCJG3PK.js";
14
+ } from "../chunk-RLUDQYOA.js";
15
15
  import "../chunk-MLKGABMK.js";
16
16
  export {
17
17
  SERVED_VERSION_PATH,
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  writeSignedStackManifest
4
- } from "./chunk-TVSIS36M.js";
5
- import "./chunk-3SXU7KLW.js";
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-TVSIS36M.js";
5
- import "./chunk-3SXU7KLW.js";
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-TVSIS36M.js";
5
- import "./chunk-3SXU7KLW.js";
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-QWU7Q4MJ.js");
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-3SXU7KLW.js";
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.392",
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",