@askexenow/exe-os 0.9.389 → 0.9.390
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 +144 -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 +272 -36
- package/dist/bin/vps-health-gate.js +1 -1
- package/dist/build-manifest.json +37 -37
- package/dist/{chunk-UWVXQEQQ.js → chunk-DZPSBA4B.js} +41 -2
- package/dist/{chunk-SLMKLEU6.js → chunk-HJNQAEZV.js} +1 -1
- package/dist/{chunk-J4UVI652.js → chunk-WQN2XK4V.js} +1 -1
- package/dist/{chunk-PZR54BEI.js → chunk-YI4D6E75.js} +1 -1
- package/dist/hooks/manifest.json +1 -1
- package/dist/lib/registry-proxy.js +1 -1
- package/dist/{stack-generate-manifest-AXI55MEQ.js → stack-generate-manifest-VWNDIMZ2.js} +2 -2
- package/dist/{stack-promote-6N23OGZY.js → stack-promote-DFXJDHVA.js} +2 -2
- package/dist/{stack-release-2CXFYVWM.js → stack-release-RQ2DYYD5.js} +3 -3
- package/dist/{stack-update-U53FBVCQ.js → stack-update-MVGCN3C4.js} +7 -3
- package/package.json +1 -1
- package/release-notes.json +12 -7
- package/stack.release.json +3 -3
|
@@ -4217,11 +4217,154 @@
|
|
|
4217
4217
|
}
|
|
4218
4218
|
},
|
|
4219
4219
|
"rolloutStage": "canary"
|
|
4220
|
+
},
|
|
4221
|
+
"0.9.48": {
|
|
4222
|
+
"version": "0.9.48",
|
|
4223
|
+
"releasedAt": "2026-09-22T19:52:27.677Z",
|
|
4224
|
+
"npmVersion": "0.9.389",
|
|
4225
|
+
"notes": "Authenticated config-only canary from stack 0.9.47. Deliver the reviewed SSO edge redirect fix using exe-os v0.9.389 config apply: the signed release covers the exact packaged compose template and every packaged bind-mounted config asset. Preserve every image pin from 0.9.47; no image movement is authorized. Apply only after registry tarball integrity and installed updater bytes are verified, with backups, strict dependency-ordered health checks, and rollback.",
|
|
4226
|
+
"services": {
|
|
4227
|
+
"exe-db": {
|
|
4228
|
+
"image": "update.askexe.com/askexe/pgvector:0.8.0-pg16@sha256:a132765ec351c65111b5b675928a3a0515a466a40f97277329db8b8209ad8bc9",
|
|
4229
|
+
"env": "EXE_DB_IMAGE",
|
|
4230
|
+
"composeService": "exe-db",
|
|
4231
|
+
"required": true,
|
|
4232
|
+
"category": "core",
|
|
4233
|
+
"description": "Postgres (pgvector) — digest-pinned (bug 32d6e02f; was tag-only in 0.9.25, regressed from 0.9.24)"
|
|
4234
|
+
},
|
|
4235
|
+
"exe-os": {
|
|
4236
|
+
"image": "update.askexe.com/askexe/exe-os:v0.9.382@sha256:266a59a482865eab9f79497999acb33c1fe1868ec54ea5cd1ce971c492f11bd6",
|
|
4237
|
+
"env": "EXE_OS_IMAGE_TAG",
|
|
4238
|
+
"composeService": "exe-os",
|
|
4239
|
+
"healthUrl": "http://127.0.0.1:8765/health",
|
|
4240
|
+
"required": true,
|
|
4241
|
+
"category": "core",
|
|
4242
|
+
"description": "exe-os daemon + MCP server"
|
|
4243
|
+
},
|
|
4244
|
+
"crm": {
|
|
4245
|
+
"image": "update.askexe.com/askexe/exe-crm:v0.9.62@sha256:584113f88ce09f5ea3b93b7b5e8960ebb8a00e34adaa205c92d964b46525b7a8",
|
|
4246
|
+
"env": "CRM_IMAGE_TAG",
|
|
4247
|
+
"composeService": "exe-crm",
|
|
4248
|
+
"healthUrl": "http://127.0.0.1:3000/healthz",
|
|
4249
|
+
"category": "app",
|
|
4250
|
+
"gitSha": "60f8f08faa5d8fe86b92a87eda93eb0d051a6353",
|
|
4251
|
+
"description": "Central authentication and recovery with server-validated shared sessions for CRM web and worker."
|
|
4252
|
+
},
|
|
4253
|
+
"wiki": {
|
|
4254
|
+
"image": "update.askexe.com/askexe/exe-wiki:v0.9.37@sha256:8fa22e77c0742fbb690f7b4c8431a186692a20ebd335fb141782df4289dc337f",
|
|
4255
|
+
"env": "WIKI_IMAGE_TAG",
|
|
4256
|
+
"composeService": "exe-wiki",
|
|
4257
|
+
"healthUrl": "http://127.0.0.1:3001/api/ping",
|
|
4258
|
+
"category": "app",
|
|
4259
|
+
"description": "Central hosted authentication and recovery with server-validated shared sessions, membership enforcement, and no product-local credential form.",
|
|
4260
|
+
"gitSha": "713a4370c2b641b8f8b07fd1e7390afa79a9c931"
|
|
4261
|
+
},
|
|
4262
|
+
"gateway": {
|
|
4263
|
+
"image": "update.askexe.com/askexe/exe-gateway:v2026.08.07-c15f093-stamped@sha256:1bc11a2a0ad1f3929d59d2685696f22a72042b9fca9c75e15c02dc7d3c640105",
|
|
4264
|
+
"env": "GATEWAY_IMAGE_TAG",
|
|
4265
|
+
"composeService": "exe-gateway",
|
|
4266
|
+
"healthUrl": "http://127.0.0.1:3100/health",
|
|
4267
|
+
"category": "app",
|
|
4268
|
+
"description": "WhatsApp/messaging gateway — provenance-stamped rebuild of main (outbound message persistence fix); pinned by digest, dated immutable tag.",
|
|
4269
|
+
"gitSha": "c15f093a0f59730bd5c2c73791ff77059dfa7cc1"
|
|
4270
|
+
},
|
|
4271
|
+
"monitorAgent": {
|
|
4272
|
+
"image": "update.askexe.com/askexe/exe-monitor-agent:v2026.08.09-10660eb4-stamped@sha256:29fbd445f79484df04f000603e4ff445e76f0380f86d687a0033e3a0ef7d1b7a",
|
|
4273
|
+
"env": "MONITOR_AGENT_IMAGE_TAG",
|
|
4274
|
+
"composeService": "exe-monitor-agent",
|
|
4275
|
+
"category": "monitoring",
|
|
4276
|
+
"gitSha": "10660eb4abdeffbcba2aad13549aa58e208ac7a3"
|
|
4277
|
+
},
|
|
4278
|
+
"monitorHub": {
|
|
4279
|
+
"image": "update.askexe.com/askexe/exe-monitor-hub:v2026.08.09-10660eb4-stamped@sha256:c9a9218c2713b8dffea7e1cf8c0702a614657d311a7a4e821c6a543bab29bb97",
|
|
4280
|
+
"env": "MONITOR_HUB_IMAGE_TAG",
|
|
4281
|
+
"composeService": "exe-monitor-hub",
|
|
4282
|
+
"healthUrl": "http://127.0.0.1:8090/api/health",
|
|
4283
|
+
"category": "monitoring",
|
|
4284
|
+
"gitSha": "10660eb4abdeffbcba2aad13549aa58e208ac7a3"
|
|
4285
|
+
},
|
|
4286
|
+
"auth": {
|
|
4287
|
+
"image": "update.askexe.com/askexe/exe-auth:v0.6.10@sha256:0260349962d087cd3c33fed1d362029416e82ae9cc60abca7969beac2cb8639f",
|
|
4288
|
+
"env": "AUTH_IMAGE_TAG",
|
|
4289
|
+
"composeService": "exe-auth",
|
|
4290
|
+
"healthUrl": "http://127.0.0.1:3300/",
|
|
4291
|
+
"category": "auth",
|
|
4292
|
+
"description": "GoTrue SSO and privileged license authority bridge; stateless auth proxy.",
|
|
4293
|
+
"gitSha": "29fe7015d90b80c94db3a1e3904852b934071711"
|
|
4294
|
+
},
|
|
4295
|
+
"erp": {
|
|
4296
|
+
"image": "update.askexe.com/askexe/exe-erp:v0.3.17@sha256:ef61b59d94c04189fe2975045a3816d2fecc8efe3027e0ee8c17d0fa2efdbc3b",
|
|
4297
|
+
"env": "ERP_IMAGE_TAG",
|
|
4298
|
+
"composeService": "exe-erp",
|
|
4299
|
+
"healthUrl": "http://127.0.0.1:8069/api/method/ping",
|
|
4300
|
+
"healthHost": "localhost",
|
|
4301
|
+
"required": false,
|
|
4302
|
+
"category": "app",
|
|
4303
|
+
"description": "Enterprise Resource Planning with central hosted authentication and subject-bound organization admission.",
|
|
4304
|
+
"gitSha": "a769ec5e0ae53bc5944715845869715ebbffd63a"
|
|
4305
|
+
},
|
|
4306
|
+
"gotrue": {
|
|
4307
|
+
"image": "update.askexe.com/askexe/gotrue:v2.172.1@sha256:b0dd7aa28dd1f9a8bd31f136c8b912661f701d9b863171e5843cb08f18f49de4",
|
|
4308
|
+
"composeService": "gotrue",
|
|
4309
|
+
"healthUrl": "http://127.0.0.1:9999/health",
|
|
4310
|
+
"category": "infra"
|
|
4311
|
+
},
|
|
4312
|
+
"redis": {
|
|
4313
|
+
"image": "update.askexe.com/askexe/redis:7.4-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99",
|
|
4314
|
+
"composeService": "redis",
|
|
4315
|
+
"category": "infra"
|
|
4316
|
+
},
|
|
4317
|
+
"clickhouse": {
|
|
4318
|
+
"image": "update.askexe.com/askexe/clickhouse-server:24.8.4.13-alpine@sha256:88a45f9e328549b2579256c46ee38e5c0e25ae58303d9eb6d9c7ed8d6d2bbf3c",
|
|
4319
|
+
"composeService": "clickhouse",
|
|
4320
|
+
"category": "infra"
|
|
4321
|
+
},
|
|
4322
|
+
"cloudflared": {
|
|
4323
|
+
"image": "update.askexe.com/askexe/cloudflared:2026-07@sha256:ba461b8aa9c042156dbd39c38657fe7431bafa063220eab8d5330a523863da9f",
|
|
4324
|
+
"composeService": "cloudflared",
|
|
4325
|
+
"category": "infra"
|
|
4326
|
+
},
|
|
4327
|
+
"exe-sso-edge": {
|
|
4328
|
+
"image": "update.askexe.com/askexe/nginx:alpine@sha256:20316569d8f81a160065d7d2a5eeffc7ca97d79022462ee255fd23fa103a6b5c",
|
|
4329
|
+
"composeService": "exe-sso-edge",
|
|
4330
|
+
"category": "infra"
|
|
4331
|
+
},
|
|
4332
|
+
"exe-otel-collector": {
|
|
4333
|
+
"image": "update.askexe.com/askexe/otel-collector-contrib:0.114.0@sha256:37fa87091cfaaec7234a27e4e395a40c31c2bfaea97a349a4afef6d9e9681197",
|
|
4334
|
+
"composeService": "exe-otel-collector",
|
|
4335
|
+
"category": "infra"
|
|
4336
|
+
},
|
|
4337
|
+
"dashboard": {
|
|
4338
|
+
"image": "update.askexe.com/askexe/exe-dashboard:v0.9.391@sha256:272f3440fa0f93bd19b3a0b53983c488d90b9731f252a1fb9120c9516566b4a5",
|
|
4339
|
+
"env": "DASHBOARD_IMAGE_TAG",
|
|
4340
|
+
"composeService": "exe-dashboard",
|
|
4341
|
+
"healthUrl": "http://127.0.0.1:3301/health",
|
|
4342
|
+
"required": false,
|
|
4343
|
+
"category": "optional",
|
|
4344
|
+
"description": "Customer dashboard at dashboard.<apex> — credits, usage, API keys, billing",
|
|
4345
|
+
"gitSha": "ce76474b7af9476846549a5ae2eb272dc182e9cf"
|
|
4346
|
+
}
|
|
4347
|
+
},
|
|
4348
|
+
"rolloutStage": "canary",
|
|
4349
|
+
"config": {
|
|
4350
|
+
"composeSha256": "4a648434eef4f194de15cc742684dfd3220be2334035333257dd37faa4828b3d",
|
|
4351
|
+
"assets": {
|
|
4352
|
+
"sso-edge/default.conf.template": "fac3627a51989bbb62b8cba1f2dfd4aab69555b998fd48c0a1a67deb5889f29f",
|
|
4353
|
+
"sso-edge/entrypoint.sh": "2281fc9382ee1f80dbf306a380d165d0afe1fcc37f5b752925e34f5a06419ce9",
|
|
4354
|
+
"sso-edge/error-pages/404.html": "d9a163356479204cf0c200d1cf483a986d3ef4c46c53e02d97bfe128d25e833f",
|
|
4355
|
+
"sso-edge/error-pages/500.html": "82972c5fbb237047f532c95d8757a2bcc696ca9466e70c73debf64e8b58689a7",
|
|
4356
|
+
"sso-edge/error-pages/503.html": "58f73f74cabff1a8faa63b11af36aa643c78ac740c9a31378f8a8ee6aa572cf8",
|
|
4357
|
+
"sso-edge/sso-redirect.conf": "d9e1036ba2129450372caff3f09d5ad8894b3b774ab50fe0807322e0f8896d7b",
|
|
4358
|
+
"erp-nginx/nginx.conf": "918d3e3b9c854c3c2bfefaab48ea4e41c4430dd92429e9f035676783d7136ab7",
|
|
4359
|
+
"observability/otel-collector-config.yaml": "01fcd1192e695455fdf85029e60074bee96ff9565da05f4c01b3428b264fe4d5",
|
|
4360
|
+
"cloudflared/config.yml.example": "c60e28c67d8c13e54d4d15f4a79ed3f4ad8723f6345138b084a1925c871b529a"
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4220
4363
|
}
|
|
4221
4364
|
},
|
|
4222
4365
|
"signature": {
|
|
4223
4366
|
"keyId": "exe-stack-1",
|
|
4224
4367
|
"alg": "ed25519",
|
|
4225
|
-
"signature": "
|
|
4368
|
+
"signature": "tyRpYIwHzjT4eixgdAqouFG/9rNSBi9QG0lwOkDgCYlxlr7FHePfIW/2s/DiR35f4eUsIJjJWUJltbhe8Nv8Dw=="
|
|
4226
4369
|
}
|
|
4227
4370
|
}
|
package/dist/.dist-verified.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"verified_at": "2026-09-
|
|
4
|
-
"manifest_sha256": "
|
|
5
|
-
"package_version": "0.9.
|
|
3
|
+
"verified_at": "2026-09-23T01:55:53.298Z",
|
|
4
|
+
"manifest_sha256": "adc75c50c1588490f5f4f0fdeadc2197fe248394ad1dd56f5a4766693ad7af93",
|
|
5
|
+
"package_version": "0.9.390"
|
|
6
6
|
}
|
package/dist/bin/cli.js
CHANGED
|
@@ -792,14 +792,14 @@ ${JSON.stringify(stormAlert, null, 2)}`);
|
|
|
792
792
|
const { runStackUpdateCli } = await import("./stack-update.js");
|
|
793
793
|
await runStackUpdateCli(args.slice(1));
|
|
794
794
|
} else if (args[0] === "release") {
|
|
795
|
-
const { parseReleaseArgs, runStackRelease } = await import("../stack-release-
|
|
795
|
+
const { parseReleaseArgs, runStackRelease } = await import("../stack-release-RQ2DYYD5.js");
|
|
796
796
|
const releaseFlags = parseReleaseArgs(args.slice(1));
|
|
797
797
|
await runStackRelease(releaseFlags);
|
|
798
798
|
} else if (args[0] === "stack-promote") {
|
|
799
|
-
const { runStackPromoteCli } = await import("../stack-promote-
|
|
799
|
+
const { runStackPromoteCli } = await import("../stack-promote-DFXJDHVA.js");
|
|
800
800
|
await runStackPromoteCli(args.slice(1));
|
|
801
801
|
} else if (args[0] === "stack-generate-manifest") {
|
|
802
|
-
const { runStackGenerateManifestCli } = await import("../stack-generate-manifest-
|
|
802
|
+
const { runStackGenerateManifestCli } = await import("../stack-generate-manifest-VWNDIMZ2.js");
|
|
803
803
|
process.exit(await runStackGenerateManifestCli(args.slice(1)));
|
|
804
804
|
} else if (args[0] === "repo-truth") {
|
|
805
805
|
const { runRepoTruthCli } = await import("./exe-repo-truth.js");
|
package/dist/bin/stack-update.js
CHANGED
|
@@ -35,8 +35,9 @@ import {
|
|
|
35
35
|
serviceSelectionPath,
|
|
36
36
|
verifyComposeServicesRunning,
|
|
37
37
|
verifyManifestImagesAvailable,
|
|
38
|
-
warnUnmanagedManifestServices
|
|
39
|
-
|
|
38
|
+
warnUnmanagedManifestServices,
|
|
39
|
+
withStackUpdateOperationLock
|
|
40
|
+
} from "../chunk-DZPSBA4B.js";
|
|
40
41
|
import "../chunk-EC5H53YD.js";
|
|
41
42
|
import {
|
|
42
43
|
runVerifyStack
|
|
@@ -52,7 +53,7 @@ import {
|
|
|
52
53
|
import {
|
|
53
54
|
logResult,
|
|
54
55
|
runHealthGate
|
|
55
|
-
} from "../chunk-
|
|
56
|
+
} from "../chunk-WQN2XK4V.js";
|
|
56
57
|
import "../chunk-L25OAFOU.js";
|
|
57
58
|
import "../chunk-RT57D7C4.js";
|
|
58
59
|
import "../chunk-FTZET5YX.js";
|
|
@@ -566,7 +567,7 @@ if (process.argv[1]?.endsWith("preflight.ts") || process.argv[1]?.endsWith("pref
|
|
|
566
567
|
// src/lib/stack-config-update.ts
|
|
567
568
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
568
569
|
import { createHash } from "crypto";
|
|
569
|
-
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, mkdtempSync, readFileSync as readFileSync2, readdirSync, rmSync as rmSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
570
|
+
import { chmodSync, chownSync, existsSync as existsSync2, lstatSync as lstatSync2, mkdirSync as mkdirSync2, mkdtempSync, readFileSync as readFileSync2, readdirSync, renameSync, rmSync as rmSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
570
571
|
import { tmpdir } from "os";
|
|
571
572
|
import path2 from "path";
|
|
572
573
|
function computeConfigDigest(raw) {
|
|
@@ -587,6 +588,130 @@ function collectConfigAssetDigests(configDir, subdirs = CONFIG_ASSET_DIRS) {
|
|
|
587
588
|
return digests;
|
|
588
589
|
}
|
|
589
590
|
var CONFIG_ASSET_DIRS = ["sso-edge", "erp-nginx", "observability", "cloudflared"];
|
|
591
|
+
function assertSafeAssetRelativePath(rel) {
|
|
592
|
+
if (!rel || path2.isAbsolute(rel) || rel.includes("\\") || path2.posix.normalize(rel) !== rel || rel.startsWith("../") || rel.includes("/../")) {
|
|
593
|
+
throw new Error(`Targeted config asset path is not a canonical relative path: ${rel}`);
|
|
594
|
+
}
|
|
595
|
+
const root = rel.split("/")[0];
|
|
596
|
+
if (!CONFIG_ASSET_DIRS.includes(root)) {
|
|
597
|
+
throw new Error(`Targeted config asset is outside the canonical asset roots: ${rel}`);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
function assertNoSymlinkPath(root, rel, requireLeaf) {
|
|
601
|
+
let current = root;
|
|
602
|
+
for (const [index, part] of rel.split("/").entries()) {
|
|
603
|
+
current = path2.join(current, part);
|
|
604
|
+
if (!existsSync2(current)) {
|
|
605
|
+
if (requireLeaf || index < rel.split("/").length - 1) throw new Error(`Targeted config asset path is missing: ${current}`);
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
const st = lstatSync2(current);
|
|
609
|
+
if (st.isSymbolicLink()) throw new Error(`Targeted config asset path traverses a symlink: ${current}`);
|
|
610
|
+
if (index === rel.split("/").length - 1) {
|
|
611
|
+
if (!st.isFile()) throw new Error(`Targeted config asset is not a regular file: ${current}`);
|
|
612
|
+
if (st.nlink !== 1) throw new Error(`Targeted config asset has unexpected hard links: ${current}`);
|
|
613
|
+
} else if (!st.isDirectory()) {
|
|
614
|
+
throw new Error(`Targeted config asset parent is not a directory: ${current}`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function validateTargetedAssetPatches(input) {
|
|
619
|
+
if (input.config.composeSha256 || input.config.assets) {
|
|
620
|
+
throw new Error("Signed config assetPatches are mutually exclusive with composeSha256/assets");
|
|
621
|
+
}
|
|
622
|
+
const entries = Object.entries(input.config.assetPatches ?? {});
|
|
623
|
+
if (entries.length === 0) throw new Error("Signed config assetPatches is empty");
|
|
624
|
+
const normalized = /* @__PURE__ */ new Set();
|
|
625
|
+
return entries.sort(([a], [b]) => a.localeCompare(b)).map(([rel, patch]) => {
|
|
626
|
+
assertSafeAssetRelativePath(rel);
|
|
627
|
+
const norm = path2.posix.normalize(rel);
|
|
628
|
+
if (normalized.has(norm)) throw new Error(`Duplicate normalized targeted asset path: ${rel}`);
|
|
629
|
+
normalized.add(norm);
|
|
630
|
+
if (!/^[a-f0-9]{64}$/.test(patch.fromSha256) || !/^[a-f0-9]{64}$/.test(patch.toSha256)) {
|
|
631
|
+
throw new Error(`Targeted config asset hashes must be lowercase sha256: ${rel}`);
|
|
632
|
+
}
|
|
633
|
+
if (patch.fromSha256 === patch.toSha256) throw new Error(`Targeted config asset transition must change bytes: ${rel}`);
|
|
634
|
+
if (patch.mode !== void 0 && (!Number.isInteger(patch.mode) || patch.mode < 0 || patch.mode > 4095)) {
|
|
635
|
+
throw new Error(`Targeted config asset mode is outside 0000..7777: ${rel}`);
|
|
636
|
+
}
|
|
637
|
+
const consumers = [...new Set(patch.consumers)].sort();
|
|
638
|
+
if (consumers.length === 0 || consumers.length !== patch.consumers.length || consumers.some((v) => !/^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(v))) {
|
|
639
|
+
throw new Error(`Targeted config asset consumers are invalid or duplicated: ${rel}`);
|
|
640
|
+
}
|
|
641
|
+
const source = path2.join(input.packagedRoot, rel);
|
|
642
|
+
const target = path2.join(input.liveRoot, rel);
|
|
643
|
+
assertNoSymlinkPath(input.packagedRoot, rel, true);
|
|
644
|
+
assertNoSymlinkPath(input.liveRoot, rel, true);
|
|
645
|
+
const sourceHash = computeConfigDigest(readFileSync2(source));
|
|
646
|
+
const targetHash = computeConfigDigest(readFileSync2(target));
|
|
647
|
+
if (sourceHash !== patch.toSha256) throw new Error(`Packaged targeted asset does not match signed target hash: ${rel}`);
|
|
648
|
+
if (targetHash !== patch.fromSha256 && targetHash !== patch.toSha256) throw new Error(`Live targeted asset does not match signed base or target hash: ${rel}`);
|
|
649
|
+
return {
|
|
650
|
+
rel,
|
|
651
|
+
packagedRoot: input.packagedRoot,
|
|
652
|
+
liveRoot: input.liveRoot,
|
|
653
|
+
source,
|
|
654
|
+
target,
|
|
655
|
+
...patch,
|
|
656
|
+
consumers,
|
|
657
|
+
state: targetHash === patch.toSha256 ? "to" : "from"
|
|
658
|
+
};
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
function assertTargetedAssetConsumers(patches, renderedConfigJson, liveRoot) {
|
|
662
|
+
const selected = /* @__PURE__ */ new Set();
|
|
663
|
+
for (const patch of patches) {
|
|
664
|
+
const observed = servicesMountingPaths(renderedConfigJson, liveRoot, [patch.rel]).sort();
|
|
665
|
+
const expected = [...patch.consumers].sort();
|
|
666
|
+
if (JSON.stringify(observed) !== JSON.stringify(expected)) {
|
|
667
|
+
throw new Error(
|
|
668
|
+
`Targeted config asset consumer mismatch for ${patch.rel}: signed [${expected.join(", ")}], live compose [${observed.join(", ")}]`
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
for (const service of observed) selected.add(service);
|
|
672
|
+
}
|
|
673
|
+
return [...selected].sort();
|
|
674
|
+
}
|
|
675
|
+
function atomicReplaceTargetedAsset(patch) {
|
|
676
|
+
assertNoSymlinkPath(patch.packagedRoot, patch.rel, true);
|
|
677
|
+
assertNoSymlinkPath(patch.liveRoot, patch.rel, true);
|
|
678
|
+
const sourceBytes = readFileSync2(patch.source);
|
|
679
|
+
if (computeConfigDigest(sourceBytes) !== patch.toSha256) throw new Error(`Packaged targeted asset changed before apply: ${patch.rel}`);
|
|
680
|
+
const liveHash = computeConfigDigest(readFileSync2(patch.target));
|
|
681
|
+
if (liveHash !== patch.fromSha256 && liveHash !== patch.toSha256) throw new Error(`Live targeted asset changed before apply: ${patch.rel}`);
|
|
682
|
+
const prior = statSync(patch.target);
|
|
683
|
+
const temporary = `${patch.target}.exe-targeted-${process.pid}`;
|
|
684
|
+
let created = false;
|
|
685
|
+
try {
|
|
686
|
+
writeFileSync2(temporary, sourceBytes, { mode: 384, flag: "wx" });
|
|
687
|
+
created = true;
|
|
688
|
+
chownSync(temporary, prior.uid, prior.gid);
|
|
689
|
+
const expectedMode = patch.mode ?? prior.mode & 4095;
|
|
690
|
+
chmodSync(temporary, expectedMode);
|
|
691
|
+
if ((statSync(temporary).mode & 4095) !== expectedMode) {
|
|
692
|
+
throw new Error(`Targeted config asset mode could not be applied: ${patch.rel}`);
|
|
693
|
+
}
|
|
694
|
+
renameSync(temporary, patch.target);
|
|
695
|
+
} finally {
|
|
696
|
+
if (created) rmSync2(temporary, { force: true });
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
function atomicRestoreAsset(target, prior) {
|
|
700
|
+
const temporary = `${target}.exe-rollback-${process.pid}`;
|
|
701
|
+
let created = false;
|
|
702
|
+
try {
|
|
703
|
+
writeFileSync2(temporary, prior.bytes, { mode: 384, flag: "wx" });
|
|
704
|
+
created = true;
|
|
705
|
+
chownSync(temporary, prior.uid, prior.gid);
|
|
706
|
+
chmodSync(temporary, prior.mode);
|
|
707
|
+
if ((statSync(temporary).mode & 4095) !== prior.mode) {
|
|
708
|
+
throw new Error(`Targeted config rollback mode could not be applied: ${target}`);
|
|
709
|
+
}
|
|
710
|
+
renameSync(temporary, target);
|
|
711
|
+
} finally {
|
|
712
|
+
if (created) rmSync2(temporary, { force: true });
|
|
713
|
+
}
|
|
714
|
+
}
|
|
590
715
|
function collectConfigAssetModes(configDir, subdirs = CONFIG_ASSET_DIRS) {
|
|
591
716
|
const modes = {};
|
|
592
717
|
for (const subdir of subdirs) {
|
|
@@ -1042,6 +1167,9 @@ function defaultComposeCapture(args) {
|
|
|
1042
1167
|
return result.stdout ?? "";
|
|
1043
1168
|
}
|
|
1044
1169
|
async function runStackConfigUpdate(options) {
|
|
1170
|
+
return withStackUpdateOperationLock(options.envFile, () => runStackConfigUpdateUnlocked(options));
|
|
1171
|
+
}
|
|
1172
|
+
async function runStackConfigUpdateUnlocked(options) {
|
|
1045
1173
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
1046
1174
|
const exec = options.exec ?? ((cmd, args) => {
|
|
1047
1175
|
const result2 = spawnSync2(cmd, args, { stdio: "inherit", timeout: 6e5 });
|
|
@@ -1062,19 +1190,41 @@ async function runStackConfigUpdate(options) {
|
|
|
1062
1190
|
);
|
|
1063
1191
|
}
|
|
1064
1192
|
const { release, stackVersion, stackVersionSource } = await resolveSignedRelease(options);
|
|
1065
|
-
const stagedComposeRaw = readFileSync2(templatePath, "utf8");
|
|
1066
|
-
const stagedAssetDigests = collectConfigAssetDigests(path2.dirname(templatePath));
|
|
1067
|
-
assertStagedConfigMatchesManifest({
|
|
1068
|
-
release,
|
|
1069
|
-
composeRaw: stagedComposeRaw,
|
|
1070
|
-
assetDigests: stagedAssetDigests,
|
|
1071
|
-
// Applying bytes requires the signed release to cover every byte this mode
|
|
1072
|
-
// can write. Planning may inspect legacy releases, but apply never does.
|
|
1073
|
-
strict: options.dryRun ? options.requireConfigDigest ?? process.env.EXE_STACK_REQUIRE_CONFIG_DIGEST === "1" : true
|
|
1074
|
-
});
|
|
1075
1193
|
const currentComposeRaw = readFileSync2(options.composeFile, "utf8");
|
|
1076
1194
|
const currentEnvRaw = readFileSync2(options.envFile, "utf8");
|
|
1077
|
-
const
|
|
1195
|
+
const targeted = release?.config?.assetPatches ? validateTargetedAssetPatches({
|
|
1196
|
+
config: release.config,
|
|
1197
|
+
packagedRoot: path2.dirname(templatePath),
|
|
1198
|
+
liveRoot: configDir
|
|
1199
|
+
}) : void 0;
|
|
1200
|
+
if (targeted && !options.dryRun && !options.auditUrl) {
|
|
1201
|
+
throw new Error("Targeted config delivery requires an authenticated audit URL");
|
|
1202
|
+
}
|
|
1203
|
+
const targetedControlHashes = targeted ? new Map([
|
|
1204
|
+
[options.composeFile, computeConfigDigest(currentComposeRaw)],
|
|
1205
|
+
[options.envFile, computeConfigDigest(currentEnvRaw)],
|
|
1206
|
+
...options.lockFile && existsSync2(options.lockFile) ? [[options.lockFile, computeConfigDigest(readFileSync2(options.lockFile))]] : []
|
|
1207
|
+
]) : void 0;
|
|
1208
|
+
const assertTargetedControlFilesUnchanged = () => {
|
|
1209
|
+
for (const [file, expected] of targetedControlHashes ?? []) {
|
|
1210
|
+
if (!existsSync2(file) || computeConfigDigest(readFileSync2(file)) !== expected) {
|
|
1211
|
+
throw new Error(`Targeted config delivery refused: immutable control file changed during the run: ${file}`);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
const stagedComposeRaw = targeted ? currentComposeRaw : readFileSync2(templatePath, "utf8");
|
|
1216
|
+
const stagedAssetDigests = targeted ? Object.fromEntries(targeted.map((p) => [p.rel, p.toSha256])) : collectConfigAssetDigests(path2.dirname(templatePath));
|
|
1217
|
+
if (!targeted) {
|
|
1218
|
+
assertStagedConfigMatchesManifest({
|
|
1219
|
+
release,
|
|
1220
|
+
composeRaw: stagedComposeRaw,
|
|
1221
|
+
assetDigests: stagedAssetDigests,
|
|
1222
|
+
// Applying bytes requires the signed release to cover every byte this mode
|
|
1223
|
+
// can write. Planning may inspect legacy releases, but apply never does.
|
|
1224
|
+
strict: options.dryRun ? options.requireConfigDigest ?? process.env.EXE_STACK_REQUIRE_CONFIG_DIGEST === "1" : true
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
const envPlan = targeted ? { derived: {}, missingImagePins: [], unresolvable: [] } : planConfigOnlyEnv({ stagedComposeRaw, envRaw: currentEnvRaw, release });
|
|
1078
1228
|
let adopted = {};
|
|
1079
1229
|
if (envPlan.missingImagePins.length > 0 && options.adoptDeployedPins) {
|
|
1080
1230
|
const deployedImages = parseDeployedImages(capture([...composeArgs, "ps", "--format", "json"]));
|
|
@@ -1098,13 +1248,40 @@ async function runStackConfigUpdate(options) {
|
|
|
1098
1248
|
const beforeHashes = parseComposeConfigHashes(capture([...composeArgs, "config", "--hash=*"]));
|
|
1099
1249
|
const beforeImages = parseComposeImages(capture([...composeArgs, "config", "--format", "json"]));
|
|
1100
1250
|
const beforeConfigJson = capture([...composeArgs, "config", "--format", "json"]);
|
|
1251
|
+
let deployedBeforeJson = capture([...composeArgs, ...DEPLOYED_IMAGE_PS_ARGS]);
|
|
1252
|
+
const assertTargetedRuntimeGates = () => {
|
|
1253
|
+
if (!targeted) return;
|
|
1254
|
+
const consumers = assertTargetedAssetConsumers(targeted, beforeConfigJson, configDir);
|
|
1255
|
+
assertRollbackConfigMatchesDeployed(
|
|
1256
|
+
beforeHashes,
|
|
1257
|
+
parseDeployedConfigHashes(deployedBeforeJson),
|
|
1258
|
+
consumers
|
|
1259
|
+
);
|
|
1260
|
+
assertNoPendingImageMovement(
|
|
1261
|
+
beforeImages,
|
|
1262
|
+
parseDeployedImages(deployedBeforeJson),
|
|
1263
|
+
consumers
|
|
1264
|
+
);
|
|
1265
|
+
};
|
|
1266
|
+
assertTargetedRuntimeGates();
|
|
1101
1267
|
const hostAssetDigests = collectConfigAssetDigests(configDir);
|
|
1268
|
+
const hostAssetModes = collectConfigAssetModes(configDir);
|
|
1269
|
+
const stagedAssetModes = targeted ? Object.fromEntries(targeted.map((patch) => [patch.rel, patch.mode ?? hostAssetModes[patch.rel]])) : collectConfigAssetModes(path2.dirname(templatePath));
|
|
1102
1270
|
const assetChanges = diffConfigAssets(stagedAssetDigests, hostAssetDigests, {
|
|
1103
|
-
packaged:
|
|
1104
|
-
host:
|
|
1271
|
+
packaged: stagedAssetModes,
|
|
1272
|
+
host: hostAssetModes
|
|
1105
1273
|
});
|
|
1274
|
+
for (const patch of targeted?.filter((p) => p.state === "to") ?? []) {
|
|
1275
|
+
if (!assetChanges.some((change) => change.rel === patch.rel)) {
|
|
1276
|
+
assetChanges.push({ rel: patch.rel, kind: "changed", reason: "bytes" });
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1106
1279
|
const composeChanged = stagedComposeRaw !== currentComposeRaw;
|
|
1107
1280
|
if (!composeChanged && assetChanges.length === 0) {
|
|
1281
|
+
if (targeted) {
|
|
1282
|
+
assertTargetedAssetConsumers(targeted, beforeConfigJson, configDir);
|
|
1283
|
+
assertTargetedControlFilesUnchanged();
|
|
1284
|
+
}
|
|
1108
1285
|
const envVerdict = collectRenderedEnvDrift(
|
|
1109
1286
|
beforeConfigJson,
|
|
1110
1287
|
readDeployedServiceEnv(capture, capture([...composeArgs, ...DEPLOYED_IMAGE_PS_ARGS]))
|
|
@@ -1135,6 +1312,12 @@ async function runStackConfigUpdate(options) {
|
|
|
1135
1312
|
};
|
|
1136
1313
|
}
|
|
1137
1314
|
console.log(`[stack-config-update] Compose template: ${composeChanged ? "REFRESH" : "already current"}`);
|
|
1315
|
+
if (targeted) {
|
|
1316
|
+
console.log("[stack-config-update] Signed targeted asset-only transition; compose, env and lock are immutable:");
|
|
1317
|
+
for (const patch of targeted) {
|
|
1318
|
+
console.log(` - ${patch.rel}: ${patch.fromSha256} \u2192 ${patch.toSha256}; consumers: ${patch.consumers.join(", ")}`);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1138
1321
|
if (assetChanges.length > 0) {
|
|
1139
1322
|
console.log(`[stack-config-update] Config assets to deliver (${assetChanges.length}):`);
|
|
1140
1323
|
for (const a of assetChanges) {
|
|
@@ -1166,6 +1349,12 @@ async function runStackConfigUpdate(options) {
|
|
|
1166
1349
|
const psJson = capture([...composeArgs, ...DEPLOYED_IMAGE_PS_ARGS]);
|
|
1167
1350
|
const envVerdict = collectRenderedEnvDrift(afterConfigJson, readDeployedServiceEnv(capture, psJson));
|
|
1168
1351
|
const assetServices = assetChanges.length > 0 ? servicesMountingPaths(afterConfigJson, configDir, assetChanges.map((a) => a.rel)) : [];
|
|
1352
|
+
if (targeted) {
|
|
1353
|
+
const authenticatedConsumers = assertTargetedAssetConsumers(targeted, afterConfigJson, configDir);
|
|
1354
|
+
if (JSON.stringify([...assetServices].sort()) !== JSON.stringify(authenticatedConsumers)) {
|
|
1355
|
+
throw new Error("Targeted config plan selected consumers outside the signed consumer set");
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1169
1358
|
const wouldRecreate = [.../* @__PURE__ */ new Set([...changeSet.changed, ...assetServices])].sort();
|
|
1170
1359
|
const pending = collectPendingImageMovements(
|
|
1171
1360
|
parseComposeImages(afterConfigJson),
|
|
@@ -1216,7 +1405,16 @@ async function runStackConfigUpdate(options) {
|
|
|
1216
1405
|
rmSync2(scratch, { recursive: true, force: true });
|
|
1217
1406
|
}
|
|
1218
1407
|
}
|
|
1219
|
-
|
|
1408
|
+
if (targeted) validateTargetedAssetPatches({
|
|
1409
|
+
config: release.config,
|
|
1410
|
+
packagedRoot: path2.dirname(templatePath),
|
|
1411
|
+
liveRoot: configDir
|
|
1412
|
+
});
|
|
1413
|
+
assertTargetedControlFilesUnchanged();
|
|
1414
|
+
if (targeted) {
|
|
1415
|
+
deployedBeforeJson = capture([...composeArgs, ...DEPLOYED_IMAGE_PS_ARGS]);
|
|
1416
|
+
assertTargetedRuntimeGates();
|
|
1417
|
+
}
|
|
1220
1418
|
const backup = backupStackConfig(stackDir, options.composeFile, currentEnvRaw, now);
|
|
1221
1419
|
console.log(`[stack-config-update] Backed up compose + env to ${backup.updateBackupDir}`);
|
|
1222
1420
|
const assetRollback = /* @__PURE__ */ new Map();
|
|
@@ -1224,8 +1422,13 @@ async function runStackConfigUpdate(options) {
|
|
|
1224
1422
|
const addedRels = [];
|
|
1225
1423
|
for (const change of assetChanges) {
|
|
1226
1424
|
const target = path2.join(configDir, change.rel);
|
|
1227
|
-
const
|
|
1228
|
-
const prior =
|
|
1425
|
+
const priorStat = existsSync2(target) ? statSync(target) : void 0;
|
|
1426
|
+
const prior = priorStat === void 0 ? null : {
|
|
1427
|
+
bytes: readFileSync2(target),
|
|
1428
|
+
mode: priorStat.mode & 4095,
|
|
1429
|
+
uid: priorStat.uid,
|
|
1430
|
+
gid: priorStat.gid
|
|
1431
|
+
};
|
|
1229
1432
|
assetRollback.set(change.rel, prior);
|
|
1230
1433
|
if (prior === null) {
|
|
1231
1434
|
addedRels.push(change.rel);
|
|
@@ -1260,8 +1463,8 @@ ${addedRels.map((r) => `${r}
|
|
|
1260
1463
|
return false;
|
|
1261
1464
|
}
|
|
1262
1465
|
};
|
|
1263
|
-
const composeRestored = step(`restore ${options.composeFile}`, () => writeFileSync2(options.composeFile, currentComposeRaw));
|
|
1264
|
-
const envRestored = step(`restore ${options.envFile}`, () => writeFileSync2(options.envFile, currentEnvRaw));
|
|
1466
|
+
const composeRestored = targeted ? step(`verify unchanged ${options.composeFile}`, assertTargetedControlFilesUnchanged) : step(`restore ${options.composeFile}`, () => writeFileSync2(options.composeFile, currentComposeRaw));
|
|
1467
|
+
const envRestored = targeted ? composeRestored : step(`restore ${options.envFile}`, () => writeFileSync2(options.envFile, currentEnvRaw));
|
|
1265
1468
|
const unsafeServices = /* @__PURE__ */ new Set();
|
|
1266
1469
|
for (const [rel, prior] of assetRollback) {
|
|
1267
1470
|
const restored = step(`restore asset ${rel}`, () => {
|
|
@@ -1270,8 +1473,9 @@ ${addedRels.map((r) => `${r}
|
|
|
1270
1473
|
rmSync2(target, { force: true });
|
|
1271
1474
|
return;
|
|
1272
1475
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1476
|
+
if (targeted) atomicRestoreAsset(target, prior);
|
|
1477
|
+
else writeFileSync2(target, prior.bytes);
|
|
1478
|
+
if (!targeted) chmodSync(target, prior.mode);
|
|
1275
1479
|
});
|
|
1276
1480
|
if (!restored) {
|
|
1277
1481
|
for (const service of servicesMountingPaths(beforeConfigJson, configDir, [rel])) {
|
|
@@ -1354,8 +1558,10 @@ ${addedRels.map((r) => `${r}
|
|
|
1354
1558
|
const target = path2.join(configDir, change.rel);
|
|
1355
1559
|
const source = path2.join(path2.dirname(templatePath), change.rel);
|
|
1356
1560
|
mkdirSync2(path2.dirname(target), { recursive: true });
|
|
1357
|
-
|
|
1358
|
-
|
|
1561
|
+
const targetedPatch = targeted?.find((p) => p.rel === change.rel);
|
|
1562
|
+
if (targetedPatch) atomicReplaceTargetedAsset(targetedPatch);
|
|
1563
|
+
else writeFileSync2(target, readFileSync2(source));
|
|
1564
|
+
if (!targetedPatch) chmodSync(target, statSync(source).mode & 511);
|
|
1359
1565
|
}
|
|
1360
1566
|
const afterConfigJson = capture([...composeArgs, "config", "--format", "json"]);
|
|
1361
1567
|
const afterImages = parseComposeImages(afterConfigJson);
|
|
@@ -1381,6 +1587,12 @@ ${addedRels.map((r) => `${r}
|
|
|
1381
1587
|
for (const d of envVerdict.drift) console.log(` - ${d.service}: ${d.missingKeys.join(", ")}`);
|
|
1382
1588
|
}
|
|
1383
1589
|
const assetServices = assetChanges.length > 0 ? servicesMountingPaths(afterConfigJson, configDir, assetChanges.map((a) => a.rel)) : [];
|
|
1590
|
+
if (targeted) {
|
|
1591
|
+
const authenticatedConsumers = assertTargetedAssetConsumers(targeted, afterConfigJson, configDir);
|
|
1592
|
+
if (JSON.stringify([...assetServices].sort()) !== JSON.stringify(authenticatedConsumers)) {
|
|
1593
|
+
throw new Error("Targeted config apply selected consumers outside the signed consumer set");
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1384
1596
|
const orphanAssets = assetChanges.length > 0 && assetServices.length === 0;
|
|
1385
1597
|
if (orphanAssets) {
|
|
1386
1598
|
console.warn(
|
|
@@ -1436,6 +1648,24 @@ ${addedRels.map((r) => `${r}
|
|
|
1436
1648
|
}
|
|
1437
1649
|
result = { status: "applied", stackVersion, stackVersionSource, changed: changeSet.changed, added: [], removed: changeSet.removed, envDrift: envVerdict.drift, envUnverified: envVerdict.unverified, assets: assetChanges, backup };
|
|
1438
1650
|
}
|
|
1651
|
+
if (targeted) {
|
|
1652
|
+
assertTargetedControlFilesUnchanged();
|
|
1653
|
+
const consumers = assertTargetedAssetConsumers(targeted, beforeConfigJson, configDir);
|
|
1654
|
+
const deployedAfterJson = capture([...composeArgs, ...DEPLOYED_IMAGE_PS_ARGS]);
|
|
1655
|
+
assertNoPendingImageMovement(beforeImages, parseDeployedImages(deployedAfterJson), consumers);
|
|
1656
|
+
assertRollbackConfigMatchesDeployed(beforeHashes, parseDeployedConfigHashes(deployedAfterJson), consumers);
|
|
1657
|
+
await postConfigAudit(options, "succeeded", stackVersion, void 0, {
|
|
1658
|
+
changedServices: result.changed,
|
|
1659
|
+
configAssets: targeted.map((p) => ({
|
|
1660
|
+
path: p.rel,
|
|
1661
|
+
fromSha256: p.fromSha256,
|
|
1662
|
+
toSha256: p.toSha256,
|
|
1663
|
+
consumers: p.consumers
|
|
1664
|
+
})),
|
|
1665
|
+
backupDir: result.backup?.updateBackupDir,
|
|
1666
|
+
targetedAssetOnly: true
|
|
1667
|
+
}, true);
|
|
1668
|
+
}
|
|
1439
1669
|
} catch (err) {
|
|
1440
1670
|
const original = err instanceof Error ? err : new Error(String(err));
|
|
1441
1671
|
const rollback = await restore();
|
|
@@ -1458,18 +1688,23 @@ ${addedRels.map((r) => `${r}
|
|
|
1458
1688
|
}
|
|
1459
1689
|
throw original;
|
|
1460
1690
|
}
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1691
|
+
if (!targeted) {
|
|
1692
|
+
await postConfigAudit(options, "succeeded", stackVersion, void 0, {
|
|
1693
|
+
changedServices: result.changed,
|
|
1694
|
+
removedFromTemplate: result.removed,
|
|
1695
|
+
// Keys only — never values.
|
|
1696
|
+
renderedEnvKeysAdded: result.envDrift,
|
|
1697
|
+
configAssets: result.assets,
|
|
1698
|
+
backupDir: result.backup?.updateBackupDir
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1469
1701
|
return result;
|
|
1470
1702
|
}
|
|
1471
|
-
async function postConfigAudit(options, status, stackVersion, error, metadata) {
|
|
1472
|
-
if (!options.auditUrl)
|
|
1703
|
+
async function postConfigAudit(options, status, stackVersion, error, metadata, required = false) {
|
|
1704
|
+
if (!options.auditUrl) {
|
|
1705
|
+
if (required) throw new Error("Targeted config delivery requires an authenticated audit URL");
|
|
1706
|
+
return;
|
|
1707
|
+
}
|
|
1473
1708
|
const postJson = options.postJson ?? defaultPostJson;
|
|
1474
1709
|
try {
|
|
1475
1710
|
await postJson(options.auditUrl, {
|
|
@@ -1483,6 +1718,7 @@ async function postConfigAudit(options, status, stackVersion, error, metadata) {
|
|
|
1483
1718
|
}, options.manifestAuthToken);
|
|
1484
1719
|
} catch (err) {
|
|
1485
1720
|
console.warn(`[stack-config-update] deploy audit failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1721
|
+
if (required) throw err;
|
|
1486
1722
|
}
|
|
1487
1723
|
}
|
|
1488
1724
|
function resolvePackagedComposeDir() {
|
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-23T01:55:53.241Z",
|
|
4
4
|
"files": [
|
|
5
5
|
{
|
|
6
6
|
"path": "active-agent-RZ24KSJN.js",
|
|
@@ -179,7 +179,7 @@
|
|
|
179
179
|
},
|
|
180
180
|
{
|
|
181
181
|
"path": "bin/cli.js",
|
|
182
|
-
"sha256": "
|
|
182
|
+
"sha256": "f28c012345311069b7b0cf118ffd90f721a35bc1caae17389c4973866a1e631e",
|
|
183
183
|
"bytes": 69528
|
|
184
184
|
},
|
|
185
185
|
{
|
|
@@ -524,7 +524,7 @@
|
|
|
524
524
|
},
|
|
525
525
|
{
|
|
526
526
|
"path": "bin/registry-proxy.js",
|
|
527
|
-
"sha256": "
|
|
527
|
+
"sha256": "f6863591b7274a3e5762805442d0d398b0245d2da9a8c8db3cd53be89dda9a5f",
|
|
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": "ac357db942b991e27bd9ecfa8396bfbceec2a636472d993247ea04e6b1e8026e",
|
|
553
|
+
"bytes": 124422
|
|
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": "6f1523300863a2955319c458138533e788a090a976ba5e4e23d0bace31617480",
|
|
573
573
|
"bytes": 333
|
|
574
574
|
},
|
|
575
575
|
{
|
|
@@ -1087,6 +1087,11 @@
|
|
|
1087
1087
|
"sha256": "cf3ce4cf6c9cdba17a8a5e3d9f0c78ae42f1cb72f1a06608cc8da1fb27f06a76",
|
|
1088
1088
|
"bytes": 3292
|
|
1089
1089
|
},
|
|
1090
|
+
{
|
|
1091
|
+
"path": "chunk-DZPSBA4B.js",
|
|
1092
|
+
"sha256": "10ad560a524f111988594bc6b060e31f7eff292836561b19d520595b77c60662",
|
|
1093
|
+
"bytes": 217772
|
|
1094
|
+
},
|
|
1090
1095
|
{
|
|
1091
1096
|
"path": "chunk-E23LNUWJ.js",
|
|
1092
1097
|
"sha256": "6d692be7ac73bf481e7013c4b948b70acd8282234f645d562457754afaa7a02e",
|
|
@@ -1242,6 +1247,11 @@
|
|
|
1242
1247
|
"sha256": "0e8678ecd10db44714ef9a25fcab116b0cb3f86b6de9e384ac5bf2effb0dc3c0",
|
|
1243
1248
|
"bytes": 2405
|
|
1244
1249
|
},
|
|
1250
|
+
{
|
|
1251
|
+
"path": "chunk-HJNQAEZV.js",
|
|
1252
|
+
"sha256": "0ca99c9f7c52613ebee3db4f210a65094c9afd7b3abceb09ba4638c216ce9078",
|
|
1253
|
+
"bytes": 3766
|
|
1254
|
+
},
|
|
1245
1255
|
{
|
|
1246
1256
|
"path": "chunk-HME6ITKV.js",
|
|
1247
1257
|
"sha256": "d820c2d874d6841b5c9c1186ba98c24aa25d559b9be05a29ae2373e7f70e7960",
|
|
@@ -1302,11 +1312,6 @@
|
|
|
1302
1312
|
"sha256": "c0b3a6372de1ceaaa0ee8077beab90baddb26d44c0fbf933543b963927819e16",
|
|
1303
1313
|
"bytes": 57011
|
|
1304
1314
|
},
|
|
1305
|
-
{
|
|
1306
|
-
"path": "chunk-J4UVI652.js",
|
|
1307
|
-
"sha256": "3f9abccde5d49dcc844f16c00b04dd9c4166c72c09da21aba1d77c13c48e833e",
|
|
1308
|
-
"bytes": 7587
|
|
1309
|
-
},
|
|
1310
1315
|
{
|
|
1311
1316
|
"path": "chunk-J6KMGEJW.js",
|
|
1312
1317
|
"sha256": "4ed2938b62c86ebc41d12ac03a4217103a1ae035764b21707a6f4dc301df3d5d",
|
|
@@ -1582,11 +1587,6 @@
|
|
|
1582
1587
|
"sha256": "3dbde846e7f1326185966deb9f0b3f9a476e5eee8185707e841e6e032c4e3cb3",
|
|
1583
1588
|
"bytes": 3317
|
|
1584
1589
|
},
|
|
1585
|
-
{
|
|
1586
|
-
"path": "chunk-PZR54BEI.js",
|
|
1587
|
-
"sha256": "a4d5d925118925edec323fd27179e708a905b6967c0b5fa17906b4907e8bfaec",
|
|
1588
|
-
"bytes": 40844
|
|
1589
|
-
},
|
|
1590
1590
|
{
|
|
1591
1591
|
"path": "chunk-Q3V7K4ME.js",
|
|
1592
1592
|
"sha256": "da852c37b5a75dd01c94885d382a0e940438d0c1c2c4eacef5b354edf5e6468e",
|
|
@@ -1742,11 +1742,6 @@
|
|
|
1742
1742
|
"sha256": "032609f5317492d3118b5e8c3d7392dccc7b7b628996f78167c0d108cde68130",
|
|
1743
1743
|
"bytes": 13671
|
|
1744
1744
|
},
|
|
1745
|
-
{
|
|
1746
|
-
"path": "chunk-SLMKLEU6.js",
|
|
1747
|
-
"sha256": "dbcd057bc0b493c6aa1912c794ff7177a6e045c978dfd05370427cb3e8ee94ff",
|
|
1748
|
-
"bytes": 3766
|
|
1749
|
-
},
|
|
1750
1745
|
{
|
|
1751
1746
|
"path": "chunk-SNHYZYGZ.js",
|
|
1752
1747
|
"sha256": "f0b853b3055ffc0f869ade14fa735d7e7457c6d77c78f58244bcaa9c0b8a2b36",
|
|
@@ -1857,11 +1852,6 @@
|
|
|
1857
1852
|
"sha256": "ae7f759300bb873272d6570df8f4e898f1cb08a7411543a35ce80e3f00f0bb43",
|
|
1858
1853
|
"bytes": 7964
|
|
1859
1854
|
},
|
|
1860
|
-
{
|
|
1861
|
-
"path": "chunk-UWVXQEQQ.js",
|
|
1862
|
-
"sha256": "349cfc7abc14b5a1a1abbc73aa756368f6644e43e0257d5949b927f7027cd6e4",
|
|
1863
|
-
"bytes": 216205
|
|
1864
|
-
},
|
|
1865
1855
|
{
|
|
1866
1856
|
"path": "chunk-V4TZI6EO.js",
|
|
1867
1857
|
"sha256": "697ecdef6991c061baab1e9759bc14aecd28631954b2edfdb721c98c72d41c82",
|
|
@@ -1932,6 +1922,11 @@
|
|
|
1932
1922
|
"sha256": "4a905397d309c73ed4ad5d1ec488309541be3eec2fc6021d275f433ca92cc7a9",
|
|
1933
1923
|
"bytes": 2265
|
|
1934
1924
|
},
|
|
1925
|
+
{
|
|
1926
|
+
"path": "chunk-WQN2XK4V.js",
|
|
1927
|
+
"sha256": "802b24d979abc8934743af0537c96194aed4b4965ecec08b64c39cc58121427e",
|
|
1928
|
+
"bytes": 7587
|
|
1929
|
+
},
|
|
1935
1930
|
{
|
|
1936
1931
|
"path": "chunk-WT4RI36X.js",
|
|
1937
1932
|
"sha256": "7ce4fd5dee79504398f6549a7fadeceafaadaea2cd94ef9bcc310280306147c6",
|
|
@@ -2007,6 +2002,11 @@
|
|
|
2007
2002
|
"sha256": "176e0aa963a006bc1b64eef84c5ec6eeb4c80ab70ee8dddeb578f4448e48a769",
|
|
2008
2003
|
"bytes": 19304
|
|
2009
2004
|
},
|
|
2005
|
+
{
|
|
2006
|
+
"path": "chunk-YI4D6E75.js",
|
|
2007
|
+
"sha256": "5d05ebae4399895f6857bcc6f3cba70bcdee9d2113faf2b6d5e522d647b10ab2",
|
|
2008
|
+
"bytes": 40844
|
|
2009
|
+
},
|
|
2010
2010
|
{
|
|
2011
2011
|
"path": "chunk-YMRQIENV.js",
|
|
2012
2012
|
"sha256": "5590051acf78d41e8e1901a3293246b3fceadd28b972b2ff895889df80fbdab9",
|
|
@@ -2539,7 +2539,7 @@
|
|
|
2539
2539
|
},
|
|
2540
2540
|
{
|
|
2541
2541
|
"path": "hooks/manifest.json",
|
|
2542
|
-
"sha256": "
|
|
2542
|
+
"sha256": "d14c91374638ec9d80f35caf6f5666a7e39718c256396130c8817e1f7a6dfdec",
|
|
2543
2543
|
"bytes": 1841
|
|
2544
2544
|
},
|
|
2545
2545
|
{
|
|
@@ -2839,7 +2839,7 @@
|
|
|
2839
2839
|
},
|
|
2840
2840
|
{
|
|
2841
2841
|
"path": "lib/registry-proxy.js",
|
|
2842
|
-
"sha256": "
|
|
2842
|
+
"sha256": "988a25b33ecca19b40cbdb9111bdc500003c1503951fc39c043a68a73d7e8f49",
|
|
2843
2843
|
"bytes": 749
|
|
2844
2844
|
},
|
|
2845
2845
|
{
|
|
@@ -3413,8 +3413,8 @@
|
|
|
3413
3413
|
"bytes": 111
|
|
3414
3414
|
},
|
|
3415
3415
|
{
|
|
3416
|
-
"path": "stack-generate-manifest-
|
|
3417
|
-
"sha256": "
|
|
3416
|
+
"path": "stack-generate-manifest-VWNDIMZ2.js",
|
|
3417
|
+
"sha256": "4add4b1efd40afc672c5548604be13f6cb631d2804f43081278604529054ccef",
|
|
3418
3418
|
"bytes": 56960
|
|
3419
3419
|
},
|
|
3420
3420
|
{
|
|
@@ -3423,19 +3423,19 @@
|
|
|
3423
3423
|
"bytes": 4761
|
|
3424
3424
|
},
|
|
3425
3425
|
{
|
|
3426
|
-
"path": "stack-promote-
|
|
3427
|
-
"sha256": "
|
|
3426
|
+
"path": "stack-promote-DFXJDHVA.js",
|
|
3427
|
+
"sha256": "d401050d81af3fa38a53044a3e7e51956d79bbf6b3e6f042b026a1d979ee42ce",
|
|
3428
3428
|
"bytes": 7425
|
|
3429
3429
|
},
|
|
3430
3430
|
{
|
|
3431
|
-
"path": "stack-release-
|
|
3432
|
-
"sha256": "
|
|
3431
|
+
"path": "stack-release-RQ2DYYD5.js",
|
|
3432
|
+
"sha256": "5b3f61e3c60c6ad2bf17d13a93b8925cb9eb8f1e0638aa63ec7314aab1286f1a",
|
|
3433
3433
|
"bytes": 117550
|
|
3434
3434
|
},
|
|
3435
3435
|
{
|
|
3436
|
-
"path": "stack-update-
|
|
3437
|
-
"sha256": "
|
|
3438
|
-
"bytes":
|
|
3436
|
+
"path": "stack-update-MVGCN3C4.js",
|
|
3437
|
+
"sha256": "00460b684d4ee48912250328a7b1d90231a120b66e60e7024f03b43c49382257",
|
|
3438
|
+
"bytes": 6967
|
|
3439
3439
|
},
|
|
3440
3440
|
{
|
|
3441
3441
|
"path": "stalled-dispatch-escalation-MHLYH2PT.js",
|
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
|
|
23
23
|
// src/lib/stack-update.ts
|
|
24
24
|
import { execFileSync, spawnSync as spawnSync2 } from "child_process";
|
|
25
|
-
import { createVerify, randomBytes, verify as verifySignature } from "crypto";
|
|
26
|
-
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
25
|
+
import { createHash, createVerify, randomBytes, verify as verifySignature } from "crypto";
|
|
26
|
+
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, realpathSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
27
27
|
import http from "http";
|
|
28
28
|
import https from "https";
|
|
29
29
|
import net from "net";
|
|
@@ -3142,7 +3142,44 @@ function isFatalLiveArtifactFailure(result) {
|
|
|
3142
3142
|
if (!result.verdicts) return true;
|
|
3143
3143
|
return (result.verdicts.disagrees ?? 0) > 0;
|
|
3144
3144
|
}
|
|
3145
|
+
function stackUpdateOperationLockPort(envFile) {
|
|
3146
|
+
let ancestor = path.resolve(envFile);
|
|
3147
|
+
const suffix = [];
|
|
3148
|
+
while (!existsSync(ancestor)) {
|
|
3149
|
+
const parent = path.dirname(ancestor);
|
|
3150
|
+
if (parent === ancestor) throw new Error(`Cannot canonicalize stack update path: ${envFile}`);
|
|
3151
|
+
suffix.unshift(path.basename(ancestor));
|
|
3152
|
+
ancestor = parent;
|
|
3153
|
+
}
|
|
3154
|
+
const canonical = path.join(realpathSync(ancestor), ...suffix);
|
|
3155
|
+
const digest = createHash("sha256").update(canonical).digest();
|
|
3156
|
+
return 4e4 + digest.readUInt32BE(0) % 2e4;
|
|
3157
|
+
}
|
|
3158
|
+
async function withStackUpdateOperationLock(envFile, operation) {
|
|
3159
|
+
const port = stackUpdateOperationLockPort(envFile);
|
|
3160
|
+
const server = net.createServer();
|
|
3161
|
+
server.on("connection", (socket) => socket.destroy());
|
|
3162
|
+
await new Promise((resolve, reject) => {
|
|
3163
|
+
const onError = (err) => {
|
|
3164
|
+
server.close();
|
|
3165
|
+
reject(err.code === "EADDRINUSE" ? new Error(`Another stack update operation holds 127.0.0.1:${port}; refusing concurrent mutation`) : err);
|
|
3166
|
+
};
|
|
3167
|
+
server.once("error", onError);
|
|
3168
|
+
server.listen({ host: "127.0.0.1", port, exclusive: true }, () => {
|
|
3169
|
+
server.off("error", onError);
|
|
3170
|
+
resolve();
|
|
3171
|
+
});
|
|
3172
|
+
});
|
|
3173
|
+
try {
|
|
3174
|
+
return await operation();
|
|
3175
|
+
} finally {
|
|
3176
|
+
await new Promise((resolve, reject) => server.close((err) => err ? reject(err) : resolve()));
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3145
3179
|
async function runStackUpdate(options) {
|
|
3180
|
+
return withStackUpdateOperationLock(options.envFile, () => runStackUpdateUnlocked(options));
|
|
3181
|
+
}
|
|
3182
|
+
async function runStackUpdateUnlocked(options) {
|
|
3146
3183
|
const exec = options.exec ?? defaultExec;
|
|
3147
3184
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
3148
3185
|
const phaseDurations = [];
|
|
@@ -4845,6 +4882,8 @@ export {
|
|
|
4845
4882
|
hardenHost,
|
|
4846
4883
|
LIVE_ARTIFACT_MATCHES_MANIFEST_CHECK,
|
|
4847
4884
|
isFatalLiveArtifactFailure,
|
|
4885
|
+
stackUpdateOperationLockPort,
|
|
4886
|
+
withStackUpdateOperationLock,
|
|
4848
4887
|
runStackUpdate,
|
|
4849
4888
|
readCurrentStackVersion,
|
|
4850
4889
|
listAvailableVersions,
|
|
@@ -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-MVGCN3C4.js");
|
|
216
216
|
const paths = defaultStackPaths();
|
|
217
217
|
await rollbackStackUpdate({
|
|
218
218
|
manifestRef: paths.manifestRef,
|
|
@@ -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-MVGCN3C4.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-23T01:55:53.027Z",
|
|
4
4
|
"hashes": {
|
|
5
5
|
"bug-report-worker.js": "e114760c0e7c5c3637bfa9a9041a4390760d541dcdacb9c7d751c9fc1d8311af",
|
|
6
6
|
"codex-stop-task-finalizer.js": "1a465be64326faca8789b113ec2a1d6b8f9020d97dd1ac0a01fec30447d8b2eb",
|
|
@@ -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-HJNQAEZV.js";
|
|
5
|
+
import "./chunk-DZPSBA4B.js";
|
|
6
6
|
import "./chunk-EC5H53YD.js";
|
|
7
7
|
import "./chunk-B7WVYLAQ.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-HJNQAEZV.js";
|
|
5
|
+
import "./chunk-DZPSBA4B.js";
|
|
6
6
|
import "./chunk-EC5H53YD.js";
|
|
7
7
|
import "./chunk-B7WVYLAQ.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-HJNQAEZV.js";
|
|
5
|
+
import "./chunk-DZPSBA4B.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-VWNDIMZ2.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) {
|
|
@@ -103,6 +103,7 @@ import {
|
|
|
103
103
|
saveServiceSelection,
|
|
104
104
|
serviceSelectionPath,
|
|
105
105
|
snapshotComposeImageDefaults,
|
|
106
|
+
stackUpdateOperationLockPort,
|
|
106
107
|
stripImageDigest,
|
|
107
108
|
syncImagePinsWithProcessEnv,
|
|
108
109
|
unmanagedManifestServices,
|
|
@@ -115,8 +116,9 @@ import {
|
|
|
115
116
|
verifySsoGatedIngressRouting,
|
|
116
117
|
verifyStackManifestSignature,
|
|
117
118
|
waitForHttpOk,
|
|
118
|
-
warnUnmanagedManifestServices
|
|
119
|
-
|
|
119
|
+
warnUnmanagedManifestServices,
|
|
120
|
+
withStackUpdateOperationLock
|
|
121
|
+
} from "./chunk-DZPSBA4B.js";
|
|
120
122
|
import "./chunk-EC5H53YD.js";
|
|
121
123
|
import "./chunk-B7WVYLAQ.js";
|
|
122
124
|
import "./chunk-L25OAFOU.js";
|
|
@@ -234,6 +236,7 @@ export {
|
|
|
234
236
|
saveServiceSelection,
|
|
235
237
|
serviceSelectionPath,
|
|
236
238
|
snapshotComposeImageDefaults,
|
|
239
|
+
stackUpdateOperationLockPort,
|
|
237
240
|
stripImageDigest,
|
|
238
241
|
syncImagePinsWithProcessEnv,
|
|
239
242
|
unmanagedManifestServices,
|
|
@@ -246,5 +249,6 @@ export {
|
|
|
246
249
|
verifySsoGatedIngressRouting,
|
|
247
250
|
verifyStackManifestSignature,
|
|
248
251
|
waitForHttpOk,
|
|
249
|
-
warnUnmanagedManifestServices
|
|
252
|
+
warnUnmanagedManifestServices,
|
|
253
|
+
withStackUpdateOperationLock
|
|
250
254
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askexenow/exe-os",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.390",
|
|
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",
|
package/release-notes.json
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
{
|
|
2
|
-
"current": "0.9.
|
|
2
|
+
"current": "0.9.390",
|
|
3
3
|
"notes": {
|
|
4
|
-
"0.9.
|
|
5
|
-
"version": "0.9.
|
|
6
|
-
"date": "2026-09-
|
|
7
|
-
"features": [
|
|
4
|
+
"0.9.390": {
|
|
5
|
+
"version": "0.9.390",
|
|
6
|
+
"date": "2026-09-23",
|
|
7
|
+
"features": [
|
|
8
|
+
"add signed targeted assets, kernel locking, rollback, and regressions"
|
|
9
|
+
],
|
|
8
10
|
"fixes": [
|
|
9
|
-
"
|
|
11
|
+
"serialize aliases and preflight targeted audit"
|
|
10
12
|
],
|
|
11
13
|
"security": [],
|
|
12
|
-
"other": [
|
|
14
|
+
"other": [
|
|
15
|
+
"prepare updater v0.9.390",
|
|
16
|
+
"signed config-only canary v0.9.48"
|
|
17
|
+
],
|
|
13
18
|
"migration_notes": []
|
|
14
19
|
},
|
|
15
20
|
"0.9.371": {
|
package/stack.release.json
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"repo": "AskExe/exe-os",
|
|
5
5
|
"service": "exe-os",
|
|
6
6
|
"packageName": "@askexenow/exe-os",
|
|
7
|
-
"version": "0.9.
|
|
8
|
-
"image": "ghcr.io/askexe/exe-os:v0.9.
|
|
7
|
+
"version": "0.9.390",
|
|
8
|
+
"image": "ghcr.io/askexe/exe-os:v0.9.390",
|
|
9
9
|
"imageEnv": "EXE_OS_IMAGE_TAG",
|
|
10
10
|
"imageDigestTodo": "The top-level image names the forthcoming exe-os build and receives its digest after publication. Independently released components, including dashboard, retain verified digest pins; stack publication validates every service pin.",
|
|
11
11
|
"components": {
|
|
@@ -110,5 +110,5 @@
|
|
|
110
110
|
},
|
|
111
111
|
"deploymentScope": "customer",
|
|
112
112
|
"highGhostStack": "0.9.9",
|
|
113
|
-
"sourceVersion": "0.9.
|
|
113
|
+
"sourceVersion": "0.9.390"
|
|
114
114
|
}
|