@askexenow/exe-os 0.9.392 → 0.9.393

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.
@@ -4909,11 +4909,144 @@
4909
4909
  "composeSha256": "220b251375f372107e8d8d54a9c3e4ceab1f57aae80f4babf2933c3887532263",
4910
4910
  "composeOverrideSha256": "af295ca2f75b85788416e4df51cab86d8b773bf4cafe6cf6df9c7c26b9a42d6d"
4911
4911
  }
4912
+ },
4913
+ "0.9.53": {
4914
+ "version": "0.9.53",
4915
+ "releasedAt": "2026-09-23T21:55:18.488Z",
4916
+ "npmVersion": "0.9.393",
4917
+ "notes": "Dashboard-only Exe dogfood canary replacing unapplied 0.9.52: npm 0.9.393 scopes retained-Compose health to Dashboard, required core, and rendered dependencies while recording unchanged unrelated red containers and failing new regressions. From live stack 0.9.50, update only Dashboard to v0.9.392. Preserve CRM v0.9.66, Auth v0.6.10, and every other service pin. Signed live base and override Compose bytes are unchanged; no config assets are delivered. Public DEMO remains disabled pending separate CRM rollout.",
4918
+ "services": {
4919
+ "exe-db": {
4920
+ "image": "update.askexe.com/askexe/pgvector:0.8.0-pg16@sha256:a132765ec351c65111b5b675928a3a0515a466a40f97277329db8b8209ad8bc9",
4921
+ "env": "EXE_DB_IMAGE",
4922
+ "composeService": "exe-db",
4923
+ "required": true,
4924
+ "category": "core",
4925
+ "description": "Postgres (pgvector) — digest-pinned (bug 32d6e02f; was tag-only in 0.9.25, regressed from 0.9.24)"
4926
+ },
4927
+ "exe-os": {
4928
+ "image": "update.askexe.com/askexe/exe-os:v0.9.382@sha256:266a59a482865eab9f79497999acb33c1fe1868ec54ea5cd1ce971c492f11bd6",
4929
+ "env": "EXE_OS_IMAGE_TAG",
4930
+ "composeService": "exe-os",
4931
+ "healthUrl": "http://127.0.0.1:8765/health",
4932
+ "required": true,
4933
+ "category": "core",
4934
+ "description": "exe-os daemon + MCP server"
4935
+ },
4936
+ "crm": {
4937
+ "image": "update.askexe.com/askexe/exe-crm:v0.9.66@sha256:38bbae74ddd33dbbd62175a46ad0d3f99ef6c671b0c2187e35398ca9c504c3eb",
4938
+ "env": "CRM_IMAGE_TAG",
4939
+ "composeService": "exe-crm",
4940
+ "healthUrl": "http://127.0.0.1:3000/healthz",
4941
+ "category": "app",
4942
+ "gitSha": "a3d107483304d10b05c9f1ee5e1b0182c4628e65",
4943
+ "description": "Central authentication and recovery with server-validated shared sessions for CRM web and worker."
4944
+ },
4945
+ "wiki": {
4946
+ "image": "update.askexe.com/askexe/exe-wiki:v0.9.37@sha256:8fa22e77c0742fbb690f7b4c8431a186692a20ebd335fb141782df4289dc337f",
4947
+ "env": "WIKI_IMAGE_TAG",
4948
+ "composeService": "exe-wiki",
4949
+ "healthUrl": "http://127.0.0.1:3001/api/ping",
4950
+ "category": "app",
4951
+ "description": "Central hosted authentication and recovery with server-validated shared sessions, membership enforcement, and no product-local credential form.",
4952
+ "gitSha": "713a4370c2b641b8f8b07fd1e7390afa79a9c931"
4953
+ },
4954
+ "gateway": {
4955
+ "image": "update.askexe.com/askexe/exe-gateway:v2026.08.07-c15f093-stamped@sha256:1bc11a2a0ad1f3929d59d2685696f22a72042b9fca9c75e15c02dc7d3c640105",
4956
+ "env": "GATEWAY_IMAGE_TAG",
4957
+ "composeService": "exe-gateway",
4958
+ "healthUrl": "http://127.0.0.1:3100/health",
4959
+ "category": "app",
4960
+ "description": "WhatsApp/messaging gateway — provenance-stamped rebuild of main (outbound message persistence fix); pinned by digest, dated immutable tag.",
4961
+ "gitSha": "c15f093a0f59730bd5c2c73791ff77059dfa7cc1"
4962
+ },
4963
+ "monitorAgent": {
4964
+ "image": "update.askexe.com/askexe/exe-monitor-agent:v2026.08.09-10660eb4-stamped@sha256:29fbd445f79484df04f000603e4ff445e76f0380f86d687a0033e3a0ef7d1b7a",
4965
+ "env": "MONITOR_AGENT_IMAGE_TAG",
4966
+ "composeService": "exe-monitor-agent",
4967
+ "category": "monitoring",
4968
+ "gitSha": "10660eb4abdeffbcba2aad13549aa58e208ac7a3"
4969
+ },
4970
+ "monitorHub": {
4971
+ "image": "update.askexe.com/askexe/exe-monitor-hub:v2026.08.09-10660eb4-stamped@sha256:c9a9218c2713b8dffea7e1cf8c0702a614657d311a7a4e821c6a543bab29bb97",
4972
+ "env": "MONITOR_HUB_IMAGE_TAG",
4973
+ "composeService": "exe-monitor-hub",
4974
+ "healthUrl": "http://127.0.0.1:8090/api/health",
4975
+ "category": "monitoring",
4976
+ "gitSha": "10660eb4abdeffbcba2aad13549aa58e208ac7a3"
4977
+ },
4978
+ "auth": {
4979
+ "image": "update.askexe.com/askexe/exe-auth:v0.6.10@sha256:0260349962d087cd3c33fed1d362029416e82ae9cc60abca7969beac2cb8639f",
4980
+ "env": "AUTH_IMAGE_TAG",
4981
+ "composeService": "exe-auth",
4982
+ "healthUrl": "http://127.0.0.1:3300/",
4983
+ "category": "auth",
4984
+ "description": "GoTrue SSO and privileged license authority bridge; stateless auth proxy.",
4985
+ "gitSha": "29fe7015d90b80c94db3a1e3904852b934071711"
4986
+ },
4987
+ "erp": {
4988
+ "image": "update.askexe.com/askexe/exe-erp:v0.3.17@sha256:ef61b59d94c04189fe2975045a3816d2fecc8efe3027e0ee8c17d0fa2efdbc3b",
4989
+ "env": "ERP_IMAGE_TAG",
4990
+ "composeService": "exe-erp",
4991
+ "healthUrl": "http://127.0.0.1:8069/api/method/ping",
4992
+ "healthHost": "localhost",
4993
+ "required": false,
4994
+ "category": "app",
4995
+ "description": "Enterprise Resource Planning with central hosted authentication and subject-bound organization admission.",
4996
+ "gitSha": "a769ec5e0ae53bc5944715845869715ebbffd63a"
4997
+ },
4998
+ "gotrue": {
4999
+ "image": "update.askexe.com/askexe/gotrue:v2.172.1@sha256:b0dd7aa28dd1f9a8bd31f136c8b912661f701d9b863171e5843cb08f18f49de4",
5000
+ "composeService": "gotrue",
5001
+ "healthUrl": "http://127.0.0.1:9999/health",
5002
+ "category": "infra"
5003
+ },
5004
+ "redis": {
5005
+ "image": "update.askexe.com/askexe/redis:7.4-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99",
5006
+ "composeService": "redis",
5007
+ "category": "infra"
5008
+ },
5009
+ "clickhouse": {
5010
+ "image": "update.askexe.com/askexe/clickhouse-server:24.8.4.13-alpine@sha256:88a45f9e328549b2579256c46ee38e5c0e25ae58303d9eb6d9c7ed8d6d2bbf3c",
5011
+ "composeService": "clickhouse",
5012
+ "category": "infra"
5013
+ },
5014
+ "cloudflared": {
5015
+ "image": "update.askexe.com/askexe/cloudflared:2026-07@sha256:ba461b8aa9c042156dbd39c38657fe7431bafa063220eab8d5330a523863da9f",
5016
+ "composeService": "cloudflared",
5017
+ "category": "infra"
5018
+ },
5019
+ "exe-sso-edge": {
5020
+ "image": "update.askexe.com/askexe/nginx:alpine@sha256:20316569d8f81a160065d7d2a5eeffc7ca97d79022462ee255fd23fa103a6b5c",
5021
+ "composeService": "exe-sso-edge",
5022
+ "category": "infra"
5023
+ },
5024
+ "exe-otel-collector": {
5025
+ "image": "update.askexe.com/askexe/otel-collector-contrib:0.114.0@sha256:37fa87091cfaaec7234a27e4e395a40c31c2bfaea97a349a4afef6d9e9681197",
5026
+ "composeService": "exe-otel-collector",
5027
+ "category": "infra"
5028
+ },
5029
+ "dashboard": {
5030
+ "image": "update.askexe.com/askexe/exe-dashboard:v0.9.392@sha256:d151441554c00e216084bd1caf2278e8c41fde188c039e0f167e158a511cbeec",
5031
+ "env": "DASHBOARD_IMAGE_TAG",
5032
+ "composeService": "exe-dashboard",
5033
+ "healthUrl": "http://127.0.0.1:3301/health",
5034
+ "required": false,
5035
+ "category": "optional",
5036
+ "description": "Customer dashboard at dashboard.<apex> — credits, usage, API keys, billing",
5037
+ "gitSha": "e11b25ad567389440da8ed5e566a9c59bce24123"
5038
+ }
5039
+ },
5040
+ "rolloutStage": "canary",
5041
+ "config": {
5042
+ "composeSha256": "220b251375f372107e8d8d54a9c3e4ceab1f57aae80f4babf2933c3887532263",
5043
+ "composeOverrideSha256": "af295ca2f75b85788416e4df51cab86d8b773bf4cafe6cf6df9c7c26b9a42d6d"
5044
+ }
4912
5045
  }
