@askexenow/exe-os 0.9.377 → 0.9.378
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/compose/.env.example +4 -1
- package/deploy/compose/backup.sh +13 -1
- package/deploy/compose/generate-env.ts +15 -1
- package/deploy/compose/uptime-check.sh +58 -9
- 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 +3 -3
- package/dist/bin/vps-health-gate.js +1 -1
- package/dist/build-manifest.json +43 -43
- package/dist/{chunk-5QKLWJ4Z.js → chunk-3M3N7D7O.js} +1 -1
- package/dist/{chunk-TT5BBVLX.js → chunk-5IUXUD3X.js} +1 -1
- package/dist/{chunk-FCMQVEVL.js → chunk-A3P3CLE6.js} +1 -1
- package/dist/{chunk-GHR4ZPJ7.js → chunk-EC5H53YD.js} +43 -6
- package/dist/{chunk-JRMHGO5B.js → chunk-U3GM5MYE.js} +1 -1
- package/dist/hooks/manifest.json +1 -1
- package/dist/lib/registry-proxy.js +1 -1
- package/dist/{resolve-image-labels-ZI6UBW5W.js → resolve-image-labels-N27XXTUG.js} +3 -1
- package/dist/{stack-generate-manifest-7WJZWSPK.js → stack-generate-manifest-BR3GZESE.js} +4 -4
- package/dist/{stack-promote-OP2URGFF.js → stack-promote-Y4JXWM6R.js} +3 -3
- package/dist/{stack-release-MY6U2QIO.js → stack-release-JFB3NJLO.js} +3 -3
- package/dist/{stack-update-QSHSLUTZ.js → stack-update-SWRZLQVQ.js} +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -10
- package/stack.release.json +4 -4
|
@@ -27,7 +27,10 @@ REDIS_PASSWORD=CHANGEME_REDIS_PASSWORD
|
|
|
27
27
|
# Off-box upload: the daily cron runs `backup.sh --upload-r2`. Without this the
|
|
28
28
|
# encrypted archive only ever lands on the same disk as live data (disk death =
|
|
29
29
|
# total loss). R2 creds are reused from the R2_MEDIA_* vars when set.
|
|
30
|
-
# EXE_BACKUP_KEY
|
|
30
|
+
# EXE_BACKUP_KEY is REQUIRED for off-box upload — backup.sh refuses to push an
|
|
31
|
+
# unencrypted archive to R2/S3. A generated .env gets a random one; set yours with
|
|
32
|
+
# EXE_BACKUP_KEY=$(openssl rand -base64 32)
|
|
33
|
+
EXE_BACKUP_KEY=CHANGEME_EXE_BACKUP_KEY
|
|
31
34
|
BACKUP_RETENTION_DAYS=7
|
|
32
35
|
# Override the DB container name if your topology differs (default: exe-db).
|
|
33
36
|
# BACKUP_DB_CONTAINER=exe-db
|
package/deploy/compose/backup.sh
CHANGED
|
@@ -139,7 +139,8 @@ rm -rf "$ARCHIVE"
|
|
|
139
139
|
echo "[backup] Archive: $TARFILE ($(du -h "$TARFILE" | cut -f1))"
|
|
140
140
|
|
|
141
141
|
# 6b. Encrypt before upload — customer data never leaves the VPS unencrypted.
|
|
142
|
-
# Uses EXE_BACKUP_KEY from .env (256-bit, generated
|
|
142
|
+
# Uses EXE_BACKUP_KEY from .env (256-bit, generated by generate-env.ts and
|
|
143
|
+
# backfilled onto existing hosts by tasks/backup-cron.yml).
|
|
143
144
|
# Without this key, R2 backups are unreadable — even AskExe cannot access them.
|
|
144
145
|
BACKUP_KEY="${EXE_BACKUP_KEY:-}"
|
|
145
146
|
if [[ -z "$BACKUP_KEY" && -f "$SCRIPT_DIR/.env" ]]; then
|
|
@@ -156,8 +157,19 @@ if [[ -n "$BACKUP_KEY" ]]; then
|
|
|
156
157
|
rm -f "$TARFILE"
|
|
157
158
|
TARFILE="$ENCRYPTED_FILE"
|
|
158
159
|
echo "[backup] Encrypted: $TARFILE ($(du -h "$TARFILE" | cut -f1))"
|
|
160
|
+
elif $UPLOAD_R2 || [[ -n "$UPLOAD_S3" ]]; then
|
|
161
|
+
# Off-box upload without a key would push the WHOLE archive — pg_dumpall of
|
|
162
|
+
# every database, the stack .env with POSTGRES_PASSWORD and GOTRUE_JWT_SECRET
|
|
163
|
+
# in it, and the WhatsApp auth state — to third-party object storage in
|
|
164
|
+
# plaintext. The deploy playbook installs `backup.sh --upload-r2` as a nightly
|
|
165
|
+
# root cron, so a warn-and-continue here meant every box whose .env never got
|
|
166
|
+
# the key shipped customer data in the clear every night. Refuse. An operator
|
|
167
|
+
# who wants an unencrypted archive can still take one — locally, by running
|
|
168
|
+
# backup.sh with no upload flag.
|
|
169
|
+
fail "encrypt" "EXE_BACKUP_KEY not set — refusing to upload UNENCRYPTED customer data off-box. Set EXE_BACKUP_KEY in /opt/exe-stack/.env (openssl rand -base64 32), or drop --upload-r2/--upload-s3 to keep the archive on this host."
|
|
159
170
|
else
|
|
160
171
|
echo "[backup] WARNING: EXE_BACKUP_KEY not set — backup NOT encrypted. Set it in .env for E2E encryption." >&2
|
|
172
|
+
echo "[backup] (an off-box upload would have been refused; local-only archive kept)" >&2
|
|
161
173
|
fi
|
|
162
174
|
|
|
163
175
|
# 7. Retention — delete old backups (match BOTH .tar.gz and encrypted .tar.gz.enc).
|
|
@@ -225,6 +225,17 @@ export function generateEnv(options: GenerateEnvOptions): string {
|
|
|
225
225
|
"",
|
|
226
226
|
`REDIS_PASSWORD=${randomSecret(RANDOM_SECRET_32)}`,
|
|
227
227
|
"",
|
|
228
|
+
"# --- Backups ---",
|
|
229
|
+
"# The nightly cron installed by deploy.yml runs `backup.sh --upload-r2`, which",
|
|
230
|
+
"# pushes the archive to third-party object storage. That archive contains a",
|
|
231
|
+
"# pg_dumpall of every database, THIS .env (POSTGRES_PASSWORD, GOTRUE_JWT_SECRET),",
|
|
232
|
+
"# and the WhatsApp auth state — so it is encrypted with AES-256 under the key",
|
|
233
|
+
"# below before it leaves the host, and backup.sh REFUSES to upload without one.",
|
|
234
|
+
"# Generated here rather than left commented out: an unset key used to mean a",
|
|
235
|
+
"# silent plaintext upload every night. Keep a copy — losing it makes every",
|
|
236
|
+
"# off-box backup unrecoverable, by design (not even AskExe can read them).",
|
|
237
|
+
`EXE_BACKUP_KEY=${randomSecret(RANDOM_SECRET_32)}`,
|
|
238
|
+
"",
|
|
228
239
|
"# --- GoTrue (shared auth) ---",
|
|
229
240
|
`GOTRUE_JWT_SECRET=${randomSecret(RANDOM_SECRET_48)}`,
|
|
230
241
|
"GOTRUE_API_PORT=9999",
|
|
@@ -530,7 +541,10 @@ export function generateExampleEnv(): string {
|
|
|
530
541
|
"# Off-box upload: the daily cron runs `backup.sh --upload-r2`. Without this the",
|
|
531
542
|
"# encrypted archive only ever lands on the same disk as live data (disk death =",
|
|
532
543
|
"# total loss). R2 creds are reused from the R2_MEDIA_* vars when set.",
|
|
533
|
-
"# EXE_BACKUP_KEY
|
|
544
|
+
"# EXE_BACKUP_KEY is REQUIRED for off-box upload — backup.sh refuses to push an",
|
|
545
|
+
"# unencrypted archive to R2/S3. A generated .env gets a random one; set yours with",
|
|
546
|
+
"# EXE_BACKUP_KEY=$(openssl rand -base64 32)",
|
|
547
|
+
"EXE_BACKUP_KEY=CHANGEME_EXE_BACKUP_KEY",
|
|
534
548
|
"BACKUP_RETENTION_DAYS=7",
|
|
535
549
|
"# Override the DB container name if your topology differs (default: exe-db).",
|
|
536
550
|
"# BACKUP_DB_CONTAINER=exe-db",
|
|
@@ -35,6 +35,20 @@ check_http() {
|
|
|
35
35
|
fi
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
# --- Data-access boundary ---
|
|
39
|
+
# This script NEVER reads customer rows. It runs as a root-owned host health
|
|
40
|
+
# check on the customer's own VPS and is restricted, deliberately, to two kinds
|
|
41
|
+
# of query against the stack Postgres container:
|
|
42
|
+
# 1. liveness — `SELECT 1`, which touches no relation at all;
|
|
43
|
+
# 2. catalog — pg_class / pg_namespace metadata about whether the wiki's
|
|
44
|
+
# tables EXIST in the schema the container is pointed at.
|
|
45
|
+
# It previously ran `count(*)` over <schema>.users and <schema>.workspace_documents.
|
|
46
|
+
# That is a scan of customer tables by a host cron, outside the data-access
|
|
47
|
+
# boundary the repo requires for customer data, and it was flagged as such in
|
|
48
|
+
# review of #922. Catalog metadata answers the question the check actually asks
|
|
49
|
+
# ("is the wiki pointed at a schema that has its tables?") without reading a
|
|
50
|
+
# single row. Everything that escapes these functions is a fixed status token
|
|
51
|
+
# (ok / fail:<reason> / skip) written to a root-owned JSONL.
|
|
38
52
|
check_postgres() {
|
|
39
53
|
local result
|
|
40
54
|
result=$(docker exec exe-db psql -U "${POSTGRES_USER:-exe}" -d "${POSTGRES_DB:-exedb}" \
|
|
@@ -99,8 +113,12 @@ NOW=$(ts)
|
|
|
99
113
|
# and no error anywhere — silent-empty, which is the whole failure mode. Checking it
|
|
100
114
|
# here bounds how long that can go unnoticed to one cron interval.
|
|
101
115
|
# Reads the LIVE container env, not .env: a compose override can rewrite DATABASE_URL.
|
|
116
|
+
# Data-access note: CATALOG METADATA ONLY — no customer row is read (see the
|
|
117
|
+
# boundary note above check_postgres). This cannot be done through the wiki's
|
|
118
|
+
# own HTTP API — the wiki serves 200 on an empty schema, which IS the failure
|
|
119
|
+
# mode being detected.
|
|
102
120
|
check_wiki_schema() {
|
|
103
|
-
local url schema
|
|
121
|
+
local url schema rows
|
|
104
122
|
url=$(docker inspect exe-wiki --format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null \
|
|
105
123
|
| sed -n 's/^DATABASE_URL=//p')
|
|
106
124
|
[[ -z "$url" ]] && { echo "skip"; return; }
|
|
@@ -108,16 +126,47 @@ check_wiki_schema() {
|
|
|
108
126
|
[[ -z "$schema" ]] && schema="public"
|
|
109
127
|
# Only ever interpolate a bare identifier into SQL.
|
|
110
128
|
[[ ! "$schema" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] && { echo "fail:bad-schema-name"; return; }
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
129
|
+
# `to_regnamespace`/`to_regclass` return NULL instead of erroring on a name
|
|
130
|
+
# that does not exist, so a wrong schema comes back as a clean "missing"
|
|
131
|
+
# rather than as an aborted query indistinguishable from "psql is down".
|
|
132
|
+
# Identifiers are lowercased first: unquoted SQL folds case, but the string
|
|
133
|
+
# form fed to to_regnamespace does not, so `?schema=Wiki` would otherwise
|
|
134
|
+
# look missing when it is fine.
|
|
135
|
+
schema=$(printf '%s' "$schema" | tr '[:upper:]' '[:lower:]')
|
|
136
|
+
rows=$(docker exec exe-db psql -U "${POSTGRES_USER:-exe}" -d "${POSTGRES_DB:-exedb}" -tA -F' ' -c \
|
|
137
|
+
"SELECT t.tbl,
|
|
138
|
+
CASE WHEN c.oid IS NULL THEN 'missing' ELSE 'present' END,
|
|
139
|
+
COALESCE(c.reltuples::bigint, -1)
|
|
140
|
+
FROM (VALUES ('users'), ('workspace_documents')) AS t(tbl)
|
|
141
|
+
LEFT JOIN pg_class c
|
|
142
|
+
ON c.relname = t.tbl
|
|
143
|
+
AND c.relkind IN ('r', 'p')
|
|
144
|
+
AND c.relnamespace = to_regnamespace('${schema}')
|
|
145
|
+
ORDER BY t.tbl" 2>/dev/null)
|
|
146
|
+
# Two rows are always returned when the query ran at all; anything else means
|
|
147
|
+
# the container or psql is unavailable, which check_postgres already reports.
|
|
148
|
+
[[ "$(printf '%s\n' "$rows" | grep -c .)" -ne 2 ]] && { echo "skip"; return; }
|
|
149
|
+
|
|
150
|
+
local missing est max_est=-1
|
|
151
|
+
missing=$(printf '%s\n' "$rows" | awk '$2 == "missing" { print $1 }' | paste -sd, -)
|
|
152
|
+
if [[ -n "$missing" ]]; then
|
|
153
|
+
# The wrong-schema failure mode: the container is pointed somewhere its
|
|
154
|
+
# tables were never created. This is the signal bug 2a8d3003 is about.
|
|
155
|
+
echo "fail:missing-tables-$schema($missing)"
|
|
156
|
+
return
|
|
157
|
+
fi
|
|
158
|
+
while read -r _ _ est; do
|
|
159
|
+
[[ "${est:-0}" -gt "$max_est" ]] && max_est="$est"
|
|
160
|
+
done < <(printf '%s\n' "$rows")
|
|
161
|
+
if [[ "$max_est" -gt 0 ]]; then
|
|
118
162
|
echo "ok:$schema"
|
|
119
163
|
else
|
|
120
|
-
|
|
164
|
+
# reltuples is a PLANNER ESTIMATE, and it is 0 or -1 on a table that has
|
|
165
|
+
# simply never been ANALYZEd — which is exactly the state of a freshly
|
|
166
|
+
# restored or freshly migrated wiki that DOES have rows. Reporting "empty"
|
|
167
|
+
# here would fire the critical DO-NOT-RESTORE alert below on a healthy
|
|
168
|
+
# host, so an unknown estimate is reported as unknown, never as a failure.
|
|
169
|
+
echo "unknown:$schema-stats-stale"
|
|
121
170
|
fi
|
|
122
171
|
}
|
|
123
172
|
|
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-02T17:30:10.063Z",
|
|
4
|
+
"manifest_sha256": "2ac28000eca0fef6a9f2dd65b7297c98868732c8a9cbcd1ff178fc30e2119ccb",
|
|
5
|
+
"package_version": "0.9.378"
|
|
6
6
|
}
|
package/dist/bin/cli.js
CHANGED
|
@@ -709,14 +709,14 @@ ${JSON.stringify(stormAlert, null, 2)}`);
|
|
|
709
709
|
const { runStackUpdateCli } = await import("./stack-update.js");
|
|
710
710
|
await runStackUpdateCli(args.slice(1));
|
|
711
711
|
} else if (args[0] === "release") {
|
|
712
|
-
const { parseReleaseArgs, runStackRelease } = await import("../stack-release-
|
|
712
|
+
const { parseReleaseArgs, runStackRelease } = await import("../stack-release-JFB3NJLO.js");
|
|
713
713
|
const releaseFlags = parseReleaseArgs(args.slice(1));
|
|
714
714
|
await runStackRelease(releaseFlags);
|
|
715
715
|
} else if (args[0] === "stack-promote") {
|
|
716
|
-
const { runStackPromoteCli } = await import("../stack-promote-
|
|
716
|
+
const { runStackPromoteCli } = await import("../stack-promote-Y4JXWM6R.js");
|
|
717
717
|
await runStackPromoteCli(args.slice(1));
|
|
718
718
|
} else if (args[0] === "stack-generate-manifest") {
|
|
719
|
-
const { runStackGenerateManifestCli } = await import("../stack-generate-manifest-
|
|
719
|
+
const { runStackGenerateManifestCli } = await import("../stack-generate-manifest-BR3GZESE.js");
|
|
720
720
|
process.exit(await runStackGenerateManifestCli(args.slice(1)));
|
|
721
721
|
} else if (args[0] === "repo-truth") {
|
|
722
722
|
const { runRepoTruthCli } = await import("./exe-repo-truth.js");
|
package/dist/bin/stack-update.js
CHANGED
|
@@ -27,8 +27,8 @@ import {
|
|
|
27
27
|
serviceSelectionPath,
|
|
28
28
|
verifyManifestImagesAvailable,
|
|
29
29
|
warnUnmanagedManifestServices
|
|
30
|
-
} from "../chunk-
|
|
31
|
-
import "../chunk-
|
|
30
|
+
} from "../chunk-5IUXUD3X.js";
|
|
31
|
+
import "../chunk-EC5H53YD.js";
|
|
32
32
|
import {
|
|
33
33
|
runVerifyStack
|
|
34
34
|
} from "../chunk-PTY2N23C.js";
|
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
import {
|
|
44
44
|
logResult,
|
|
45
45
|
runHealthGate
|
|
46
|
-
} from "../chunk-
|
|
46
|
+
} from "../chunk-U3GM5MYE.js";
|
|
47
47
|
import "../chunk-L25OAFOU.js";
|
|
48
48
|
import "../chunk-W5MCOZTW.js";
|
|
49
49
|
import "../chunk-FTZET5YX.js";
|
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-02T17:30:09.805Z",
|
|
4
4
|
"files": [
|
|
5
5
|
{
|
|
6
6
|
"path": "active-agent-BOBBUJ74.js",
|
|
@@ -179,7 +179,7 @@
|
|
|
179
179
|
},
|
|
180
180
|
{
|
|
181
181
|
"path": "bin/cli.js",
|
|
182
|
-
"sha256": "
|
|
182
|
+
"sha256": "ff0ebe61eb90170c55e5e3b19df328a051f73a6a4bd40233e168b3e9e9a22ac1",
|
|
183
183
|
"bytes": 65011
|
|
184
184
|
},
|
|
185
185
|
{
|
|
@@ -524,7 +524,7 @@
|
|
|
524
524
|
},
|
|
525
525
|
{
|
|
526
526
|
"path": "bin/registry-proxy.js",
|
|
527
|
-
"sha256": "
|
|
527
|
+
"sha256": "2d38e5b49eb5749a62453f23c9d84fb3b0fb7f2bce3922a7eafefe295a020648",
|
|
528
528
|
"bytes": 1885
|
|
529
529
|
},
|
|
530
530
|
{
|
|
@@ -544,7 +544,7 @@
|
|
|
544
544
|
},
|
|
545
545
|
{
|
|
546
546
|
"path": "bin/stack-update.js",
|
|
547
|
-
"sha256": "
|
|
547
|
+
"sha256": "e0ff5ef9a72ab3afb5e26e9b52cbe4bdb14649d40a9b892a484de419b55ab1b0",
|
|
548
548
|
"bytes": 57247
|
|
549
549
|
},
|
|
550
550
|
{
|
|
@@ -564,7 +564,7 @@
|
|
|
564
564
|
},
|
|
565
565
|
{
|
|
566
566
|
"path": "bin/vps-health-gate.js",
|
|
567
|
-
"sha256": "
|
|
567
|
+
"sha256": "c5489e1f08d2f20572bd17a3ef584abc4472cb387514f992c044f9d5c47037af",
|
|
568
568
|
"bytes": 333
|
|
569
569
|
},
|
|
570
570
|
{
|
|
@@ -647,6 +647,11 @@
|
|
|
647
647
|
"sha256": "0aae97bac703b5f5102ff180a9b279efb0f0e6b208cb4292f83d353ff47924e7",
|
|
648
648
|
"bytes": 3642
|
|
649
649
|
},
|
|
650
|
+
{
|
|
651
|
+
"path": "chunk-3M3N7D7O.js",
|
|
652
|
+
"sha256": "da17bad4260b6af38836556ef7b2698b9ec8c66ec3633c70558c8bd8c8d17f53",
|
|
653
|
+
"bytes": 3250
|
|
654
|
+
},
|
|
650
655
|
{
|
|
651
656
|
"path": "chunk-3OEVDGIY.js",
|
|
652
657
|
"sha256": "ed3690ce4d418043eb922810ed6b67ed626119ff677ef351def6e0bf1e6016d7",
|
|
@@ -747,6 +752,11 @@
|
|
|
747
752
|
"sha256": "19a9706b415bb25b177a37e1192d9df7d2cfc60cf3c0d997fc5d87c5001e59b1",
|
|
748
753
|
"bytes": 6076
|
|
749
754
|
},
|
|
755
|
+
{
|
|
756
|
+
"path": "chunk-5IUXUD3X.js",
|
|
757
|
+
"sha256": "9d553f894e977b53724f71de8a6d9f0a8aec2f48a0700bbb19d245bd60cd660a",
|
|
758
|
+
"bytes": 186260
|
|
759
|
+
},
|
|
750
760
|
{
|
|
751
761
|
"path": "chunk-5MOYSU5A.js",
|
|
752
762
|
"sha256": "c2e2367cffa3e57666f0307f2374f6ca8cbf2f07ba45b7361d680e8271be90ec",
|
|
@@ -762,11 +772,6 @@
|
|
|
762
772
|
"sha256": "80bfcc6daf45ce066dc277b635bdeba44e42387f0c3b4cfee22287bac913612a",
|
|
763
773
|
"bytes": 20507
|
|
764
774
|
},
|
|
765
|
-
{
|
|
766
|
-
"path": "chunk-5QKLWJ4Z.js",
|
|
767
|
-
"sha256": "5ee3b527880c81a19faf07ab497b6c58260a795b5fb753313015ff6029e049cf",
|
|
768
|
-
"bytes": 3250
|
|
769
|
-
},
|
|
770
775
|
{
|
|
771
776
|
"path": "chunk-5UBWBXLF.js",
|
|
772
777
|
"sha256": "a36075b55ed774c96edb71e5ed68e0aad3c2cc91c82807d374ab3762081ab601",
|
|
@@ -867,6 +872,11 @@
|
|
|
867
872
|
"sha256": "9ffc6029453a6cecb1164230dbe11515f25d93db4e7d7568a2a9b363ff6689f2",
|
|
868
873
|
"bytes": 12110
|
|
869
874
|
},
|
|
875
|
+
{
|
|
876
|
+
"path": "chunk-A3P3CLE6.js",
|
|
877
|
+
"sha256": "24bc185c48cdc2736374cf83a594eeecd488987e19f539bbfc2fde38985b2146",
|
|
878
|
+
"bytes": 30665
|
|
879
|
+
},
|
|
870
880
|
{
|
|
871
881
|
"path": "chunk-A7JYF4RG.js",
|
|
872
882
|
"sha256": "3517b5704e13c31d180df1840da5a4ddf3b55cbad396daa963e783bb9c8f6846",
|
|
@@ -1047,6 +1057,11 @@
|
|
|
1047
1057
|
"sha256": "0391139d25845faff65e341cf4edf95bb48a2976fbc2a99f35dd47f661cfdb1f",
|
|
1048
1058
|
"bytes": 21845
|
|
1049
1059
|
},
|
|
1060
|
+
{
|
|
1061
|
+
"path": "chunk-EC5H53YD.js",
|
|
1062
|
+
"sha256": "31542497d924523e81c2fe63027b0a008d756ce0682592f0de592649a5188302",
|
|
1063
|
+
"bytes": 3928
|
|
1064
|
+
},
|
|
1050
1065
|
{
|
|
1051
1066
|
"path": "chunk-ECGTESAP.js",
|
|
1052
1067
|
"sha256": "80fa0c3b882a1c739530913a08ab4c46e89c76c665ef45084be4baa8fe187283",
|
|
@@ -1087,11 +1102,6 @@
|
|
|
1087
1102
|
"sha256": "50cfb3eeca20703519c19f97c16bca1578e6d41bf068adca7d62ea37e0cab4be",
|
|
1088
1103
|
"bytes": 3242
|
|
1089
1104
|
},
|
|
1090
|
-
{
|
|
1091
|
-
"path": "chunk-FCMQVEVL.js",
|
|
1092
|
-
"sha256": "8b74587c379962e637612221d6677b16ea88405bc7c22f0a0a8c63229b4fcf7e",
|
|
1093
|
-
"bytes": 30665
|
|
1094
|
-
},
|
|
1095
1105
|
{
|
|
1096
1106
|
"path": "chunk-FNEZ4BWT.js",
|
|
1097
1107
|
"sha256": "03b95f888cd6e804bdfe00e94a9ed83aac34a59e3bf28cd1c3995b5e273a3532",
|
|
@@ -1142,11 +1152,6 @@
|
|
|
1142
1152
|
"sha256": "6d3b5ff73e66f76bcdcad9cc898bd85904e6ffc3c27bb340055eae1a4d28fe81",
|
|
1143
1153
|
"bytes": 1155
|
|
1144
1154
|
},
|
|
1145
|
-
{
|
|
1146
|
-
"path": "chunk-GHR4ZPJ7.js",
|
|
1147
|
-
"sha256": "6f9dc5236c64d9ff74cd8247cc7667d9dcd01c4a5062f2b84a6289b5c589685a",
|
|
1148
|
-
"bytes": 2490
|
|
1149
|
-
},
|
|
1150
1155
|
{
|
|
1151
1156
|
"path": "chunk-GJV3WDWM.js",
|
|
1152
1157
|
"sha256": "59017c5a02fd07899282dfaaad148e33de6fa003a9b6bdae78b60fdd9d3acc9f",
|
|
@@ -1307,11 +1312,6 @@
|
|
|
1307
1312
|
"sha256": "533a919e763433ed5e882f31ee225e3a75ae8a643d2936a17767722a6f52fe7d",
|
|
1308
1313
|
"bytes": 3708
|
|
1309
1314
|
},
|
|
1310
|
-
{
|
|
1311
|
-
"path": "chunk-JRMHGO5B.js",
|
|
1312
|
-
"sha256": "2322ed24b906fe73faa32d00dfb0dc171094e6611e446b95289c804105426258",
|
|
1313
|
-
"bytes": 7587
|
|
1314
|
-
},
|
|
1315
1315
|
{
|
|
1316
1316
|
"path": "chunk-JSTTPYL3.js",
|
|
1317
1317
|
"sha256": "acc037274d4e9a5a5705b3f24c44a891dd55f37e428173d29f2d9f582809a24e",
|
|
@@ -1772,16 +1772,16 @@
|
|
|
1772
1772
|
"sha256": "c044678bdb81f58be175ce56eec162b1f678b19509eafa8893e3beac7def6f72",
|
|
1773
1773
|
"bytes": 2633
|
|
1774
1774
|
},
|
|
1775
|
-
{
|
|
1776
|
-
"path": "chunk-TT5BBVLX.js",
|
|
1777
|
-
"sha256": "5d609d7dc2f4979fb6e8c6326eee56ff9d9c8cf60b92bc252df0c1356b2955e2",
|
|
1778
|
-
"bytes": 186260
|
|
1779
|
-
},
|
|
1780
1775
|
{
|
|
1781
1776
|
"path": "chunk-TWMWHDOY.js",
|
|
1782
1777
|
"sha256": "d1a4b120060e1bda1c09919ff863fbb281c71f017d89b31f47e8df07587814c0",
|
|
1783
1778
|
"bytes": 2488
|
|
1784
1779
|
},
|
|
1780
|
+
{
|
|
1781
|
+
"path": "chunk-U3GM5MYE.js",
|
|
1782
|
+
"sha256": "7760a15dba0485ca567c96c9c1e0c3bf59c9bbdc9223869336a7481fd3a25019",
|
|
1783
|
+
"bytes": 7587
|
|
1784
|
+
},
|
|
1785
1785
|
{
|
|
1786
1786
|
"path": "chunk-UBMZ3YQU.js",
|
|
1787
1787
|
"sha256": "340c9342ed76ef63e08ecb48573e8b76ad912f640876b0d50152839f747b6078",
|
|
@@ -2464,7 +2464,7 @@
|
|
|
2464
2464
|
},
|
|
2465
2465
|
{
|
|
2466
2466
|
"path": "hooks/manifest.json",
|
|
2467
|
-
"sha256": "
|
|
2467
|
+
"sha256": "935211c99bdd95f9dd65e1a3126e66b8366d3e3e2e4c021dd0e299d98f397e1f",
|
|
2468
2468
|
"bytes": 1841
|
|
2469
2469
|
},
|
|
2470
2470
|
{
|
|
@@ -2754,7 +2754,7 @@
|
|
|
2754
2754
|
},
|
|
2755
2755
|
{
|
|
2756
2756
|
"path": "lib/registry-proxy.js",
|
|
2757
|
-
"sha256": "
|
|
2757
|
+
"sha256": "bde5270fe62007134099d85e5c7ae1e8500c6c844204bcf8662ed946fe3b56d9",
|
|
2758
2758
|
"bytes": 527
|
|
2759
2759
|
},
|
|
2760
2760
|
{
|
|
@@ -3188,9 +3188,9 @@
|
|
|
3188
3188
|
"bytes": 433
|
|
3189
3189
|
},
|
|
3190
3190
|
{
|
|
3191
|
-
"path": "resolve-image-labels-
|
|
3192
|
-
"sha256": "
|
|
3193
|
-
"bytes":
|
|
3191
|
+
"path": "resolve-image-labels-N27XXTUG.js",
|
|
3192
|
+
"sha256": "d3f89a38f106f08cb7f736894a6629e6ce1bf2ae8bcb03f0bf9b9fcf2269bcc5",
|
|
3193
|
+
"bytes": 333
|
|
3194
3194
|
},
|
|
3195
3195
|
{
|
|
3196
3196
|
"path": "restore-proof-HR4TXI3X.js",
|
|
@@ -3293,23 +3293,23 @@
|
|
|
3293
3293
|
"bytes": 111
|
|
3294
3294
|
},
|
|
3295
3295
|
{
|
|
3296
|
-
"path": "stack-generate-manifest-
|
|
3297
|
-
"sha256": "
|
|
3296
|
+
"path": "stack-generate-manifest-BR3GZESE.js",
|
|
3297
|
+
"sha256": "65b10366eb91c3f157ef01682b81f22c8ea0ec80c0436aef31661b8a86b29047",
|
|
3298
3298
|
"bytes": 42703
|
|
3299
3299
|
},
|
|
3300
3300
|
{
|
|
3301
|
-
"path": "stack-promote-
|
|
3302
|
-
"sha256": "
|
|
3301
|
+
"path": "stack-promote-Y4JXWM6R.js",
|
|
3302
|
+
"sha256": "4e4f5cae4da628657e7045b26a68895885f232fb2e0a43890592227766caa06e",
|
|
3303
3303
|
"bytes": 7395
|
|
3304
3304
|
},
|
|
3305
3305
|
{
|
|
3306
|
-
"path": "stack-release-
|
|
3307
|
-
"sha256": "
|
|
3306
|
+
"path": "stack-release-JFB3NJLO.js",
|
|
3307
|
+
"sha256": "fcc7c556b206f0da831d25a5f15184c656097dd532528488d5ce92e6dc79e1ec",
|
|
3308
3308
|
"bytes": 57491
|
|
3309
3309
|
},
|
|
3310
3310
|
{
|
|
3311
|
-
"path": "stack-update-
|
|
3312
|
-
"sha256": "
|
|
3311
|
+
"path": "stack-update-SWRZLQVQ.js",
|
|
3312
|
+
"sha256": "09bb94cf4e2a6cc99d7c7d112d0de93573cefbe3e1e2faba42af820f0f910f08",
|
|
3313
3313
|
"bytes": 6185
|
|
3314
3314
|
},
|
|
3315
3315
|
{
|
|
@@ -136,7 +136,7 @@ async function assertManifestSignature(manifest, publicKeyPem) {
|
|
|
136
136
|
"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"
|
|
137
137
|
);
|
|
138
138
|
}
|
|
139
|
-
const { verifyStackManifestSignature } = await import("./stack-update-
|
|
139
|
+
const { verifyStackManifestSignature } = await import("./stack-update-SWRZLQVQ.js");
|
|
140
140
|
try {
|
|
141
141
|
verifyStackManifestSignature(manifest, publicKeyPem);
|
|
142
142
|
} catch (err) {
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
// scripts/lib/resolve-image-labels.mjs
|
|
2
2
|
import { spawnSync } from "child_process";
|
|
3
3
|
var defaultRunner = (cmd, args) => spawnSync(cmd, args, { encoding: "utf8", timeout: 6e4 });
|
|
4
|
+
var ImageLabelResolutionError = class extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "ImageLabelResolutionError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var INDEX_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
11
|
+
"application/vnd.oci.image.index.v1+json",
|
|
12
|
+
"application/vnd.docker.distribution.manifest.list.v2+json"
|
|
13
|
+
]);
|
|
4
14
|
function repositoryOf(ref) {
|
|
5
15
|
if (typeof ref !== "string") return ref;
|
|
6
16
|
const at = ref.indexOf("@");
|
|
@@ -24,13 +34,39 @@ function amd64ChildDigest(rawJson) {
|
|
|
24
34
|
const digest = pick?.digest;
|
|
25
35
|
return typeof digest === "string" && digest.length > 0 ? digest : void 0;
|
|
26
36
|
}
|
|
27
|
-
function resolvePlatformRef(imageRef, { runner } = {}) {
|
|
37
|
+
function resolvePlatformRef(imageRef, { runner, failClosed = false } = {}) {
|
|
28
38
|
const run = runner ?? defaultRunner;
|
|
29
39
|
const res = run("docker", ["buildx", "imagetools", "inspect", imageRef, "--raw"]);
|
|
30
|
-
if (!res || res.status !== 0)
|
|
40
|
+
if (!res || res.status !== 0) {
|
|
41
|
+
if (failClosed) {
|
|
42
|
+
throw new ImageLabelResolutionError(
|
|
43
|
+
`registry manifest fetch failed for ${imageRef} (docker buildx imagetools inspect --raw exited ${res ? res.status : "spawn-failed"}) \u2014 cannot resolve to a platform manifest; refusing to read labels at index level (bug 8415a222)`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return imageRef;
|
|
47
|
+
}
|
|
31
48
|
const raw = (res.stdout ?? "").toString();
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
let doc;
|
|
50
|
+
try {
|
|
51
|
+
doc = JSON.parse(raw);
|
|
52
|
+
} catch {
|
|
53
|
+
if (failClosed) {
|
|
54
|
+
throw new ImageLabelResolutionError(
|
|
55
|
+
`registry returned an unparseable manifest for ${imageRef} \u2014 cannot determine index-vs-manifest; refusing to guess (bug 8415a222)`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return imageRef;
|
|
59
|
+
}
|
|
60
|
+
const childDigest = amd64ChildDigest(doc);
|
|
61
|
+
if (!childDigest) {
|
|
62
|
+
const isIndex = Boolean(doc && Array.isArray(doc.manifests)) || typeof doc?.mediaType === "string" && INDEX_MEDIA_TYPES.has(doc.mediaType);
|
|
63
|
+
if (isIndex && failClosed) {
|
|
64
|
+
throw new ImageLabelResolutionError(
|
|
65
|
+
`${imageRef} is a multi-arch index with no linux/amd64 child manifest \u2014 no platform manifest exists whose config carries the labels (bug 8415a222)`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return imageRef;
|
|
69
|
+
}
|
|
34
70
|
return `${repositoryOf(imageRef)}@${childDigest}`;
|
|
35
71
|
}
|
|
36
72
|
function readLabelOff(platformRef, label, { runner } = {}) {
|
|
@@ -51,8 +87,8 @@ function readLabelOff(platformRef, label, { runner } = {}) {
|
|
|
51
87
|
}
|
|
52
88
|
return void 0;
|
|
53
89
|
}
|
|
54
|
-
function resolveImageLabels(imageRef, labels, { runner } = {}) {
|
|
55
|
-
const platformRef = resolvePlatformRef(imageRef, { runner });
|
|
90
|
+
function resolveImageLabels(imageRef, labels, { runner, failClosed = false } = {}) {
|
|
91
|
+
const platformRef = resolvePlatformRef(imageRef, { runner, failClosed });
|
|
56
92
|
const out = {};
|
|
57
93
|
for (const label of labels) out[label] = readLabelOff(platformRef, label, { runner });
|
|
58
94
|
return out;
|
|
@@ -62,6 +98,7 @@ function readImageLabel(imageRef, label, opts = {}) {
|
|
|
62
98
|
}
|
|
63
99
|
|
|
64
100
|
export {
|
|
101
|
+
ImageLabelResolutionError,
|
|
65
102
|
repositoryOf,
|
|
66
103
|
amd64ChildDigest,
|
|
67
104
|
resolvePlatformRef,
|
|
@@ -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-SWRZLQVQ.js");
|
|
216
216
|
const paths = defaultStackPaths();
|
|
217
217
|
await rollbackStackUpdate({
|
|
218
218
|
manifestRef: paths.manifestRef,
|
package/dist/hooks/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"generatedAt": "2026-09-02T17:30:09.713Z",
|
|
4
4
|
"hashes": {
|
|
5
5
|
"bug-report-worker.js": "8191d9e9ad67127e44969bb4671d21f3ac603dcc8dd978960f1e50b215eac475",
|
|
6
6
|
"codex-stop-task-finalizer.js": "1c7ebddd870afd357eeab64172a88163ea02957eff527a5e13c5dd24e2f1a678",
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ImageLabelResolutionError,
|
|
2
3
|
amd64ChildDigest,
|
|
3
4
|
readImageLabel,
|
|
4
5
|
repositoryOf,
|
|
5
6
|
resolveImageLabels,
|
|
6
7
|
resolvePlatformRef
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-EC5H53YD.js";
|
|
8
9
|
import "./chunk-MLKGABMK.js";
|
|
9
10
|
export {
|
|
11
|
+
ImageLabelResolutionError,
|
|
10
12
|
amd64ChildDigest,
|
|
11
13
|
readImageLabel,
|
|
12
14
|
repositoryOf,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
writeSignedStackManifest
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-3M3N7D7O.js";
|
|
5
|
+
import "./chunk-5IUXUD3X.js";
|
|
6
|
+
import "./chunk-EC5H53YD.js";
|
|
7
7
|
import "./chunk-C5HUMAA7.js";
|
|
8
8
|
import "./chunk-L25OAFOU.js";
|
|
9
9
|
import "./chunk-Y2HD2YB7.js";
|
|
@@ -710,7 +710,7 @@ async function resolveImageDigest(ref) {
|
|
|
710
710
|
return match?.[1];
|
|
711
711
|
}
|
|
712
712
|
async function readProvenanceLabels(ref) {
|
|
713
|
-
const { resolveImageLabels } = await import("./resolve-image-labels-
|
|
713
|
+
const { resolveImageLabels } = await import("./resolve-image-labels-N27XXTUG.js");
|
|
714
714
|
return resolveImageLabels(ref, [
|
|
715
715
|
"org.opencontainers.image.version",
|
|
716
716
|
"org.opencontainers.image.revision",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
writeSignedStackManifest
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-3M3N7D7O.js";
|
|
5
|
+
import "./chunk-5IUXUD3X.js";
|
|
6
|
+
import "./chunk-EC5H53YD.js";
|
|
7
7
|
import "./chunk-C5HUMAA7.js";
|
|
8
8
|
import "./chunk-L25OAFOU.js";
|
|
9
9
|
import "./chunk-Y2HD2YB7.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
writeSignedStackManifest
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-3M3N7D7O.js";
|
|
5
|
+
import "./chunk-5IUXUD3X.js";
|
|
6
|
+
import "./chunk-EC5H53YD.js";
|
|
7
7
|
import {
|
|
8
8
|
assertPreflightGate,
|
|
9
9
|
runPreflightOnManifest
|
|
@@ -105,8 +105,8 @@ import {
|
|
|
105
105
|
verifyStackManifestSignature,
|
|
106
106
|
waitForHttpOk,
|
|
107
107
|
warnUnmanagedManifestServices
|
|
108
|
-
} from "./chunk-
|
|
109
|
-
import "./chunk-
|
|
108
|
+
} from "./chunk-5IUXUD3X.js";
|
|
109
|
+
import "./chunk-EC5H53YD.js";
|
|
110
110
|
import "./chunk-C5HUMAA7.js";
|
|
111
111
|
import "./chunk-L25OAFOU.js";
|
|
112
112
|
import "./chunk-Y2HD2YB7.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askexenow/exe-os",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.378",
|
|
4
4
|
"description": "AI employee operating system — 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,21 +1,17 @@
|
|
|
1
1
|
{
|
|
2
|
-
"current": "0.9.
|
|
2
|
+
"current": "0.9.378",
|
|
3
3
|
"notes": {
|
|
4
|
-
"0.9.
|
|
5
|
-
"version": "0.9.
|
|
4
|
+
"0.9.378": {
|
|
5
|
+
"version": "0.9.378",
|
|
6
6
|
"date": "2026-09-02",
|
|
7
7
|
"features": [],
|
|
8
8
|
"fixes": [
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"sso-edge erp vhost preserves Host + websocket upgrade headers (#1024)",
|
|
12
|
-
"refuse to push a tag stamped with a non-AskExe identity (#1021)"
|
|
9
|
+
"wire backup + uptime crons into deploy.yml, with the data boundary closed (re-land, bug 6c1893f0) (#1028)",
|
|
10
|
+
"G2 label resolution dereferences the OCI index fail-closed, never reads labels at index level (bug 8415a222) (#1023)"
|
|
13
11
|
],
|
|
14
12
|
"security": [],
|
|
15
13
|
"other": [
|
|
16
|
-
"bump to v0.9.
|
|
17
|
-
"cross-arch builds use the docker driver (Rosetta), not bundled qemu (#1030)",
|
|
18
|
-
"migrate release/x64 lanes off build-my onto the local ci-linux VM (#1026)"
|
|
14
|
+
"bump to v0.9.378 (#1032)"
|
|
19
15
|
],
|
|
20
16
|
"migration_notes": []
|
|
21
17
|
},
|
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.378",
|
|
8
|
+
"image": "ghcr.io/askexe/exe-os:v0.9.378",
|
|
9
9
|
"imageEnv": "EXE_OS_IMAGE_TAG",
|
|
10
10
|
"imageDigestTodo": "TODO(bug 32d6e02f), NARROWED by bug 81ca8903. The top-level `image` is tag-only BY CONSTRUCTION and that is correct: it names the tag the release run is about to build, whose digest does not exist until the push (see the 'Cross-check built digest against the manifest pin' step in .github/workflows/release-stack-image.yml). Demanding a digest here is unsatisfiable, which is why this TODO kept surviving. The one genuinely resolvable case it also covered — components.erp, built in a DIFFERENT repo and therefore already sitting in the registry — is now resolved and digest-pinned below. What remains open is ONLY components.dashboard, and not for the reason this TODO claimed: ghcr.io/askexe/exe-dashboard:v0.9.372 DOES NOT EXIST. .github/workflows/release-dashboard-image.yml has produced exactly one run in its lifetime (id 32405074431, 2026-08-20, tag v0.9.374) and that run FAILED, so the dashboard release lane has never emitted a single v* tag — the only refs in ghcr.io/askexe/exe-dashboard are the floating :main and :sha-<sha> dev refs from dashboard-image.yml. The dashboard pin is therefore a PHANTOM, not an unresolved digest, and it is deliberately left tag-only here rather than silently repointed at a :main digest, because choosing which dashboard bytes constitute a release is a release decision, not a lint fix. FAIL-CLOSED still holds and is enforced where it bites: scripts/publish-stack-release.mjs verifyServicePin() refuses to publish any tag-only app image into the customer manifest.",
|
|
11
11
|
"components": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"note": "Bug 81ca8903: was `ghcr.io/askexe/exe-erp:v0.9.312` — a tag that has never existed. ERP is versioned on its own 0.x line; v0.9.312 is an EXE-OS version number that bumpVersions() stamped onto this component because rebaseImageTag() rebased every tag-only component image to the exe-os release version regardless of which repo built it. That rebase is now scoped (same commit) so it can no longer forge tags for independently-versioned components. Repinned to the real, current ERP release v0.3.4, digest resolved live from ghcr.io/askexe/exe-erp on 2026-09-01. Digest-pinning is also what makes this entry self-defending: rebaseImageTag() returns any `@sha256:`-bearing ref untouched. Prior intent for the record (bug 790794e8) was v0.2.0-final8, the first ERP image shipping the realtime/socketio entrypoint (exe-erp PR #15); final3/final7 crash-looped exe-erp-websocket on a missing socketio.js. v0.3.4 supersedes it."
|
|
17
17
|
},
|
|
18
18
|
"dashboard": {
|
|
19
|
-
"image": "ghcr.io/askexe/exe-dashboard:v0.9.
|
|
19
|
+
"image": "ghcr.io/askexe/exe-dashboard:v0.9.378",
|
|
20
20
|
"env": "DASHBOARD_IMAGE_TAG",
|
|
21
21
|
"composeService": "exe-dashboard",
|
|
22
22
|
"versionLockstep": true,
|
|
@@ -110,5 +110,5 @@
|
|
|
110
110
|
},
|
|
111
111
|
"deploymentScope": "customer",
|
|
112
112
|
"highGhostStack": "0.9.9",
|
|
113
|
-
"sourceVersion": "0.9.
|
|
113
|
+
"sourceVersion": "0.9.378"
|
|
114
114
|
}
|