@bongos/core 1.19.132 → 1.19.134
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 +23 -18
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/fitness.js +26 -2
- package/scripts/gds/lib.sh +50 -20
- package/scripts/gds/portability-smoke-memory.sh +41 -12
- package/scripts/gds/smoke-batch-claim.sh +2 -1
- package/scripts/gds/smoke-batch-release.sh +2 -1
- package/scripts/gds/smoke-claim-consistency.sh +2 -1
- package/scripts/gds/smoke-dependencies.sh +11 -11
- package/scripts/gds/smoke-write.sh +14 -5
- package/src/module-api.js +5 -1
- package/tests/lib_sh_resolution.mjs +152 -0
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.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.134",
|
|
6
|
+
"core_contract": "1.19.134",
|
|
7
|
+
"source_commit": "7823db141a9cd6375e337f63779958b76c9b064b",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-15T03:
|
|
9
|
+
"built_at": "2026-08-15T03:48:20.281Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 303,
|
|
13
13
|
"agent_docs_stubbed": 21,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1249,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1573,
|
|
20
|
+
"tree_sha256": "545ba84996f88bbbb913baafcf7d5dd429dff68ba6178562f03a90efcdf2e807",
|
|
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": "
|
|
4175
|
+
"sha256": "97adf4b1513014f9dbcf8a3dfb9182e606b4b4f5e7a4da13dd4b4188e670789d"
|
|
4176
4176
|
},
|
|
4177
4177
|
{
|
|
4178
4178
|
"path": "package.json",
|
|
4179
4179
|
"mode": "0000644",
|
|
4180
|
-
"sha256": "
|
|
4180
|
+
"sha256": "f17e6fe30f33a4f990ab5be6afe7cc9067d0e1d2ab7eca51dcdac886e5bcfd1d"
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
5330
|
+
"sha256": "96a38493a51e3b21d6059bda25bff0bc36123117f7a683d7fc83e4ccf779b899"
|
|
5331
5331
|
},
|
|
5332
5332
|
{
|
|
5333
5333
|
"path": "scripts/gds/smoke-batch-release.sh",
|
|
5334
5334
|
"mode": "0000644",
|
|
5335
|
-
"sha256": "
|
|
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": "
|
|
5345
|
+
"sha256": "55aa25f1d7143edec57833dcd582f05d1a1b7261bd73d51044fc48dff3532ee1"
|
|
5346
5346
|
},
|
|
5347
5347
|
{
|
|
5348
5348
|
"path": "scripts/gds/smoke-dependencies.sh",
|
|
5349
5349
|
"mode": "0000644",
|
|
5350
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
5765
|
+
"sha256": "681070293ecb18cd5bf028ce0c7367372d5212d0cb7c578e31d323b64b98b639"
|
|
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.
|
|
3
|
+
"version": "1.19.134",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.134",
|
|
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.
|
|
3
|
+
"version": "1.19.134",
|
|
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",
|
package/scripts/gds/fitness.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
package/scripts/gds/lib.sh
CHANGED
|
@@ -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
|
|
6
|
-
#
|
|
7
|
-
#
|
|
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
|
-
#
|
|
20
|
-
#
|
|
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
|
-
|
|
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
|
|
26
|
-
#
|
|
27
|
-
#
|
|
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=$(
|
|
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
|
|
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
|
|
22
|
-
# # target with GDS_API_BASE (default
|
|
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="$
|
|
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" ]
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
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
|
-
#
|
|
48
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
18
|
-
# Requires curl
|
|
17
|
+
# Run from any environment with a valid CLI session (/builder-setup).
|
|
18
|
+
# Requires curl.
|
|
19
19
|
set -euo pipefail
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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:
|
|
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 (
|
|
149
|
-
|
|
150
|
-
|
|
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 (
|
|
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,12 @@ 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)
|
|
696
|
+
// 1.19.134 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
697
|
+
// landed since 1.19.133 with no explicit bump. run 31862749526. (task 1002620)
|
|
694
698
|
// ---------------------------------------------------------------------------
|
|
695
|
-
const CORE_VERSION = '1.19.
|
|
699
|
+
const CORE_VERSION = '1.19.134'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
696
700
|
|
|
697
701
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
698
702
|
// 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
|
+
});
|