4913
5046
  },
4914
5047
  "signature": {
4915
5048
  "keyId": "exe-stack-1",
4916
5049
  "alg": "ed25519",
4917
- "signature": "xdgiq8L3up4Mr/AAKup4KFb0istz4Mw80ACvfp1K69hy5NXCou/JX3/eyCfgp0ZP20nbzjjEDfYvHATXQ36XCQ=="
5050
+ "signature": "sr79I2vY2ZVjMGGV/aGJp5i3sT3+nZX0urNqH7jw10Zu1yZNZa62y1973V6b2N5DKNwjE7aZHlQn5aF6nouBDg=="
4918
5051
  }
4919
5052
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "verified_at": "2026-09-23T21:11:44.589Z",
4
- "manifest_sha256": "7001b01788ede1ea0aca7f3688cacaa8a618cac6faf6aa49894111e38765e4c5",
5
- "package_version": "0.9.392"
3
+ "verified_at": "2026-09-23T22:15:04.008Z",
4
+ "manifest_sha256": "c3c456977d8ec9e709146a39b984af444449c4bc1716922aa8323e33f06bdfa9",
5
+ "package_version": "0.9.393"
6
6
  }
package/dist/bin/cli.js CHANGED
@@ -798,14 +798,14 @@ ${JSON.stringify(stormAlert, null, 2)}`);
798
798
  const { runStackUpdateCli } = await import("./stack-update.js");
799
799
  await runStackUpdateCli(args.slice(1));
800
800
  } else if (args[0] === "release") {
801
- const { parseReleaseArgs, runStackRelease } = await import("../stack-release-7YHSLY7J.js");
801
+ const { parseReleaseArgs, runStackRelease } = await import("../stack-release-YEO6BFVR.js");
802
802
  const releaseFlags = parseReleaseArgs(args.slice(1));
803
803
  await runStackRelease(releaseFlags);
804
804
  } else if (args[0] === "stack-promote") {
805
- const { runStackPromoteCli } = await import("../stack-promote-26RFMNSB.js");
805
+ const { runStackPromoteCli } = await import("../stack-promote-EAPCVGKJ.js");
806
806
  await runStackPromoteCli(args.slice(1));
807
807
  } else if (args[0] === "stack-generate-manifest") {
808
- const { runStackGenerateManifestCli } = await import("../stack-generate-manifest-QWU7Q4MJ.js");
808
+ const { runStackGenerateManifestCli } = await import("../stack-generate-manifest-XZ534AJH.js");
809
809
  process.exit(await runStackGenerateManifestCli(args.slice(1)));
810
810
  } else if (args[0] === "repo-truth") {
811
811
  const { runRepoTruthCli } = await import("./exe-repo-truth.js");
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  registryProxyOptionsFromEnv,
4
4
  runRegistryProxy
5
- } from "../chunk-PRCJG3PK.js";
5
+ } from "../chunk-NJ57AHX4.js";
6
6
  import {
7
7
  isMainModule
8
8
  } from "../chunk-6Y4B3QF6.js";
@@ -7,6 +7,7 @@ import {
7
7
  assertRetainedComposeRenderedImage,
8
8
  assertRetainedComposeRunningService,
9
9
  assertSignedManifestDeliveryGate,
10
+ assessRetainedHealthBefore,
10
11
  backupStackConfig,
11
12
  bootstrapStackHost,
12
13
  builtinStackManifestPublicKeyPath,
@@ -23,6 +24,7 @@ import {
23
24
  loadStackManifestWithFallback,
24
25
  parseComposeVolumeNames,
25
26
  parseEnv,
27
+ parseRetainedContainerHealth,
26
28
  patchEnv,
27
29
  printDanglingVolumes,
28
30
  printVolumeCleanupResult,
@@ -33,6 +35,9 @@ import {
33
35
  reportStackManifestSource,
34
36
  requiredImagePinKeys,
35
37
  resolveComposeProjectName,
38
+ retainedCompletedDependencies,
39
+ retainedContainerIsHealthy,
40
+ retainedCriticalServices,
36
41
  runScopedServiceSelection,
37
42
  runStackUpdate,
38
43
  saveServiceSelection,
@@ -41,7 +46,7 @@ import {
41
46
  verifyManifestImagesAvailable,
42
47
  warnUnmanagedManifestServices,
43
48
  withStackUpdateOperationLock
44
- } from "../chunk-3SXU7KLW.js";
49
+ } from "../chunk-WMGS5U2J.js";
45
50
  import "../chunk-EC5H53YD.js";
46
51
  import {
47
52
  runVerifyStack
@@ -57,7 +62,7 @@ import {
57
62
  import {
58
63
  logResult,
59
64
  runHealthGate
60
- } from "../chunk-33CHRORT.js";
65
+ } from "../chunk-POBFVHIS.js";
61
66
  import "../chunk-L25OAFOU.js";
62
67
  import "../chunk-RT57D7C4.js";
63
68
  import "../chunk-FTZET5YX.js";
@@ -2076,7 +2081,25 @@ function readInstalledVersion(lockFile) {
2076
2081
  return void 0;
2077
2082
  }
2078
2083
  }
2079
- function printChanges(changes, composeFile, envFile) {
2084
+ function printChanges(changes, composeFile, envFile, retainedHealth) {
2085
+ if (retainedHealth) {
2086
+ if (changes.length > 0) {
2087
+ console.log("Planned image tag changes:");
2088
+ for (const c of changes) console.log(` - ${c.service}: ${c.key}
2089
+ ${c.before ?? "<unset>"} \u2192 ${c.after}`);
2090
+ }
2091
+ console.log("\nCurrent container health (selected service and required dependencies are gates):");
2092
+ for (const [service, container] of retainedHealth.containers) {
2093
+ const critical = retainedHealth.critical.has(service);
2094
+ const healthy = retainedContainerIsHealthy(container, retainedHealth.completedDependencies?.has(service));
2095
+ const label = critical ? "required for this update" : "unrelated, unchanged baseline";
2096
+ console.log(` ${healthy ? "\u2705" : critical ? "\u274C" : "\u26A0\uFE0F"} ${service}: ${container.state}${container.health ? ` (health: ${container.health})` : ""} \u2014 ${label}`);
2097
+ }
2098
+ const failures = assessRetainedHealthBefore(retainedHealth);
2099
+ if (failures.length > 0) console.log(`
2100
+ \u{1F534} Retained-Compose health gate failed: ${failures.join(", ")}`);
2101
+ return failures.length;
2102
+ }
2080
2103
  if (changes.length === 0) {
2081
2104
  const running2 = areCliContainersRunning(composeFile, envFile);
2082
2105
  if (running2) {
@@ -2111,6 +2134,23 @@ function printChanges(changes, composeFile, envFile) {
2111
2134
  }
2112
2135
  return 0;
2113
2136
  }
2137
+ function captureRetainedHealthForCli(composeFile, envFile, selectedService, requiredCore) {
2138
+ const args = composeArgsForStack(composeFile, envFile);
2139
+ const config = spawnSync3("docker", [...args, "config", "--format", "json"], {
2140
+ encoding: "utf8",
2141
+ stdio: ["pipe", "pipe", "pipe"],
2142
+ timeout: 2e4
2143
+ });
2144
+ const critical = retainedCriticalServices(config.status === 0 ? config.stdout : null, selectedService, requiredCore);
2145
+ const completedDependencies = retainedCompletedDependencies(config.stdout, critical, selectedService, requiredCore);
2146
+ const ps = spawnSync3("docker", [...args, "ps", "--all", "--format", "json"], {
2147
+ encoding: "utf8",
2148
+ stdio: ["pipe", "pipe", "pipe"],
2149
+ timeout: 2e4
2150
+ });
2151
+ const containers = parseRetainedContainerHealth(ps.status === 0 ? ps.stdout : null);
2152
+ return { critical, completedDependencies, containers };
2153
+ }
2114
2154
  function areCliContainersRunning(composeFile, envFile) {
2115
2155
  try {
2116
2156
  const result = spawnSync3("docker", [...composeArgsForStack(composeFile, envFile), "ps", "-q"], {
@@ -2511,7 +2551,16 @@ async function main(args = process.argv.slice(2)) {
2511
2551
  console.log("");
2512
2552
  }
2513
2553
  }
2514
- const unhealthyCount = printChanges(plan.changes, opts.composeFile, opts.envFile);
2554
+ const retainedHealth = opts.retainCompose && targetRelease ? captureRetainedHealthForCli(
2555
+ opts.composeFile,
2556
+ opts.envFile,
2557
+ Object.values(plan.release.services)[0].composeService ?? opts.serviceName,
2558
+ Object.entries(targetRelease.services).filter(([, service]) => service.required === true && service.category === "core").map(([name, service]) => service.composeService ?? name)
2559
+ ) : void 0;
2560
+ const unhealthyCount = printChanges(plan.changes, opts.composeFile, opts.envFile, retainedHealth);
2561
+ if (opts.retainCompose && unhealthyCount > 0 && !opts.check && !opts.dryRun) {
2562
+ throw new Error("--retain-compose refused: selected service or required dependency is unhealthy");
2563
+ }
2515
2564
  printBreaking(plan.breakingChanges);
2516
2565
  if (opts.verifyImages) {
2517
2566
  const started = Date.now();
@@ -2704,6 +2753,7 @@ export {
2704
2753
  isProductionControlPlaneApply,
2705
2754
  manifestSignatureSkipBanner,
2706
2755
  parseArgs as parseStackUpdateArgs,
2756
+ printChanges,
2707
2757
  reportPartialRun,
2708
2758
  resolveManifestSignaturePolicy,
2709
2759
  main as runStackUpdateCli,
@@ -8,7 +8,7 @@ import {
8
8
  logResult,
9
9
  main,
10
10
  runHealthGate
11
- } from "../chunk-33CHRORT.js";
11
+ } from "../chunk-POBFVHIS.js";
12
12
  import "../chunk-MLKGABMK.js";
13
13
  export {
14
14
  checkCRM,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 2,
3
- "generated_at": "2026-09-23T21:11:44.524Z",
3
+ "generated_at": "2026-09-23T22:15:03.947Z",
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": "a26f29a3a9652a702b0bdafd1bbceb13389b3fffc4d49477eae926e855a59ee6",
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": "e4b41eb986a2f7baf4371bb8a1bd5ec046ef6741c5df16a1f7aaeda5cb946675",
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": "45e9139c3b040aff8c4e863ada54ca75c58c4c14b7c8571f67a599372a0978bb",
553
+ "bytes": 131290
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": "1b4e8961edf1b549fc3900950e8ddd6bf1bff6dc02fed969028f31b4c730a3d3",
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",
@@ -1537,6 +1527,11 @@
1537
1527
  "sha256": "cfdcf9141d53bd73b7b8cc3a4097773d1f854a9dffb7a81324f2782c7d70fe6c",
1538
1528
  "bytes": 14891
1539
1529
  },
1530
+ {
1531
+ "path": "chunk-NJ57AHX4.js",
1532
+ "sha256": "37cd6a8a4df230eca98e5fc2792fa39027c3ca8cbfbb5438f87680a1c996a755",
1533
+ "bytes": 40844
1534
+ },
1540
1535
  {
1541
1536
  "path": "chunk-NKPPPX6I.js",
1542
1537
  "sha256": "4a5f8acc2c7b70708de8b68c7c3b7b3f5981395f2bd1a4db3b3b7151769910db",
@@ -1663,9 +1658,9 @@
1663
1658
  "bytes": 3317
1664
1659
  },
1665
1660
  {
1666
- "path": "chunk-PRCJG3PK.js",
1667
- "sha256": "cd4c509a50c7fa4c72cb7a1d62c216f49024b671c65c8b032d4ef664b7da49e7",
1668
- "bytes": 40844
1661
+ "path": "chunk-POBFVHIS.js",
1662
+ "sha256": "398f78513e36934eebeaceb2644137c3a95acb515783aa0bdf6dd970bb33cbb9",
1663
+ "bytes": 7587
1669
1664
  },
1670
1665
  {
1671
1666
  "path": "chunk-PYS43F2X.js",
@@ -1862,11 +1857,6 @@
1862
1857
  "sha256": "56262268c24e15325112a9d2d1dd785dea9fe96ac44e0c14ff243edd1fbd7e04",
1863
1858
  "bytes": 76651
1864
1859
  },
1865
- {
1866
- "path": "chunk-TVSIS36M.js",
1867
- "sha256": "c12d360e3677fc87730097aab294f995d40c3bb8daf837fbe1856f258cc2a742",
1868
- "bytes": 3766
1869
- },
1870
1860
  {
1871
1861
  "path": "chunk-TWMWHDOY.js",
1872
1862
  "sha256": "d1a4b120060e1bda1c09919ff863fbb281c71f017d89b31f47e8df07587814c0",
@@ -1917,6 +1907,11 @@
1917
1907
  "sha256": "697ecdef6991c061baab1e9759bc14aecd28631954b2edfdb721c98c72d41c82",
1918
1908
  "bytes": 1813
1919
1909
  },
1910
+ {
1911
+ "path": "chunk-V57YDQQA.js",
1912
+ "sha256": "6284d4a8d979d18fdb3b863f59f91c7cd616c169349bb77766a94693477706e9",
1913
+ "bytes": 3766
1914
+ },
1920
1915
  {
1921
1916
  "path": "chunk-VGMACSEV.js",
1922
1917
  "sha256": "4a51d1fb50f1389160ec2d06ebeb702976a647212f7731ab58f562de3f53f215",
@@ -1972,6 +1967,11 @@
1972
1967
  "sha256": "288099f4b532013a4c9ba4c6129273a6f5036fc99f574e4b4d2b0fddc3792b67",
1973
1968
  "bytes": 1549
1974
1969
  },
1970
+ {
1971
+ "path": "chunk-WMGS5U2J.js",
1972
+ "sha256": "688da29e942b4732af8dc9d226f26a1c57c37e30581a97e30be5af4bfc4ad4ae",
1973
+ "bytes": 260597
1974
+ },
1975
1975
  {
1976
1976
  "path": "chunk-WPTIHQ6M.js",
1977
1977
  "sha256": "a999baf6db449dcd8c856393a0721a50aceb27e74a49f71bbc6b553081d98fc2",
@@ -2574,7 +2574,7 @@
2574
2574
  },
2575
2575
  {
2576
2576
  "path": "hooks/manifest.json",
2577
- "sha256": "ba4ff8f10ee0055e5a4c3ea79bc8ea9c7f0154d0d7b53cdac2a7f72e901cdd03",
2577
+ "sha256": "af052428c7e0f413b988ae47ed6a7120020847a8d665eb125ff24e0f5571b633",
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": "086622c69175e1c94ca91d2ac0dab2042db655eb14252e8acabc3e02fc2dd40b",
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-XZ534AJH.js",
3467
+ "sha256": "5a3b5b3ab0b6160c45abf107a099e188db1cd3239f983c774bb32a4304d02fa9",
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-EAPCVGKJ.js",
3477
+ "sha256": "5dbfd92d46224fa325522dcb29a0c37a04503bcbc240338e6dd81999915e41df",
3478
3478
  "bytes": 7425
3479
3479
  },
3480
3480
  {
3481
- "path": "stack-release-7YHSLY7J.js",
3482
- "sha256": "27806d515012b3af2d4c85f87f6dfd439ba46e01dac917cbf95e60cf0fcece2c",
3481
+ "path": "stack-release-YEO6BFVR.js",
3482
+ "sha256": "59e8eb373abf14f60ab5c7b758b8ab9cebe044490622af22c1af3746f0bd2102",
3483
3483
  "bytes": 117550
3484
3484
  },
3485
3485
  {
3486
- "path": "stack-update-DTMW7GMW.js",
3487
- "sha256": "d05d7f8051ba0503d7ffa650eb5d85b08e841d94c08263c07eea5b0874770d99",
3486
+ "path": "stack-update-NEDES5S4.js",
3487
+ "sha256": "f70a348356634acc818edf94d0deaf756ca7ad3198d6f339b39b8358a6962afe",
3488
3488
  "bytes": 7863
3489
3489
  },
3490
3490
  {
@@ -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-NEDES5S4.js");
142
142
  try {
143
143
  verifyStackManifestSignature(manifest, publicKeyPem);
144
144
  } catch (err) {
@@ -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-NEDES5S4.js");
216
216
  const paths = defaultStackPaths();
217
217
  await rollbackStackUpdate({
218
218
  manifestRef: paths.manifestRef,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  canonicalizeStackManifest
3
- } from "./chunk-3SXU7KLW.js";
3
+ } from "./chunk-WMGS5U2J.js";
4
4
 
5
5
  // src/lib/stack-manifest-write.ts
6
6
  import { readFileSync, statSync, writeFileSync } from "fs";
@@ -120,6 +120,162 @@ ${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) {
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 requiredCore) visit(service);
206
+ return critical;
207
+ }
208
+ function retainedCompletedDependencies(renderedCompose, critical, selectedService, requiredCore) {
209
+ const services = parseRenderedServices(renderedCompose);
210
+ const inbound = /* @__PURE__ */ new Map();
211
+ for (const parent of critical) {
212
+ const dependencies = services[parent]?.depends_on;
213
+ if (!dependencies) continue;
214
+ const entries = Array.isArray(dependencies) ? dependencies.map((name) => [
215
+ String(name),
216
+ { condition: "service_started" }
217
+ ]) : Object.entries(dependencies);
218
+ for (const [name, value] of entries) {
219
+ if (!critical.has(name)) continue;
220
+ const condition = value && typeof value === "object" ? String(
221
+ value.condition ?? "service_started"
222
+ ) : "service_started";
223
+ inbound.set(name, [...inbound.get(name) ?? [], condition]);
224
+ }
225
+ }
226
+ const roots = /* @__PURE__ */ new Set([selectedService, ...requiredCore]);
227
+ return new Set(
228
+ [...inbound].filter(
229
+ ([name, conditions]) => !roots.has(name) && // Compose's omitted restart policy is "no". An explicit policy
230
+ // that restarts the job is not a completed one-shot dependency.
231
+ (services[name]?.restart === void 0 || services[name]?.restart === "no") && conditions.length > 0 && conditions.every(
232
+ (condition) => condition === "service_completed_successfully"
233
+ )
234
+ ).map(([name]) => name)
235
+ );
236
+ }
237
+ function retainedContainerIsHealthy(container, completedDependency = false) {
238
+ 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);
239
+ }
240
+ function assessRetainedHealthBefore(witness) {
241
+ const failures = [];
242
+ for (const service of witness.critical) {
243
+ if (!retainedContainerIsHealthy(
244
+ witness.containers.get(service),
245
+ witness.completedDependencies?.has(service)
246
+ ))
247
+ failures.push(service);
248
+ }
249
+ for (const [service, container] of witness.containers) {
250
+ if (!witness.critical.has(service) && !retainedContainerIsHealthy(container) && !container.id) {
251
+ failures.push(`${service} (unhealthy container identity unavailable)`);
252
+ }
253
+ }
254
+ return failures;
255
+ }
256
+ function assessRetainedHealthAfter(before, after) {
257
+ const failures = [];
258
+ for (const service of before.critical) {
259
+ if (!retainedContainerIsHealthy(
260
+ after.get(service),
261
+ before.completedDependencies?.has(service)
262
+ ))
263
+ failures.push(service);
264
+ }
265
+ for (const [service, current] of after) {
266
+ if (before.critical.has(service) || retainedContainerIsHealthy(current))
267
+ continue;
268
+ const prior = before.containers.get(service);
269
+ if (!prior || retainedContainerIsHealthy(prior) || !prior.id || prior.id !== current.id || prior.state !== current.state || prior.health !== current.health || prior.restartCount !== current.restartCount) {
270
+ failures.push(service);
271
+ }
272
+ }
273
+ for (const service of before.containers.keys()) {
274
+ if (!after.has(service)) failures.push(`${service} (disappeared)`);
275
+ }
276
+ return [...new Set(failures)];
277
+ }
278
+
123
279
  // src/lib/stack-lock-derive.ts
124
280
  import { spawnSync } from "child_process";
125
281
  var GIT_SHA_LABEL = "org.exe.git_sha";
@@ -3953,6 +4109,24 @@ ${lines.join("\n")}`
3953
4109
  options.preDeployBackup(plan.targetVersion);
