@bongos/core 1.19.132 → 1.19.133

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/.bongos-core.json CHANGED
@@ -2,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.132",
6
- "core_contract": "1.19.132",
7
- "source_commit": "450f890ee24a473026548ea2cf82cf69ac1d6d9c",
5
+ "core_version": "1.19.133",
6
+ "core_contract": "1.19.133",
7
+ "source_commit": "ab4e04d82f1699e1781d780bf327f3da0150ddb9",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-15T03:38:15.933Z",
9
+ "built_at": "2026-08-15T03:42:15.630Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 303,
13
13
  "agent_docs_stubbed": 21,
14
- "functional_verbatim": 1248,
14
+ "functional_verbatim": 1249,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1572,
20
- "tree_sha256": "d0e21da3aa85bb9e774ef6c921a2cc4be514b8dab592b7a1b3fa8bb07e8be485",
19
+ "file_count": 1573,
20
+ "tree_sha256": "90710d328ac6f0b388a9c427df22ad7fb6fc7087ea9b5baebc708525e61aac50",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -4172,12 +4172,12 @@
4172
4172
  {
4173
4173
  "path": "package-lock.json",
4174
4174
  "mode": "0000644",
4175
- "sha256": "26a0143ba944f1edf0c163992ecd6f447fa911d16d7b8161578c7b1bc6725a48"
4175
+ "sha256": "fd95462763cc12a1a060875e7ba46a46b8e3a396cbda3ad743ea926357ddfda9"
4176
4176
  },
4177
4177
  {
4178
4178
  "path": "package.json",
4179
4179
  "mode": "0000644",
4180
- "sha256": "763b31a597012a53b4f7b6c0c0ce9a30e10ba13206468820d16dcc5d04d972ec"
4180
+ "sha256": "73ce7f53c0bab1ed2c0ab9dc6f6b8e64fadc8d01b6baa9afcfa2b10710f196a5"
4181
4181
  },
4182
4182
  {
4183
4183
  "path": "public-docs/index.html",
@@ -4797,7 +4797,7 @@
4797
4797
  {
4798
4798
  "path": "scripts/gds/fitness.js",
4799
4799
  "mode": "0000644",
4800
- "sha256": "2c557a65fa79495e5d84b6902e5fbedac7cd8faa22540b595d41f33505b066cb"
4800
+ "sha256": "c1bc8ad1db37b53b80f2f8daab0bd86c874178546695d6b6c3f65af15317de32"
4801
4801
  },
4802
4802
  {
4803
4803
  "path": "scripts/gds/gate-review.js",
@@ -4927,7 +4927,7 @@
4927
4927
  {
4928
4928
  "path": "scripts/gds/lib.sh",
4929
4929
  "mode": "0000644",
4930
- "sha256": "34b711dd649fe21ee149ea8b7f9f347cd36d18900846f12a6e9810b7c30c6a96"
4930
+ "sha256": "009d436a22a274f417ee84d462036be84d76226e45ced4ed7614e4460e25d9d5"
4931
4931
  },
4932
4932
  {
4933
4933
  "path": "scripts/gds/linkify-refs.js",
@@ -5077,7 +5077,7 @@
5077
5077
  {
5078
5078
  "path": "scripts/gds/portability-smoke-memory.sh",
5079
5079
  "mode": "0000644",
5080
- "sha256": "dc0595220f2cb0a6620e2f26af4b43f67117b381bbc31d5711de8934eb65a245"
5080
+ "sha256": "bec9e23a2b8092c6a2b1bb5edaed48900485e7296a3d7e683cce215ad7ec7ab9"
5081
5081
  },
5082
5082
  {
5083
5083
  "path": "scripts/gds/preflight.js",
@@ -5327,12 +5327,12 @@
5327
5327
  {
5328
5328
  "path": "scripts/gds/smoke-batch-claim.sh",
5329
5329
  "mode": "0000644",
5330
- "sha256": "c908a7aaf1497d5463d1fa315c1b25a21da84c96fd418d62c96f37a6bc69a7d8"
5330
+ "sha256": "96a38493a51e3b21d6059bda25bff0bc36123117f7a683d7fc83e4ccf779b899"
5331
5331
  },
5332
5332
  {
5333
5333
  "path": "scripts/gds/smoke-batch-release.sh",
5334
5334
  "mode": "0000644",
5335
- "sha256": "223464bf0c953cab73092acf7928ce6142d7d677ab1858be4ae080db95d9ddf6"
5335
+ "sha256": "1b0276b21420cd4fdbb9a77f0b34270e90109cd62579469e71607c286821904b"
5336
5336
  },
5337
5337
  {
5338
5338
  "path": "scripts/gds/smoke-box.sh",
@@ -5342,12 +5342,12 @@
5342
5342
  {
5343
5343
  "path": "scripts/gds/smoke-claim-consistency.sh",
5344
5344
  "mode": "0000644",
5345
- "sha256": "b4f9c864f116f5899d3e4513805d7e68cf4699346ae52f7bb87a07305185d1f8"
5345
+ "sha256": "55aa25f1d7143edec57833dcd582f05d1a1b7261bd73d51044fc48dff3532ee1"
5346
5346
  },
5347
5347
  {
5348
5348
  "path": "scripts/gds/smoke-dependencies.sh",
5349
5349
  "mode": "0000644",
5350
- "sha256": "7698dc3d1c1a1c9908f734e93d4880fda389011011e9a050df3154cc418e800d"
5350
+ "sha256": "7e0e240869ad03ed8448748c5918a232dd8c0b80a084834b44ab453d7a98c1e9"
5351
5351
  },
5352
5352
  {
5353
5353
  "path": "scripts/gds/smoke-gds.js",
@@ -5357,7 +5357,7 @@
5357
5357
  {
5358
5358
  "path": "scripts/gds/smoke-write.sh",
5359
5359
  "mode": "0000644",
5360
- "sha256": "29b47bc1f8664051d4fb0271adbe92e0247b7b7bb9bc254312e45390426cb29a"
5360
+ "sha256": "9c3a382def8c074a37bad66d7e3deef41c7f2bb9406f02f107be0e9a7dc7bfb3"
5361
5361
  },
5362
5362
  {
5363
5363
  "path": "scripts/gds/start.js",
@@ -5762,7 +5762,7 @@
5762
5762
  {
5763
5763
  "path": "src/module-api.js",
5764
5764
  "mode": "0000644",
5765
- "sha256": "9754ad61248f7a9ccf1b4361bf7de1895318872a4994f74f53668151c60f8aeb"
5765
+ "sha256": "6d426bc93757b3261642a8969c3ee1e87ab9c2c60e2cdb74539db67759501bbd"
5766
5766
  },
5767
5767
  {
5768
5768
  "path": "src/module-loader/catalog.js",
@@ -6944,6 +6944,11 @@
6944
6944
  "mode": "0000644",
6945
6945
  "sha256": "89a9d21ee1c5b00ee09de2d888873fa13c1b2b6d15ab05a6bfeb7e7a5d016a03"
6946
6946
  },
6947
+ {
6948
+ "path": "tests/lib_sh_resolution.mjs",
6949
+ "mode": "0000644",
6950
+ "sha256": "353fa78c96ac85e13d4fce417e32b061d9b95e8077baa5f2348fb576f18b72f6"
6951
+ },
6947
6952
  {
6948
6953
  "path": "tests/lifecycle-card.mjs",
6949
6954
  "mode": "0000644",
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.132",
3
+ "version": "1.19.133",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.132",
9
+ "version": "1.19.133",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.132",
3
+ "version": "1.19.133",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -974,7 +974,20 @@ const IDENTITY_SCAN_EXTRA_FILES = ['scripts/gds/architect-audit.js'];
974
974
  // records of what those tasks said when they were filed. Rewriting them would
975
975
  // falsify the record; deleting the files would destroy it. They are inert
976
976
  // (idempotent on source_ref) and referenced only by generated manifests.
977
+ // * the four *.sh entries — surfaced when `.sh` joined the extension set (task
978
+ // 1002567). Each is a HOST-PINNED OPS script, not portable code: they name the
979
+ // live box's actual Postgres database/role and its actual /etc secrets dir, and
980
+ // every one of those literals is already only the DEFAULT behind an env override
981
+ // (GDS_BACKUP_DB, PROD_DB/STAGING_DB, PG_SOCKET). Same rationale as box.js's real
982
+ // DNS zones — an infrastructure identifier, not an instance URL, and the systemd
983
+ // units on that box invoke these by path. Re-pointing an instance's backups is
984
+ // deployment work, not a branding read. Their COUNT is still frozen here, so a
985
+ // NEW literal in any of them fails exactly like one in an unlisted file.
977
986
  const IDENTITY_SCAN_ALLOWED = {
987
+ 'scripts/gds/audit-replay.sh': 1,
988
+ 'scripts/gds/db-backup-nightly.sh': 1,
989
+ 'scripts/gds/db-backup-offsite.sh': 2,
990
+ 'scripts/gds/seed-staging.sh': 4,
978
991
  'scripts/gds/backfill-sessions.js': 2,
979
992
  'scripts/gds/box.js': 3,
980
993
  'scripts/gds/publish-manifest.js': 2,
@@ -989,7 +1002,13 @@ const IDENTITY_SCAN_ALLOWED = {
989
1002
  'scripts/gds/seed-provisioning-tasks.js': 2,
990
1003
  'scripts/gds/version-close.js': 1,
991
1004
  };
992
- const IDENTITY_SCAN_EXTS = new Set(['.js', '.html', '.css']);
1005
+ // `.sh` joined at task 1002567: the extension set was `.js/.html/.css`, so NO shell
1006
+ // script had ever been scanned — which is how scripts/gds/lib.sh kept a hardcoded
1007
+ // founder host as its API-base default (and a hardcoded ~/.config/otb token path)
1008
+ // through the entire task 1002201 sweep, leaving all five bash smokes dead on any
1009
+ // instance with a different configDir. The guard could not see the file class the
1010
+ // bug lived in.
1011
+ const IDENTITY_SCAN_EXTS = new Set(['.js', '.html', '.css', '.sh']);
993
1012
  // Path-prefix carve-outs (see the header note). Kept SMALL and documented.
994
1013
  const IDENTITY_SCAN_EXCLUDE = ['modules/game/'];
995
1014
  // Directories with no authored source to gate.
@@ -1001,6 +1020,11 @@ const IDENTITY_SCAN_SKIP_DIRS = new Set(['node_modules', '.git', 'generated', 'i
1001
1020
  function stripCommentsForIdentity(text, ext) {
1002
1021
  let t = String(text == null ? '' : text);
1003
1022
  if (ext === '.html') return t.replace(/<!--[\s\S]*?-->/g, '');
1023
+ // Shell has no block comments, so it returns BEFORE the /* */ strip below (a
1024
+ // `"$TMP"/*` glob would otherwise eat real code). Only FULL-LINE `#` comments
1025
+ // (and the shebang) are stripped: cutting from an inline `#` would truncate
1026
+ // `${v#p}` / `$#` lines and could hide a literal sitting after them.
1027
+ if (ext === '.sh') return t.split(/\r?\n/).map((l) => (/^\s*#/.test(l) ? '' : l)).join('\n');
1004
1028
  t = t.replace(/\/\*[\s\S]*?\*\//g, ''); // block comments (js + css)
1005
1029
  // Split on /\r?\n/, NOT '\n' (task 1002428). On a CRLF checkout the old split
1006
1030
  // left a trailing \r on every line, and since `.` does not match \r the `.*$`
@@ -1102,7 +1126,7 @@ function checkKernelPromptsNoHardcodedIdentity() {
1102
1126
  hardFail: violations.length > 0,
1103
1127
  violations,
1104
1128
  warnings: [],
1105
- note: `${scanned} portable source file(s) scanned (src/bongos/** + modules/** + scripts/gds/** + kernel-prompt CLIs, minus the instance-1 product + generated dirs) — none may hardcode the host brand; identity comes from branding()/window.__BRANDING__ (ADR 0062 §3/§5). ${ratcheted} scripts/gds file(s) hold documented permanent carve-outs (real DNS zones, real doc filenames, operator runbook text, historical seeded prose) capped at a count they may not exceed — task 1002428.`,
1129
+ note: `${scanned} portable source file(s) scanned (.js/.html/.css/.sh under src/bongos/** + modules/** + scripts/gds/** + kernel-prompt CLIs, minus the instance-1 product + generated dirs) — none may hardcode the host brand; identity comes from branding()/window.__BRANDING__ (ADR 0062 §3/§5). ${ratcheted} scripts/gds file(s) hold documented permanent carve-outs (real DNS zones, real doc filenames, operator runbook text, historical seeded prose) capped at a count they may not exceed — task 1002428.`,
1106
1130
  };
1107
1131
  }
1108
1132
 
@@ -2,9 +2,20 @@
2
2
  # scripts/gds/lib.sh — shared helpers for the GDS shell smoke scripts.
3
3
  # SOURCED, never executed: source "$(dirname "$0")/lib.sh"
4
4
  #
5
- # Holds ONLY the pieces that were byte-identical across smoke-batch-claim.sh,
6
- # smoke-claim-consistency.sh, and smoke-write.sh: the GDS API base-URL default
7
- # (a literal that had drifted) and the CLI session-token resolution.
5
+ # Holds ONLY the pieces that were byte-identical across the sourcing smokes: the
6
+ # GDS API base URL and the CLI session-token resolution.
7
+ #
8
+ # BOTH now delegate to `node`, the one runtime this repo guarantees (task 686),
9
+ # instead of re-implementing config resolution in bash. Re-implementing it is
10
+ # exactly what made every sourcing script instance-blind (task 1002567): the
11
+ # token path was a baked-in ~/.config/otb/ and the API base a baked-in founder
12
+ # host, so on any instance with a different configDir the scripts either died
13
+ # looking for a session that was never there, or — worse, had they found one —
14
+ # would have exercised somebody else's instance. src/instance-config.js and
15
+ # cli-lib.js are the single source of truth for both values; shelling out to
16
+ # them means this file cannot drift from them again. It also drops the python3
17
+ # dependency these two helpers carried, which is the other reason they could
18
+ # not run on a Windows builder's machine.
8
19
  #
9
20
  # Deliberately NOT here:
10
21
  # - the per-script curl `call()` wrapper — the two contracts differ in
@@ -12,32 +23,51 @@
12
23
  # non-zero so its caller can read CODE/PAYLOAD; smoke-write/-consistency echo
13
24
  # the payload and `return 1` on a non-2xx so `set -e` aborts). Merging them
14
25
  # would break one or the other, so each script keeps its own.
15
- # - smoke-dependencies.sh / portability-smoke-memory.sh, whose token + base-URL
16
- # handling intentionally differs (macOS-only path / env-first token / no PMS
17
- # fallback / skip-on-missing) — left as-is to preserve that drift.
18
26
 
19
- # The GDS API host. One home for the default that had drifted across the smoke
20
- # scripts; still overridable via GDS_API_BASE / (legacy) PMS_API_BASE.
27
+ # lib.sh's own directory, captured at SOURCE time (BASH_SOURCE, not $0 — $0 is
28
+ # the sourcing script). Captured eagerly because a caller may cd afterwards:
29
+ # portability-smoke-memory.sh cd's to the repo root right after sourcing.
30
+ GDS_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
31
+
32
+ # Run a node snippet with scripts/gds as its cwd, so relative require()s resolve
33
+ # no matter where the caller is — and so no absolute path crosses the bash→node
34
+ # boundary (Git Bash would hand node an MSYS path it cannot resolve).
35
+ gds_node() {
36
+ if ! command -v node >/dev/null 2>&1; then
37
+ echo "FAIL: node is not on PATH — the GDS smokes resolve their config through it (task 686)." >&2
38
+ return 2
39
+ fi
40
+ ( cd "$GDS_LIB_DIR" && node -e "$1" )
41
+ }
42
+
43
+ # The GDS API host. GDS_API_BASE / (legacy) PMS_API_BASE win, then whatever
44
+ # instance-config resolves for this checkout + session (env → session api_base →
45
+ # this instance's own origin) — the same order every node CLI uses.
21
46
  gds_api_base() {
22
- printf '%s' "${GDS_API_BASE:-${PMS_API_BASE:-https://amazonprimea.com}}"
47
+ local base="${GDS_API_BASE:-${PMS_API_BASE:-}}"
48
+ if [[ -n "$base" ]]; then printf '%s' "$base"; return 0; fi
49
+ gds_node "process.stdout.write(require('../../src/instance-config').resolveApiBase())"
50
+ }
51
+
52
+ # The instance this session actually belongs to, IGNORING the env overrides
53
+ # (session api_base, else this instance's own origin). For guards that must
54
+ # check an overridden base AGAINST the real instance before sending it a token.
55
+ gds_instance_base() {
56
+ gds_node "process.stdout.write(require('../../src/instance-config').resolveApiBase({ env: {} }))"
23
57
  }
24
58
 
25
- # Resolve the CLI session token: prefer ~/.config/otb/gds-session.json, fall back
26
- # to the legacy pms-session.json. Echoes the token on success. On a missing file
27
- # or empty token it prints a FAIL line to stderr and returns 2 — callers run
59
+ # Resolve the CLI session token through cli-lib's own reader, so a smoke looks in
60
+ # exactly the paths every /builder-* command does (the configured instance config
61
+ # dir, then the legacy dir, then the legacy pms-session.json filename). Echoes the
62
+ # token on success. With no session it prints a FAIL line to stderr and returns 2
63
+ # — callers run
28
64
  # TOKEN="$(gds_resolve_token)" || exit $?
29
65
  # so the failure propagates out of the command substitution.
30
66
  gds_resolve_token() {
31
- local sf="${HOME}/.config/otb/gds-session.json"
32
- [[ -f "$sf" ]] || sf="${HOME}/.config/otb/pms-session.json"
33
- if [[ ! -f "$sf" ]]; then
34
- echo "FAIL: no session at $sf. Run setup first." >&2
35
- return 2
36
- fi
37
67
  local tok
38
- tok=$(python3 -c "import json; print(json.load(open('$sf'))['token'])")
68
+ tok=$(gds_node "const s = require('./cli-lib.js').readSessionToken(); if (s && s.token) process.stdout.write(s.token);" 2>/dev/null) || tok=""
39
69
  if [[ -z "$tok" ]]; then
40
- echo "FAIL: no token in $sf" >&2
70
+ echo "FAIL: no GDS session token found. Run /builder-setup (or /builder-reauth) first." >&2
41
71
  return 2
42
72
  fi
43
73
  printf '%s' "$tok"
@@ -18,39 +18,67 @@
18
18
  #
19
19
  # Usage:
20
20
  # GDS_SMOKE_TOKEN=<token> bash scripts/gds/portability-smoke-memory.sh
21
- # # token falls back to ~/.config/otb/gds-session.json for local runs
22
- # # target with GDS_API_BASE (default prod; CI should point at staging)
21
+ # # token falls back to this instance's CLI session for local runs (lib.sh)
22
+ # # target with GDS_API_BASE (default this instance; CI should point at staging)
23
23
 
24
24
  set -uo pipefail
25
+ # Sourced BEFORE the cd — lib.sh captures its own directory at source time, and
26
+ # both helpers below resolve the config through node rather than a baked-in path
27
+ # or host, which is what made this script instance-blind (task 1002567).
28
+ # shellcheck source=scripts/gds/lib.sh
29
+ source "$(dirname "$0")/lib.sh"
25
30
  cd "$(dirname "$0")/../.." # repo root (consistent with portability-smoke-art.sh)
26
31
 
27
- BASE="${GDS_API_BASE:-https://amazonprimea.com}"
32
+ BASE="$(gds_api_base)"
28
33
  PASS=0; FAIL=0
29
34
  ok() { echo "PASS $1"; PASS=$((PASS+1)); }
30
35
  bad() { echo "FAIL $1"; FAIL=$((FAIL+1)); }
31
36
 
32
37
  # --- resolve token -----------------------------------------------------------
33
38
  TOKEN="${GDS_SMOKE_TOKEN:-}"; TOKEN_SRC="env"
34
- if [ -z "$TOKEN" ] && [ -f "$HOME/.config/otb/gds-session.json" ]; then
35
- TOKEN=$(python3 -c "import json,os;print(json.load(open(os.path.expanduser('~/.config/otb/gds-session.json'))).get('token',''))" 2>/dev/null || true)
36
- TOKEN_SRC="file"
39
+ if [ -z "$TOKEN" ]; then
40
+ # Same session lookup every /builder-* command does; stays a SKIP (not a FAIL)
41
+ # when there is no session, which is this script's contract.
42
+ TOKEN=$(gds_resolve_token 2>/dev/null) || TOKEN=""
43
+ [ -n "$TOKEN" ] && TOKEN_SRC="file"
37
44
  fi
38
45
  if [ -z "$TOKEN" ]; then
39
46
  echo "SKIP: no GDS token (set GDS_SMOKE_TOKEN or run /builder-setup). Memory portability not certified this run."
40
47
  exit 0
41
48
  fi
42
49
 
43
- # --- defense-in-depth: never send the token to a non-amazonprimea host -------
50
+ # --- defense-in-depth: never send the token to another instance's host -------
44
51
  # We hold a real GDS_SMOKE_TOKEN and are about to send it (Authorization: Bearer)
45
52
  # to whatever GDS_API_BASE resolves to. A fat-fingered or compromised
46
53
  # STAGING_API_BASE must not be able to exfiltrate it, so refuse any host outside
47
- # the amazonprimea zone (localhost allowed for local dev). (idea 127 nit 3.)
48
- BASE_HOST=$(printf '%s' "$BASE" | sed -E 's#^[a-zA-Z][a-zA-Z0-9+.-]*://##; s#[:/?].*$##')
54
+ # THIS instance's own zone (localhost allowed for local dev). (idea 127 nit 3.)
55
+ # The zone is resolved from the session + branding pack, never a baked-in domain
56
+ # — a literal here pinned the guard to one instance and refused every other one
57
+ # outright, the same instance-blindness as the base/token defaults (task 1002567).
58
+ host_of() { printf '%s' "$1" | sed -E 's#^[a-zA-Z][a-zA-Z0-9+.-]*://##; s#[:/?].*$##'; }
59
+ BASE_HOST=$(host_of "$BASE")
60
+ ALLOWED_HOST=$(host_of "$(gds_instance_base)")
61
+ if [ -z "$ALLOWED_HOST" ]; then
62
+ echo "FAIL could not resolve this instance's own host (is node on PATH?)."
63
+ echo " Refusing to send GDS_SMOKE_TOKEN to an unverified target."
64
+ exit 1
65
+ fi
66
+ # ALLOWED_HOST lands unquoted-side in the case patterns below, where glob
67
+ # metacharacters (* ? [ ) would silently WIDEN the allowlist. It comes from
68
+ # operator config, not attacker input, but a malformed branding pack must fail
69
+ # closed, not open.
70
+ case "$ALLOWED_HOST" in
71
+ *[!A-Za-z0-9.-]*)
72
+ echo "FAIL resolved instance host '$ALLOWED_HOST' contains characters invalid in a hostname."
73
+ echo " Refusing to build a token allowlist from it (check branding.domains / session api_base)."
74
+ exit 1
75
+ ;;
76
+ esac
49
77
  case "$BASE_HOST" in
50
- amazonprimea.com|*.amazonprimea.com|localhost|127.0.0.1) ;;
78
+ "$ALLOWED_HOST"|*."$ALLOWED_HOST"|localhost|127.0.0.1) ;;
51
79
  *)
52
80
  echo "FAIL GDS_API_BASE host '$BASE_HOST' is not an allowed target for the smoke token"
53
- echo " (allow: amazonprimea.com, *.amazonprimea.com, localhost). Refusing to send GDS_SMOKE_TOKEN."
81
+ echo " (allow: $ALLOWED_HOST, *.$ALLOWED_HOST, localhost). Refusing to send GDS_SMOKE_TOKEN."
54
82
  exit 1
55
83
  ;;
56
84
  esac
@@ -66,7 +94,8 @@ if [ -z "$REMOTE" ]; then bad "could not resolve origin remote for fresh-clone t
66
94
  ok "repo clones from origin (cloneable)"
67
95
  # --- 2. the fresh clone leaks NO secrets / NO builder memory ------------
68
96
  LEAKS=0
69
- for p in ".env.local" "spaces.env" ".config/otb/gds-session.json" "etc/amazonprimea"; do
97
+ CFG_DIR=$(gds_node "process.stdout.write(require('../../src/instance-config').configDirName())" 2>/dev/null) || CFG_DIR=""
98
+ for p in ".env.local" "spaces.env" ".config/${CFG_DIR:-otb}/gds-session.json" "etc/${CFG_DIR:-otb}"; do
70
99
  if [ -e "$TMP/clone/$p" ]; then echo " leak: $p present in clone"; LEAKS=$((LEAKS+1)); fi
71
100
  done
72
101
  # builder_memory is server-side only — no memory dir should ship in-repo
@@ -13,7 +13,8 @@
13
13
  # 'abandoned' so they don't linger.
14
14
  #
15
15
  # Usage: bash scripts/gds/smoke-batch-claim.sh
16
- # Env: GDS_API_BASE / PMS_API_BASE override host (default https://amazonprimea.com)
16
+ # Env: GDS_API_BASE / PMS_API_BASE override host (default: this instance's own,
17
+ # resolved from the session + branding pack — see lib.sh)
17
18
 
18
19
  set -euo pipefail
19
20
 
@@ -20,7 +20,8 @@
20
20
  # 'abandoned' so they don't linger.
21
21
  #
22
22
  # Usage: bash scripts/gds/smoke-batch-release.sh
23
- # Env: GDS_API_BASE / PMS_API_BASE override host (default https://amazonprimea.com)
23
+ # Env: GDS_API_BASE / PMS_API_BASE override host (default: this instance's own,
24
+ # resolved from the session + branding pack — see lib.sh)
24
25
 
25
26
  set -euo pipefail
26
27
 
@@ -30,7 +30,8 @@
30
30
  # Usage:
31
31
  # bash scripts/gds/smoke-claim-consistency.sh
32
32
  # Optional env:
33
- # GDS_API_BASE / PMS_API_BASE override the API host (default: https://amazonprimea.com)
33
+ # GDS_API_BASE / PMS_API_BASE override the API host (default: this instance's own,
34
+ # resolved from the session + branding pack — see lib.sh)
34
35
 
35
36
  set -euo pipefail
36
37
 
@@ -14,20 +14,20 @@
14
14
  # 9. Claim A — succeeds
15
15
  # 10. Release A + clean up
16
16
  #
17
- # Run from any environment where ~/.config/otb/{gds,pms}-session.json is valid.
18
- # Requires curl + jq.
17
+ # Run from any environment with a valid CLI session (/builder-setup).
18
+ # Requires curl.
19
19
  set -euo pipefail
20
20
 
21
- TOKEN=$(python3 -c "
22
- import json, os, sys
23
- for p in ['/Users/'+os.environ['USER']+'/.config/otb/gds-session.json',
24
- '/Users/'+os.environ['USER']+'/.config/otb/pms-session.json']:
25
- if os.path.exists(p):
26
- print(json.load(open(p))['token']); sys.exit(0)
27
- sys.exit(1)
28
- ")
21
+ # Token + API base both come from lib.sh, which resolves them through
22
+ # instance-config the way every /builder-* command does. The literals that used
23
+ # to live here were doubly broken: a macOS-only /Users/$USER path and a baked-in
24
+ # founder host, so this smoke could not run on any other OS or instance
25
+ # (task 1002567).
26
+ # shellcheck source=scripts/gds/lib.sh
27
+ source "$(dirname "$0")/lib.sh"
29
28
 
30
- BASE="${GDS_BASE:-https://amazonprimea.com}"
29
+ TOKEN="$(gds_resolve_token)" || exit $?
30
+ BASE="${GDS_BASE:-$(gds_api_base)}"
31
31
  SUFFIX="$(date +%s)"
32
32
 
33
33
  echo "=== 1. Create task B (depends-on target, status=ready) ==="
@@ -18,7 +18,8 @@
18
18
  # Usage:
19
19
  # bash scripts/gds/smoke-write.sh
20
20
  # Optional env:
21
- # GDS_API_BASE / PMS_API_BASE override the API host (default: https://amazonprimea.com)
21
+ # GDS_API_BASE / PMS_API_BASE override the API host (default: this instance's own,
22
+ # resolved from the session + branding pack — see lib.sh)
22
23
 
23
24
  set -euo pipefail
24
25
 
@@ -145,9 +146,17 @@ else
145
146
  fail=1
146
147
  fi
147
148
 
148
- # 4d. session_logs row exists — checked via DB (need ssh)
149
- if command -v ssh >/dev/null && ssh -o BatchMode=yes -o ConnectTimeout=3 lars@104.236.254.243 true 2>/dev/null; then
150
- SLOG_COUNT=$(ssh lars@104.236.254.243 "psql -d amazonprimea -tAc \"SELECT count(*) FROM session_logs WHERE claim_id = ${CLAIM_ID}\"")
149
+ # 4d. session_logs row exists — checked via DB (needs ssh to the instance's host).
150
+ # The ssh target + DB name come from the operator (GDS_SMOKE_DB_SSH /
151
+ # GDS_SMOKE_DB_NAME) with NO default, so this simply SKIPs unless someone opted
152
+ # in. It used to hardcode one instance's box IP, login and DB name, which made it
153
+ # dead weight on every other instance — the same blindness as the base/token
154
+ # defaults this script inherited from lib.sh (task 1002567).
155
+ DB_SSH="${GDS_SMOKE_DB_SSH:-}"
156
+ DB_NAME="${GDS_SMOKE_DB_NAME:-}"
157
+ if [[ -n "${DB_SSH}" && -n "${DB_NAME}" ]] && command -v ssh >/dev/null \
158
+ && ssh -o BatchMode=yes -o ConnectTimeout=3 "${DB_SSH}" true 2>/dev/null; then
159
+ SLOG_COUNT=$(ssh "${DB_SSH}" "psql -d ${DB_NAME} -tAc \"SELECT count(*) FROM session_logs WHERE claim_id = ${CLAIM_ID}\"")
151
160
  if [[ "${SLOG_COUNT}" == "1" ]]; then
152
161
  echo "PASS session_logs row exists (claim_id=${CLAIM_ID})"
153
162
  else
@@ -155,7 +164,7 @@ if command -v ssh >/dev/null && ssh -o BatchMode=yes -o ConnectTimeout=3 lars@10
155
164
  fail=1
156
165
  fi
157
166
  else
158
- echo "SKIP session_logs DB check (no SSH access from this host)"
167
+ echo "SKIP session_logs DB check (set GDS_SMOKE_DB_SSH + GDS_SMOKE_DB_NAME to enable)"
159
168
  fi
160
169
 
161
170
  echo
package/src/module-api.js CHANGED
@@ -691,8 +691,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
691
691
  // landed since 1.19.130 with no explicit bump. run 31861682626. (task 1002620)
692
692
  // 1.19.132 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
693
693
  // landed since 1.19.131 with no explicit bump. run 31862317758. (task 1002620)
694
+ // 1.19.133 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
695
+ // landed since 1.19.132 with no explicit bump. run 31862484011. (task 1002620)
694
696
  // ---------------------------------------------------------------------------
695
- const CORE_VERSION = '1.19.132'; // CI auto-patch carrier (ADR 0161); see changelog above
697
+ const CORE_VERSION = '1.19.133'; // CI auto-patch carrier (ADR 0161); see changelog above
696
698
 
697
699
  // A namespaced logger so a module's log lines are attributable + consistent.
698
700
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -0,0 +1,152 @@
1
+ // tests/lib_sh_resolution.mjs — the bash smokes resolve their token + API base
2
+ // from instance-config, never from a baked-in literal (task 1002567).
3
+ //
4
+ // THE BUG. scripts/gds/lib.sh — sourced by every bash smoke — resolved the CLI
5
+ // session token from a hardcoded `${HOME}/.config/otb/` and defaulted the API
6
+ // base to a hardcoded founder host. On any instance whose configDir is not
7
+ // `otb` (this one's is `cloudbongos`) every sourcing script died looking for a
8
+ // session that was never there; had one been found, the run would have
9
+ // exercised the WRONG instance. Nothing caught it because the host-identity
10
+ // fitness check only ever scanned .js/.html/.css — no .sh file had been looked
11
+ // at, so the guard was blind to the whole file class the bug lived in.
12
+ //
13
+ // Two layers here, matching the two halves of the fix:
14
+ // 1. the scripts themselves carry no literal and resolve through node;
15
+ // 2. the fitness scan can now SEE a .sh re-hardcode (and its shell
16
+ // comment-strip does not open a hole a literal could hide in).
17
+ //
18
+ // The live smokes are deliberately NOT executed: smoke-write.sh creates, claims
19
+ // and resolves a REAL task against production. `bash -n` + the sourced-helper
20
+ // checks below are the safe verification.
21
+ //
22
+ // Run: node --test tests/lib_sh_resolution.mjs
23
+
24
+ import assert from 'node:assert/strict';
25
+ import { test } from 'node:test';
26
+ import { createRequire } from 'node:module';
27
+ import { spawnSync } from 'node:child_process';
28
+ import fs from 'node:fs';
29
+ import path from 'node:path';
30
+ import { fileURLToPath } from 'node:url';
31
+
32
+ const require = createRequire(import.meta.url);
33
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
34
+ const GDS = path.join(ROOT, 'scripts', 'gds');
35
+ const fitness = require(path.join(GDS, 'fitness.js'));
36
+
37
+ // Every bash script that resolves a GDS token or API base.
38
+ const SMOKES = [
39
+ 'lib.sh',
40
+ 'smoke-write.sh',
41
+ 'smoke-batch-claim.sh',
42
+ 'smoke-batch-release.sh',
43
+ 'smoke-claim-consistency.sh',
44
+ 'smoke-dependencies.sh',
45
+ 'portability-smoke-memory.sh',
46
+ ];
47
+
48
+ const read = (f) => fs.readFileSync(path.join(GDS, f), 'utf8');
49
+ // CODE only. The rationale comments in these files legitimately name the old
50
+ // literals (that is what they are explaining), so they are stripped first —
51
+ // through fitness's own shell strip, so the test and the CI guard agree on what
52
+ // counts as code.
53
+ const code = (f) => fitness.stripCommentsForIdentity(read(f), '.sh');
54
+
55
+ // bash is present on Mac/Linux and on a Windows builder's Git Bash, but this
56
+ // suite must still pass where it is absent — the static assertions carry it.
57
+ function bash(args) {
58
+ const r = spawnSync('bash', args, { cwd: ROOT, encoding: 'utf8' });
59
+ return r.error && r.error.code === 'ENOENT' ? null : r;
60
+ }
61
+
62
+ // ---- 1. no script carries a baked-in host or config dir --------------------
63
+
64
+ test('no committed smoke script hardcodes a host-brand literal', () => {
65
+ for (const f of SMOKES) {
66
+ const hits = code(f).split('\n')
67
+ .map((l, i) => ({ l, n: i + 1 }))
68
+ .filter(({ l }) => /amazonprimea|off the boats/i.test(l));
69
+ assert.equal(hits.length, 0, `${f} still names a host brand: ${hits.map((h) => `${h.n}: ${h.l.trim()}`).join(' | ')}`);
70
+ }
71
+ });
72
+
73
+ test('no smoke script hardcodes the ~/.config/otb session path', () => {
74
+ for (const f of SMOKES) {
75
+ // `${CFG_DIR:-otb}` is the RESOLVED dir with a neutral fallback, not a
76
+ // hardcoded path — only a literal `/otb/` or `otb/gds-session` is the bug.
77
+ const hits = code(f).split('\n')
78
+ .map((l, i) => ({ l, n: i + 1 }))
79
+ .filter(({ l }) => /\.config\/otb\//.test(l) || /otb\/(gds|pms)-session/.test(l));
80
+ assert.equal(hits.length, 0, `${f} hardcodes the legacy config dir: ${hits.map((h) => `${h.n}: ${h.l.trim()}`).join(' | ')}`);
81
+ }
82
+ });
83
+
84
+ test('lib.sh resolves both values through node, not python3', () => {
85
+ const src = code('lib.sh');
86
+ assert.ok(!/python3/.test(src), 'lib.sh must not depend on python3 — node is the one guaranteed runtime (task 686)');
87
+ assert.match(src, /instance-config/, 'gds_api_base must resolve through src/instance-config.js');
88
+ assert.match(src, /cli-lib\.js/, 'gds_resolve_token must reuse cli-lib.js readSessionToken');
89
+ });
90
+
91
+ // ---- 2. the scripts still parse, and the helpers behave --------------------
92
+
93
+ test('every smoke script passes bash -n', (t) => {
94
+ const probe = bash(['-n', 'scripts/gds/lib.sh']);
95
+ if (!probe) return t.skip('bash not available on this machine');
96
+ for (const f of SMOKES) {
97
+ const r = bash(['-n', `scripts/gds/${f}`]);
98
+ assert.equal(r.status, 0, `bash -n scripts/gds/${f} failed:\n${r.stderr}`);
99
+ }
100
+ });
101
+
102
+ test('gds_api_base honors the GDS_API_BASE override', (t) => {
103
+ const r = bash(['-c', 'source scripts/gds/lib.sh; GDS_API_BASE=http://127.0.0.1:9 gds_api_base']);
104
+ if (!r) return t.skip('bash not available on this machine');
105
+ assert.equal(r.status, 0, r.stderr);
106
+ assert.equal(r.stdout, 'http://127.0.0.1:9');
107
+ });
108
+
109
+ test('gds_api_base falls back to the instance, never a foreign host', (t) => {
110
+ const r = bash(['-c', 'source scripts/gds/lib.sh; gds_api_base']);
111
+ if (!r) return t.skip('bash not available on this machine');
112
+ assert.equal(r.status, 0, r.stderr);
113
+ assert.match(r.stdout, /^https?:\/\/\S+$/, `expected a resolved origin, got "${r.stdout}"`);
114
+ assert.doesNotMatch(r.stdout, /amazonprimea/i, 'resolved base must come from branding/session, not the stale founder host');
115
+ // instance-config is the single source of truth — the same answer the node CLIs get.
116
+ const viaNode = spawnSync(process.execPath,
117
+ ['-e', "process.stdout.write(require('./src/instance-config').resolveApiBase())"],
118
+ { cwd: ROOT, encoding: 'utf8' });
119
+ assert.equal(r.stdout, viaNode.stdout);
120
+ });
121
+
122
+ // ---- 3. the guard can now SEE a .sh re-hardcode ----------------------------
123
+
124
+ test('the host-identity fitness scan covers .sh', () => {
125
+ const src = fs.readFileSync(path.join(GDS, 'fitness.js'), 'utf8');
126
+ assert.match(src, /IDENTITY_SCAN_EXTS = new Set\(\[[^\]]*'\.sh'/,
127
+ 'the scan extension set must include .sh, or no shell script is ever checked');
128
+ });
129
+
130
+ test('a re-hardcoded host in a .sh is caught, and a rationale comment is not', () => {
131
+ const sh = [
132
+ '#!/usr/bin/env bash',
133
+ '# rationale: this used to point at https://amazonprimea.com',
134
+ 'BASE="${GDS_API_BASE:-https://amazonprimea.com}"',
135
+ ].join('\n');
136
+ const found = fitness.scanPromptIdentity('scripts/gds/fake.sh', sh, '.sh');
137
+ assert.equal(found.length, 1, `expected exactly the code line to trip: ${JSON.stringify(found)}`);
138
+ assert.match(found[0], /fake\.sh:3/);
139
+ });
140
+
141
+ test('the .sh comment strip cannot swallow a literal after an inline #', () => {
142
+ // Cutting from ANY `#` would truncate `${v#p}` / `$#` lines and hide what
143
+ // follows them. Only full-line comments are stripped.
144
+ const sh = 'N=${VAR#prefix}; curl https://amazonprimea.com/healthz\n';
145
+ assert.equal(fitness.scanPromptIdentity('scripts/gds/fake.sh', sh, '.sh').length, 1);
146
+ assert.match(fitness.stripCommentsForIdentity(' # only a comment\ncode\n', '.sh'), /^\ncode/);
147
+ });
148
+
149
+ test('the whole host-identity check is green on this tree', () => {
150
+ const r = fitness.checkKernelPromptsNoHardcodedIdentity();
151
+ assert.equal(r.ok, true, `violations:\n${r.violations.join('\n')}`);
152
+ });