3954
4110
  } catch {
3955
4111
  }
4112
+ let retainedHealthBaseline;
4113
+ if (options.retainCompose) {
4114
+ const [selectedName, selectedEntry] = Object.entries(plan.release.services)[0];
4115
+ const selected = selectedEntry.composeService ?? selectedName;
4116
+ const requiredCore = Object.entries(targetRelease.services).filter(([, service]) => service.required === true && service.category === "core").map(([name, service]) => service.composeService ?? name);
4117
+ const args = composeArgsForStack(options.composeFile, options.envFile);
4118
+ const rendered = (options.composeConfigCapture ?? defaultComposeConfigCapture)(args, process.env);
4119
+ const critical = retainedCriticalServices(rendered, selected, requiredCore);
4120
+ const completedDependencies = retainedCompletedDependencies(rendered, critical, selected, requiredCore);
4121
+ const rawHealth = (options.composePsCapture ?? defaultComposePsCapture)(args);
4122
+ retainedHealthBaseline = { critical, completedDependencies, containers: parseRetainedContainerHealth(rawHealth) };
4123
+ const failures = assessRetainedHealthBefore(retainedHealthBaseline);
4124
+ if (failures.length > 0) throw new Error(`--retain-compose health gate failed before update: ${failures.join(", ")}`);
4125
+ const preexisting = [...retainedHealthBaseline.containers.values()].filter((container) => !critical.has(container.service) && (container.state !== "running" || container.health === "unhealthy" || container.restartCount !== null && container.restartCount > 2));
4126
+ for (const container of preexisting) {
4127
+ console.warn(`[stack-update] Pre-existing unrelated unhealthy container (unchanged identity required): ${container.service}`);
4128
+ }
4129
+ }
3956
4130
  const stackDir = path.dirname(options.envFile);
3957
4131
  const configBackup = preBootstrapBackup ?? backupStackConfig(stackDir, options.composeFile, envRaw, now);
3958
4132
  const updateBackupDir = configBackup.updateBackupDir;
@@ -4426,6 +4600,15 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
4426
4600
  endPhase3();
4427
4601
  }
4428
4602
  }
4603
+ if (retainedHealthBaseline) {
4604
+ const after = parseRetainedContainerHealth(
4605
+ (options.composePsCapture ?? defaultComposePsCapture)(composeArgs)
4606
+ );
4607
+ const regressions = assessRetainedHealthAfter(retainedHealthBaseline, after);
4608
+ if (regressions.length > 0) {
4609
+ throw new Error(`--retain-compose health regression after update: ${regressions.join(", ")}`);
4610
+ }
4611
+ }
4429
4612
  {
4430
4613
  const cfIngressConfigPath = path.join(path.dirname(options.composeFile), "cloudflared", "config.yml");
4431
4614
  const cfIngressConfigYml = existsSync(cfIngressConfigPath) ? readFileSync(cfIngressConfigPath, "utf8") : null;
@@ -5484,6 +5667,11 @@ function loadDefaultPublicKey() {
5484
5667
  }
5485
5668
 
5486
5669
  export {
5670
+ parseRetainedContainerHealth,
5671
+ retainedCriticalServices,
5672
+ retainedCompletedDependencies,
5673
+ retainedContainerIsHealthy,
5674
+ assessRetainedHealthBefore,
5487
5675
  resolveServiceHealthTimeout,
5488
5676
  serviceSelectionPath,
5489
5677
  loadServiceSelection,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "generatedAt": "2026-09-23T21:11:44.253Z",
3
+ "generatedAt": "2026-09-23T22:15:03.718Z",
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-NJ57AHX4.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-V57YDQQA.js";
5
+ import "./chunk-WMGS5U2J.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-V57YDQQA.js";
5
+ import "./chunk-WMGS5U2J.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-V57YDQQA.js";
5
+ import "./chunk-WMGS5U2J.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-XZ534AJH.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-WMGS5U2J.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.393",
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",
@@ -1,8 +1,8 @@
1
1
  {
2
- "current": "0.9.392",
2
+ "current": "0.9.393",
3
3
  "notes": {
4
- "0.9.392": {
5
- "version": "0.9.392",
4
+ "0.9.393": {
5
+ "version": "0.9.393",
6
6
  "date": "2026-09-23",
7
7
  "features": [
8
8
  "local CI triage — classify failing checks at zero token cost (#1441)",
@@ -13,6 +13,10 @@
13
13
  "sign recoverable B3 invite proofs with native keys"
14
14
  ],
15
15
  "fixes": [
16
+ "honor omitted default no-restart policy",
17
+ "honor completed one-shot dependencies in retained health",
18
+ "require critical container identity and preserve missing restart data",
19
+ "scope retained health to selected service and critical dependencies",
16
20
  "compare override drift to signed snapshot",
17
21
  "attest retained Compose override for scoped releases",
18
22
  "no_raw excludes captured searches (bug 37353c88 residual a) (#1509)",
@@ -33,14 +37,13 @@
33
37
  "extract-review-findings.sh fails loudly on a broken comments lookup (#1441 follow-up) (#1493)",
34
38
  "report duplicate hook entries as a --check failure, not just --apply (#1179 follow-up) (#1492)",
35
39
  "ledger mutations commit atomically without a cross-await transaction (#1229 follow-up) (#1490)",
36
- "memory/DB layer lifecycle cluster — all 10 bugs (#1473)",
37
- "any unreadable SSO-edge config means unverified delivery (#1221 follow-up) (#1489)",
38
- "session/orchestration cluster — 6 fixes, 1 verified, 4 documented deferrals (#1469)",
39
- "an unreadable grandfather marker is not an absent one (bug d11c5049, D22) (#1317)",
40
- "guard-reachability — advisory report, not a blocking gate (bug efabb902) (#1216)"
40
+ "memory/DB layer lifecycle cluster — all 10 bugs (#1473)"
41
41
  ],
42
42
  "security": [],
43
43
  "other": [
44
+ "Prepare signed Dashboard canary stack 0.9.53 with scoped updater 0.9.393",
45
+ "assert required core failure in retained scope",
46
+ "release(npm): publish 0.9.392 from exact green main source",
44
47
  "release(stack): sign Dashboard topology canary 0.9.52",
45
48
  "release(stack): pin Dashboard v0.9.392 in canary 0.9.51 (#1511)",
46
49
  "release: use declared control runner lane",