@bongos/core 1.19.736 → 1.19.738
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 +31 -36
- package/docs/module-api-changelog.md +4 -0
- package/docs/recipes/upgrading-the-core.md +3 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/go-live.js +18 -44
- package/scripts/gds/init.js +21 -29
- package/scripts/gds/provision-config.js +0 -8
- package/scripts/gds/provision-repo-auth.js +106 -0
- package/scripts/gds/provision-repo.js +34 -67
- package/scripts/gds/provision.js +3 -5
- package/scripts/gds/start.js +1 -1
- package/scripts/gds/update-channel.js +3 -3
- package/scripts/gds/upgrade.js +204 -41
- package/src/module-api.js +1 -1
- package/tests/executed_closure.mjs +8 -0
- package/tests/go_live.mjs +20 -19
- package/tests/init.mjs +11 -6
- package/tests/provision.mjs +33 -10
- package/tests/start_env_guard.mjs +8 -3
- package/tests/upgrade.mjs +290 -4
- package/scripts/gds/npmrc.js +0 -124
- package/tests/npmrc.mjs +0 -154
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.738",
|
|
6
|
+
"core_contract": "1.19.738",
|
|
7
|
+
"source_commit": "37a35be93e9879af0144594f30a8a894a84502f5",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-14T01:52:45.616Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 479,
|
|
13
13
|
"agent_docs_stubbed": 24,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2107,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2610,
|
|
20
|
+
"tree_sha256": "476bbdb5c1700b8ecf374a05e95a28bc6c61df721e5eb815c65439e08126feab",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2492,7 +2492,7 @@
|
|
|
2492
2492
|
{
|
|
2493
2493
|
"path": "docs/module-api-changelog.md",
|
|
2494
2494
|
"mode": "0000644",
|
|
2495
|
-
"sha256": "
|
|
2495
|
+
"sha256": "63fe92dfdaaacde0ba4dfb7882c97c6b875cd90d9b5f3dac0dacc404dbb1531f"
|
|
2496
2496
|
},
|
|
2497
2497
|
{
|
|
2498
2498
|
"path": "docs/modules-contract.md",
|
|
@@ -2752,7 +2752,7 @@
|
|
|
2752
2752
|
{
|
|
2753
2753
|
"path": "docs/recipes/upgrading-the-core.md",
|
|
2754
2754
|
"mode": "0000644",
|
|
2755
|
-
"sha256": "
|
|
2755
|
+
"sha256": "3691b017562bde760fbe3ff29a5efb80d886dd770f1187dc4673459871cec513"
|
|
2756
2756
|
},
|
|
2757
2757
|
{
|
|
2758
2758
|
"path": "docs/recipes/windows-builders.md",
|
|
@@ -7537,12 +7537,12 @@
|
|
|
7537
7537
|
{
|
|
7538
7538
|
"path": "package-lock.json",
|
|
7539
7539
|
"mode": "0000644",
|
|
7540
|
-
"sha256": "
|
|
7540
|
+
"sha256": "35ace7cf502387465f75cea83b1159bb82349d019555503f51769a264fd19c3e"
|
|
7541
7541
|
},
|
|
7542
7542
|
{
|
|
7543
7543
|
"path": "package.json",
|
|
7544
7544
|
"mode": "0000644",
|
|
7545
|
-
"sha256": "
|
|
7545
|
+
"sha256": "4fbaebd587863651e105d72ed373cef166d16eebd5f86a5d51395d98c33c5f60"
|
|
7546
7546
|
},
|
|
7547
7547
|
{
|
|
7548
7548
|
"path": "public-docs/index.html",
|
|
@@ -8122,7 +8122,7 @@
|
|
|
8122
8122
|
{
|
|
8123
8123
|
"path": "scripts/gds/go-live.js",
|
|
8124
8124
|
"mode": "0000644",
|
|
8125
|
-
"sha256": "
|
|
8125
|
+
"sha256": "f246947d973eec84ea174736bc87bf52b4716c939fc86bff4037b330f6790f9a"
|
|
8126
8126
|
},
|
|
8127
8127
|
{
|
|
8128
8128
|
"path": "scripts/gds/goal-names.js",
|
|
@@ -8157,7 +8157,7 @@
|
|
|
8157
8157
|
{
|
|
8158
8158
|
"path": "scripts/gds/init.js",
|
|
8159
8159
|
"mode": "0000644",
|
|
8160
|
-
"sha256": "
|
|
8160
|
+
"sha256": "3879084ce7246d6fdcf3a053006e86e7d533016b6dbb9b5aa160d65a3e9336f5"
|
|
8161
8161
|
},
|
|
8162
8162
|
{
|
|
8163
8163
|
"path": "scripts/gds/install-git-hooks.js",
|
|
@@ -8299,11 +8299,6 @@
|
|
|
8299
8299
|
"mode": "0000644",
|
|
8300
8300
|
"sha256": "d34cbccec1fa3053f960c1ac0950ce7e6b4b05f9d32a23304c4c3abc41c22ef7"
|
|
8301
8301
|
},
|
|
8302
|
-
{
|
|
8303
|
-
"path": "scripts/gds/npmrc.js",
|
|
8304
|
-
"mode": "0000644",
|
|
8305
|
-
"sha256": "1beffeed111e8528cfa0a629d5ef81c766023de5c6820a81659a390070868228"
|
|
8306
|
-
},
|
|
8307
8302
|
{
|
|
8308
8303
|
"path": "scripts/gds/oauth-secret.js",
|
|
8309
8304
|
"mode": "0000644",
|
|
@@ -8362,17 +8357,22 @@
|
|
|
8362
8357
|
{
|
|
8363
8358
|
"path": "scripts/gds/provision-config.js",
|
|
8364
8359
|
"mode": "0000644",
|
|
8365
|
-
"sha256": "
|
|
8360
|
+
"sha256": "48d13ecda786629148713e0bd89f0d020ee47de107fa5a2327d44989783907ac"
|
|
8366
8361
|
},
|
|
8367
8362
|
{
|
|
8368
8363
|
"path": "scripts/gds/provision-net.js",
|
|
8369
8364
|
"mode": "0000644",
|
|
8370
8365
|
"sha256": "502b1aeb571d463aea02ef4db3e848d23fdd8c865e7bfc37f66679c09cc203a9"
|
|
8371
8366
|
},
|
|
8367
|
+
{
|
|
8368
|
+
"path": "scripts/gds/provision-repo-auth.js",
|
|
8369
|
+
"mode": "0000644",
|
|
8370
|
+
"sha256": "b08032667c9f06495c750e4dd8d41fdc2f5aa587093cf7b4de8e7a46390f0724"
|
|
8371
|
+
},
|
|
8372
8372
|
{
|
|
8373
8373
|
"path": "scripts/gds/provision-repo.js",
|
|
8374
8374
|
"mode": "0000644",
|
|
8375
|
-
"sha256": "
|
|
8375
|
+
"sha256": "c5822f3b29f90998c831639df2b0925033f17758cc329609de507f5e6e1ad7c8"
|
|
8376
8376
|
},
|
|
8377
8377
|
{
|
|
8378
8378
|
"path": "scripts/gds/provision-units.js",
|
|
@@ -8382,7 +8382,7 @@
|
|
|
8382
8382
|
{
|
|
8383
8383
|
"path": "scripts/gds/provision.js",
|
|
8384
8384
|
"mode": "0000644",
|
|
8385
|
-
"sha256": "
|
|
8385
|
+
"sha256": "614aa63d02f3ae97f39e492cb0948a92b40364e25f4780d87546b64c01e5c69c"
|
|
8386
8386
|
},
|
|
8387
8387
|
{
|
|
8388
8388
|
"path": "scripts/gds/publish-credential-check.js",
|
|
@@ -8852,7 +8852,7 @@
|
|
|
8852
8852
|
{
|
|
8853
8853
|
"path": "scripts/gds/start.js",
|
|
8854
8854
|
"mode": "0000644",
|
|
8855
|
-
"sha256": "
|
|
8855
|
+
"sha256": "7e4b7cd97e9a414cf55e1118c902b4ab17239d3810fe26e5b90aa18d6a948432"
|
|
8856
8856
|
},
|
|
8857
8857
|
{
|
|
8858
8858
|
"path": "scripts/gds/status.js",
|
|
@@ -8897,12 +8897,12 @@
|
|
|
8897
8897
|
{
|
|
8898
8898
|
"path": "scripts/gds/update-channel.js",
|
|
8899
8899
|
"mode": "0000644",
|
|
8900
|
-
"sha256": "
|
|
8900
|
+
"sha256": "1aeeb6171244e1d42ee7189a3aa2ae5d83fd175bfe1ce89300d6ca7511819f0b"
|
|
8901
8901
|
},
|
|
8902
8902
|
{
|
|
8903
8903
|
"path": "scripts/gds/upgrade.js",
|
|
8904
8904
|
"mode": "0000644",
|
|
8905
|
-
"sha256": "
|
|
8905
|
+
"sha256": "7a82fb0cf919e14700ceba718e28da1324df2eab669a847348271c85b2f7bd66"
|
|
8906
8906
|
},
|
|
8907
8907
|
{
|
|
8908
8908
|
"path": "scripts/gds/validate-design.js",
|
|
@@ -9342,7 +9342,7 @@
|
|
|
9342
9342
|
{
|
|
9343
9343
|
"path": "src/module-api.js",
|
|
9344
9344
|
"mode": "0000644",
|
|
9345
|
-
"sha256": "
|
|
9345
|
+
"sha256": "4c5bdcc7f30c8ae47bdb1ecb596b692bd6405a227fc895fa5bfcd33cd8f92340"
|
|
9346
9346
|
},
|
|
9347
9347
|
{
|
|
9348
9348
|
"path": "src/module-loader/catalog.js",
|
|
@@ -10432,7 +10432,7 @@
|
|
|
10432
10432
|
{
|
|
10433
10433
|
"path": "tests/executed_closure.mjs",
|
|
10434
10434
|
"mode": "0000644",
|
|
10435
|
-
"sha256": "
|
|
10435
|
+
"sha256": "1e1a899e8838c7c84bdad9ea9d8533aca9173ed1e72f15e13c3616493e11900f"
|
|
10436
10436
|
},
|
|
10437
10437
|
{
|
|
10438
10438
|
"path": "tests/explore_visible_states.mjs",
|
|
@@ -10562,7 +10562,7 @@
|
|
|
10562
10562
|
{
|
|
10563
10563
|
"path": "tests/go_live.mjs",
|
|
10564
10564
|
"mode": "0000644",
|
|
10565
|
-
"sha256": "
|
|
10565
|
+
"sha256": "fb937d66f8efa9fdcfb58715785a0c9d94e52034d04175aec78310c70bd53a7f"
|
|
10566
10566
|
},
|
|
10567
10567
|
{
|
|
10568
10568
|
"path": "tests/goal_abuse_matrix.mjs",
|
|
@@ -11282,7 +11282,7 @@
|
|
|
11282
11282
|
{
|
|
11283
11283
|
"path": "tests/init.mjs",
|
|
11284
11284
|
"mode": "0000644",
|
|
11285
|
-
"sha256": "
|
|
11285
|
+
"sha256": "945aaac204e9e60772d3e7b361c97c099d236e6be6b4b794cce36ecb391d8a1a"
|
|
11286
11286
|
},
|
|
11287
11287
|
{
|
|
11288
11288
|
"path": "tests/instance_config.mjs",
|
|
@@ -11674,11 +11674,6 @@
|
|
|
11674
11674
|
"mode": "0000644",
|
|
11675
11675
|
"sha256": "c57c0211709ab041ee895bbb4ade9f4b146b43147570bf282e6d6a808560db76"
|
|
11676
11676
|
},
|
|
11677
|
-
{
|
|
11678
|
-
"path": "tests/npmrc.mjs",
|
|
11679
|
-
"mode": "0000644",
|
|
11680
|
-
"sha256": "1aaa68ee1a4c4a58563cef86aac44cfa0e046ad7376d3a2108bf6dd455580942"
|
|
11681
|
-
},
|
|
11682
11677
|
{
|
|
11683
11678
|
"path": "tests/oauth_secret.mjs",
|
|
11684
11679
|
"mode": "0000644",
|
|
@@ -11977,7 +11972,7 @@
|
|
|
11977
11972
|
{
|
|
11978
11973
|
"path": "tests/provision.mjs",
|
|
11979
11974
|
"mode": "0000644",
|
|
11980
|
-
"sha256": "
|
|
11975
|
+
"sha256": "25fbd2adccf79e921c642f8f57b082f5f19fa01cd981cd5b813dc1070511aadb"
|
|
11981
11976
|
},
|
|
11982
11977
|
{
|
|
11983
11978
|
"path": "tests/provision_restart.mjs",
|
|
@@ -12697,7 +12692,7 @@
|
|
|
12697
12692
|
{
|
|
12698
12693
|
"path": "tests/start_env_guard.mjs",
|
|
12699
12694
|
"mode": "0000644",
|
|
12700
|
-
"sha256": "
|
|
12695
|
+
"sha256": "dd7171dbc59e024de99a063763219237814cf4c943fb3d873cc324f3bfde7f25"
|
|
12701
12696
|
},
|
|
12702
12697
|
{
|
|
12703
12698
|
"path": "tests/start_rebase_warning.mjs",
|
|
@@ -12917,7 +12912,7 @@
|
|
|
12917
12912
|
{
|
|
12918
12913
|
"path": "tests/upgrade.mjs",
|
|
12919
12914
|
"mode": "0000644",
|
|
12920
|
-
"sha256": "
|
|
12915
|
+
"sha256": "def73ab0f532c04df167d77917981eaf7e0dc7e1d2a7e7c85f3f962d7d65ce2a"
|
|
12921
12916
|
},
|
|
12922
12917
|
{
|
|
12923
12918
|
"path": "tests/upgrade_persist_pin.mjs",
|
|
@@ -1946,5 +1946,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1946
1946
|
landed since 1.19.734 with no explicit bump. run 34768136326. (task 1002620)
|
|
1947
1947
|
1.19.736 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1948
1948
|
landed since 1.19.735 with no explicit bump. run 34771773838. (task 1002620)
|
|
1949
|
+
1.19.737 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1950
|
+
landed since 1.19.736 with no explicit bump. run 34795026282. (task 1002620)
|
|
1951
|
+
1.19.738 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1952
|
+
landed since 1.19.737 with no explicit bump. run 34797297122. (task 1002620)
|
|
1949
1953
|
---------------------------------------------------------------------------
|
|
1950
1954
|
```
|
|
@@ -108,7 +108,7 @@ node .claude/scheduled-tasks/core-update-subscription/subscribe.js #
|
|
|
108
108
|
node .claude/scheduled-tasks/core-update-subscription/subscribe.js --apply # upgrade
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
Each subscribed instance: read its installed core → ask the registry (`npm view`,
|
|
111
|
+
Each subscribed instance: read its installed core → ask the registry (`npm view`, no credential needed — the package is public) → resolve the newest version its channel permits → if newer, `bongos upgrade --to <v> --registry` with **auto-rollback ON**. It prints one line per instance plus a tally; a non-zero exit means an upgrade failed (already auto-reverted).
|
|
112
112
|
|
|
113
113
|
**Triple-gated, so nothing moves by accident:** the `core-update-subscription` routine is `requiresAutonomy: true` + **default OFF** in [`config/scheduled-routines.json`](../../config/scheduled-routines.json), the roster ships empty, and every upgrade is health-gated. Arm it by enabling the routine's timer on the control plane once autonomy is on.
|
|
114
114
|
|
|
@@ -116,7 +116,8 @@ Each subscribed instance: read its installed core → ask the registry (`npm vie
|
|
|
116
116
|
|
|
117
117
|
## Gotchas
|
|
118
118
|
|
|
119
|
-
- **`--registry` no
|
|
119
|
+
- **`--registry` needs no credential at all** (task 1003878). `@bongos/core` is **public** on npm (task 1003872), so `bongos upgrade --registry` resolves it from the default registry like any other dependency — no token, no login, and no instance `.npmrc`. An install failure on this path is therefore a *real* failure (network, registry outage, or a version that was never published), never a missing credential.
|
|
120
|
+
*History, so the symptom is recognisable on an old instance:* the core used to publish private, and the instance `.npmrc` read `_authToken=<redacted> npm gives that per-project key precedence, so with the env var unset it expanded EMPTY and **masked a perfectly valid literal token in `~/.npmrc`** — npm went anonymous, the private core 404'd, and the error blamed a credential the box actually held. Task 1002754 worked around it by resolving the token in-process; task 1003878 deleted the whole mechanism along with the `.npmrc` that caused it. An instance still carrying a committed `.npmrc` from a pre-1003878 scaffold can safely delete it.
|
|
120
121
|
- **A green `publish` run is not evidence of a publish.** The publish-on-merge lane succeeds on its no-op paths too (cascade terminator, armed-gate). Read the run's **job summary**, which now states the verdict explicitly: `PUBLISHED @bongos/core@<v>` vs `NO-OP — nothing was published this run`.
|
|
121
122
|
- **`PGDATABASE` is load-bearing on a co-tenant droplet.** `migrate.sh` defaults `PGDATABASE` to `example` — **production**. Always set `PGDATABASE=<instance-db>` explicitly, or you migrate the wrong database.
|
|
122
123
|
- **The health check polls the restart** (fixed in task [#1984](https://example.com/builders#/task/1984)). The service binds its port ~1s after `systemctl restart`, so `--health-url` now polls up to ~30s for a 2xx before reporting a warning — an immediate `fetch failed` no longer means a failed upgrade. (Pre-1984 cores fetched once and spuriously reported `fetch failed`; there, confirm with `is-active` + a manual `curl`.)
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.738",
|
|
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.738",
|
|
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.738",
|
|
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/go-live.js
CHANGED
|
@@ -84,7 +84,7 @@ function isSafeToken(v) {
|
|
|
84
84
|
// unit name that the script would then try to `systemctl stop`. Absent, the stop and
|
|
85
85
|
// the restart are simply skipped: there is no `reset --hard` to race.
|
|
86
86
|
const REQUIRED_FIELDS = ['instanceDir', 'service', 'healthUrl', 'versionUrl'];
|
|
87
|
-
const OPTIONAL_FIELDS = ['ssh', 'local', 'deployTimer', 'deployService', '
|
|
87
|
+
const OPTIONAL_FIELDS = ['ssh', 'local', 'deployTimer', 'deployService', 'runAs', 'backupDb', 'backupDir', 'env', 'registryPackage', 'pinMode'];
|
|
88
88
|
|
|
89
89
|
// How the bump is made durable — the other half of co-tenant mode (task 1003521).
|
|
90
90
|
//
|
|
@@ -153,7 +153,6 @@ const CONFIG_TEMPLATE = `{
|
|
|
153
153
|
"versionUrl": "http://127.0.0.1:3002/version", // box-side truth read back after
|
|
154
154
|
"deployTimer": "my-deploy.timer", // optional: pull-deploy timer (its reset --hard is the hazard)
|
|
155
155
|
"deployService": "my-deploy.service", // optional: forced tick proves the pin stuck
|
|
156
|
-
"npmEnvFile": "/etc/my/npm.env", // optional: root-only NPM_TOKEN for a PRIVATE package
|
|
157
156
|
"runAs": "me", // optional: unprivileged owner of the instance
|
|
158
157
|
"backupDb": "mydb", // optional: pg_dump before the bump
|
|
159
158
|
"backupDir": "~/backups",
|
|
@@ -247,40 +246,19 @@ function remote(target, script, { run = spawnSync, timeoutMs = 900000 } = {}) {
|
|
|
247
246
|
// `sudo -u <user> -H env K=V … <cmd>` — the shape every runbook entry uses. Returns
|
|
248
247
|
// a string safe to embed because every component was allowlist-validated.
|
|
249
248
|
//
|
|
250
|
-
// A privileged child needs npm auth, and there is NO WAY to hand it a secret through
|
|
251
|
-
// sudo without logging it (task 1003218, corrected).
|
|
252
|
-
//
|
|
253
|
-
// `env NPM_TOKEN="$NPM_TOKEN" …` → sudo logs it in COMMAND=
|
|
254
|
-
// `sudo --preserve-env=NPM_TOKEN …` → sudo logs it in ENV=
|
|
255
|
-
//
|
|
256
|
-
// The first was the original bug; the second was my "fix", which only MOVED the value
|
|
257
|
-
// to a different field of the same log line. Both put a live credential in a
|
|
258
|
-
// persistent journal. What matters is that sudo logs its whole invocation, so the
|
|
259
|
-
// secret must never be part of it.
|
|
260
|
-
//
|
|
261
|
-
// So the child DERIVES the token itself, from a file it can already read. Only the
|
|
262
|
-
// `sed` appears in argv; the value never does. This is the shape docs/recipes/
|
|
263
|
-
// core-release-pipeline.md already documents as the safe one. The guard matters as
|
|
264
|
-
// much as the derivation: an EMPTY token silently sends npm to the public registry,
|
|
265
|
-
// which 404s the private package, which auto-rolls-back "cleanly" having taught you
|
|
266
|
-
// nothing — so an empty read aborts loudly instead of half-running.
|
|
267
|
-
//
|
|
268
|
-
// (The nightly lane never takes this path: it runs under systemd as the owner with an
|
|
269
|
-
// EnvironmentFile, no sudo, and leaks nothing. This is the manual/agent path only.)
|
|
270
|
-
const NPM_AUTH_PREAMBLE = [
|
|
271
|
-
'export NPM_TOKEN="${NPM_TOKEN:-$(sed -n \'s/.*_authToken=//p\' "$HOME/.npmrc" 2>/dev/null | head -1)}"',
|
|
272
|
-
'[ -n "$NPM_TOKEN" ] || { echo "go-live: EMPTY NPM_TOKEN — npm would 404 the private core and roll back; aborting" >&2; exit 12; }',
|
|
273
|
-
].join('; ');
|
|
274
|
-
|
|
275
249
|
// `sudo -u <user> -H env K=V … <cmd>` — the shape every runbook entry uses. Returns
|
|
276
250
|
// a string safe to embed because every component was allowlist-validated.
|
|
277
251
|
//
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
|
|
252
|
+
// Only NON-secret configuration (PGDATABASE, HOME) travels in the plain `env K=V` list —
|
|
253
|
+
// seeing those in a log is useful rather than harmful.
|
|
254
|
+
//
|
|
255
|
+
// This used to take a `needsNpm` option wrapping the command in a preamble that DERIVED an
|
|
256
|
+
// npm credential on the far side, because sudo logs its whole invocation and there was no way
|
|
257
|
+
// to hand a privileged child a secret without journalling it (task 1003218). @bongos/core is
|
|
258
|
+
// public (task 1003872), so the deploy path needs no npm credential at all and the whole
|
|
259
|
+
// hazard is gone rather than mitigated (task 1003878).
|
|
260
|
+
function asOwner(target, cmd) {
|
|
261
|
+
const inner = cmd;
|
|
284
262
|
const envPairs = Object.entries(target.env || {}).map(([k, v]) => `${k}=${v}`);
|
|
285
263
|
if (!target.runAs) {
|
|
286
264
|
const joined = envPairs.join(' ');
|
|
@@ -622,23 +600,21 @@ function renderReport(r) {
|
|
|
622
600
|
// Phases
|
|
623
601
|
// ---------------------------------------------------------------------------
|
|
624
602
|
|
|
625
|
-
// Registry proof + optional content proof, in ONE round-trip. The registry is
|
|
626
|
-
// consulted ON THE BOX because
|
|
627
|
-
//
|
|
603
|
+
// Registry proof + optional content proof, in ONE round-trip. The registry is still
|
|
604
|
+
// consulted ON THE BOX — not because auth lives there (the package is public now), but
|
|
605
|
+
// because the box's answer is the one that matters: its network, its resolver, its cache.
|
|
628
606
|
function probeRemote(target, targetVersion, marker, opts = {}) {
|
|
629
607
|
const pkg = target.registryPackage || '@bongos/core';
|
|
630
|
-
const src = target.npmEnvFile ? `set -a; . ${target.npmEnvFile}; set +a` : 'true';
|
|
631
608
|
const script = `
|
|
632
609
|
set -uo pipefail
|
|
633
|
-
${src}
|
|
634
610
|
echo "##REGISTRY##"
|
|
635
|
-
${asOwner(target, `npm view ${pkg}@${targetVersion} version
|
|
611
|
+
${asOwner(target, `npm view ${pkg}@${targetVersion} version`)} 2>&1 | tail -1
|
|
636
612
|
echo "##TREE##"
|
|
637
613
|
( cd ${target.instanceDir} && ${target.runAs ? `sudo -u ${target.runAs} -H ` : ''}git status --porcelain | head -20 )
|
|
638
614
|
echo "##INSTALLED##"
|
|
639
615
|
${asOwner(target, `node -e "console.log(require('${target.instanceDir}/node_modules/${pkg}/package.json').version)"`)} 2>&1 | tail -1
|
|
640
616
|
${marker ? `echo "##MARKER##"
|
|
641
|
-
${asOwner(target, `sh -c ${shq(markerProbe(pkg, targetVersion, marker))}
|
|
617
|
+
${asOwner(target, `sh -c ${shq(markerProbe(pkg, targetVersion, marker))}`)}` : ''}
|
|
642
618
|
echo "##END##"
|
|
643
619
|
`;
|
|
644
620
|
const res = remote(target, script, opts);
|
|
@@ -709,7 +685,6 @@ function applyGoLive(target, targetName, targetVersion, from, opts = {}) {
|
|
|
709
685
|
const pkg = target.registryPackage || '@bongos/core';
|
|
710
686
|
const pinMode = pinModeOf(target);
|
|
711
687
|
const steps = [];
|
|
712
|
-
const src = target.npmEnvFile ? `set -a; . ${target.npmEnvFile}; set +a` : 'true';
|
|
713
688
|
|
|
714
689
|
// Phase A — stop the pull-deploy timer, then back up. The timer's `reset --hard`
|
|
715
690
|
// is the hazard: it would revert an uncommitted pin mid-flight. A co-tenant has no
|
|
@@ -738,8 +713,7 @@ echo "##END##"
|
|
|
738
713
|
const upgrade = upgradeCommand(target, targetVersion);
|
|
739
714
|
const phaseB = remote(target, `
|
|
740
715
|
set -uo pipefail
|
|
741
|
-
${
|
|
742
|
-
${asOwner(target, `bash -c "cd ${target.instanceDir} && ${upgrade}"`, { needsNpm: true })}
|
|
716
|
+
${asOwner(target, `bash -c "cd ${target.instanceDir} && ${upgrade}"`)}
|
|
743
717
|
`, opts);
|
|
744
718
|
const rolledBack = /rolled back/i.test(phaseB.stdout + phaseB.stderr);
|
|
745
719
|
steps.push({
|
|
@@ -995,7 +969,7 @@ if (require.main === module) {
|
|
|
995
969
|
module.exports = {
|
|
996
970
|
loadConfig, selectTarget, validateTarget, planGoLive, artistGateEntries, readArtistGate, ARTIST_GATE_PATH, ARTIST_GATE_LEVELS,
|
|
997
971
|
parseMarker, parseMarkerHits,
|
|
998
|
-
renderPlan, renderReport, failureExcerpt, asOwner, isSafeToken,
|
|
972
|
+
renderPlan, renderReport, failureExcerpt, asOwner, isSafeToken, refreshMain, isLocalTarget, gitRefExists, SUDO_PREAMBLE,
|
|
999
973
|
applyGoLive, upgradeCommand, pinStep, pinModeOf,
|
|
1000
974
|
CONFIG_TEMPLATE, HALTS, SEMVER_RE, PIN_MODES, DEFAULT_PIN_MODE,
|
|
1001
975
|
};
|
package/scripts/gds/init.js
CHANGED
|
@@ -59,13 +59,13 @@
|
|
|
59
59
|
// bongos init --vendor-core <tgz> greenfield only: vendor the @bongos/core .tgz + pin
|
|
60
60
|
// package.json to file:vendor/… + generate package-lock.json,
|
|
61
61
|
// so the scaffold is `npm ci`-installable (ADR 0108 gap 1/4).
|
|
62
|
-
// Without it the scaffold pins the caret range
|
|
63
|
-
// resolves from the
|
|
64
|
-
// bongos init --registry-core greenfield only: keep the caret pin
|
|
65
|
-
// .
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
62
|
+
// Without it the scaffold pins the caret range, which
|
|
63
|
+
// resolves from the public npm registry (--registry-core).
|
|
64
|
+
// bongos init --registry-core greenfield only: keep the caret pin and generate
|
|
65
|
+
// package-lock.json from the registry. @bongos/core is
|
|
66
|
+
// PUBLIC (task 1003872), so this needs no token, no login
|
|
67
|
+
// and no .npmrc — it resolves from the default registry
|
|
68
|
+
// like any other dependency.
|
|
69
69
|
// Mutually exclusive with --vendor-core (registry vs tarball).
|
|
70
70
|
|
|
71
71
|
const fs = require('node:fs');
|
|
@@ -78,7 +78,6 @@ const { materializeClaude } = require('./claude-materialize'); // ADR 0108 §3 (
|
|
|
78
78
|
const { detectStack, formatStackSummary } = require('./adopt-detect'); // ADR 0121 §Decision 4 (task 2043): inspect an existing repo before layering
|
|
79
79
|
const { resolveBacklogTasks, DEFAULT_MAX: BACKLOG_MAX } = require('./adopt-import'); // ADR 0121 §Decision 3 (task 2044): import an existing backlog
|
|
80
80
|
const { preflightAdopt, formatPreflight } = require('./adopt-preflight'); // ADR 0121 §Decision 5 (task 2045): the accept-gated conflict pre-flight
|
|
81
|
-
const { writeNpmrc, hasNpmToken } = require('./npmrc'); // ADR 0134 (task 2090): the registry-install path's env-fed .npmrc
|
|
82
81
|
|
|
83
82
|
const KNOWN_MODULES = ['game', 'art-pipeline', 'discord', 'dev-box'];
|
|
84
83
|
|
|
@@ -946,7 +945,7 @@ async function main(argv) {
|
|
|
946
945
|
const adopt = hasFlag('--adopt', args); // brownfield: LAYER onto an existing repo (ADR 0121)
|
|
947
946
|
const accept = hasFlag('--accept', args) || hasFlag('--yes', args) || hasFlag('-y', args); // adopt pre-flight accept-gate (§Decision 5)
|
|
948
947
|
const vendorCore = arg('--vendor-core', args); // greenfield: vendor + file:-pin a core .tgz, then generate the lockfile (ADR 0108 gap 1/4, task 2053)
|
|
949
|
-
const registryCore = hasFlag('--registry-core', args); // greenfield: caret pin
|
|
948
|
+
const registryCore = hasFlag('--registry-core', args); // greenfield: keep the caret pin and generate the lockfile from the public registry (task 1003878)
|
|
950
949
|
|
|
951
950
|
if (adopt && force) {
|
|
952
951
|
console.error('init: --force is a greenfield-only overwrite; --adopt never overwrites, so --force is ignored.');
|
|
@@ -1030,30 +1029,23 @@ async function main(argv) {
|
|
|
1030
1029
|
}
|
|
1031
1030
|
}
|
|
1032
1031
|
|
|
1033
|
-
// Registry-install path
|
|
1034
|
-
//
|
|
1035
|
-
//
|
|
1036
|
-
//
|
|
1037
|
-
//
|
|
1038
|
-
//
|
|
1032
|
+
// Registry-install path: keep the caret pin writeConfigs wrote and generate
|
|
1033
|
+
// package-lock.json from the registry, so a later `npm ci` is reproducible.
|
|
1034
|
+
//
|
|
1035
|
+
// This used to write an env-fed npm auth file and skip the lockfile when no credential was
|
|
1036
|
+
// present, because @bongos/core published PRIVATE. It is public now (task 1003872), so it
|
|
1037
|
+
// resolves from the default registry like any other dependency: no auth file, no credential,
|
|
1038
|
+
// and no tokenless-404 branch to explain (task 1003878).
|
|
1039
1039
|
if (registryCore) {
|
|
1040
1040
|
if (dryRun) {
|
|
1041
|
-
console.log(
|
|
1042
|
-
? 'run `npm install` to generate package-lock.json from the registry'
|
|
1043
|
-
: 'SKIP lockfile generation (no NPM_TOKEN — the private core 404s until one is provided)'}`);
|
|
1041
|
+
console.log('\n[dry-run] would run `npm install` to generate package-lock.json from the registry');
|
|
1044
1042
|
} else {
|
|
1045
|
-
console.log('\nWiring the registry-install path
|
|
1046
|
-
|
|
1047
|
-
if (
|
|
1048
|
-
|
|
1049
|
-
if (!inst.ok) {
|
|
1050
|
-
console.error(` ! npm install failed (${inst.reason || 'exit ' + inst.code}) — confirm NPM_TOKEN reaches the private @bongos org; package-lock.json not generated.`);
|
|
1051
|
-
} else {
|
|
1052
|
-
console.log(' ✓ npm install → package-lock.json generated (pulled @bongos/core from the registry)');
|
|
1053
|
-
}
|
|
1043
|
+
console.log('\nWiring the registry-install path:');
|
|
1044
|
+
const inst = upgrade.npmInstall(dir);
|
|
1045
|
+
if (!inst.ok) {
|
|
1046
|
+
console.error(` ! npm install failed (${inst.reason || 'exit ' + inst.code}) — package-lock.json not generated. @bongos/core is public, so this is a real failure (network, registry outage, or an unpublished version), not a missing credential.`);
|
|
1054
1047
|
} else {
|
|
1055
|
-
console.log('
|
|
1056
|
-
console.log(' Provide a read-only automation token (paid `bongos` org) as NPM_TOKEN before `npm ci`; without it the private package 404s cleanly.');
|
|
1048
|
+
console.log(' ✓ npm install → package-lock.json generated (pulled @bongos/core from the registry)');
|
|
1057
1049
|
}
|
|
1058
1050
|
}
|
|
1059
1051
|
}
|
|
@@ -19,12 +19,6 @@ const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
|
19
19
|
// plain require (its require.main guard means importing it runs no CLI).
|
|
20
20
|
const oauthSecret = require('./oauth-secret');
|
|
21
21
|
|
|
22
|
-
// The registry-install gates + env-fed .npmrc (ADR 0134, task 2090). Lets the
|
|
23
|
-
// standalone scaffold OPT IN to pulling the PRIVATE @bongos/core from the npm
|
|
24
|
-
// registry (PROVISION_CORE_FROM_REGISTRY=1) instead of vendoring a tarball, with a
|
|
25
|
-
// fail-soft fallback to the vendored path when no NPM_TOKEN is present.
|
|
26
|
-
const { registryCoreRequested, hasNpmToken } = require('./npmrc');
|
|
27
|
-
|
|
28
22
|
// The HTTP User-Agent the manifest conversion stamps its GitHub call with (GitHub
|
|
29
23
|
// requires a UA). Overridable so a fork can identify itself; not secret.
|
|
30
24
|
const MANIFEST_UA = process.env.PROVISION_GITHUB_UA || 'cloudbongos-provisioner';
|
|
@@ -195,10 +189,8 @@ module.exports = {
|
|
|
195
189
|
REPO_ROOT,
|
|
196
190
|
coreVersionSafe,
|
|
197
191
|
hasFlag,
|
|
198
|
-
hasNpmToken,
|
|
199
192
|
loadDeps,
|
|
200
193
|
numEnv,
|
|
201
194
|
oauthSecret,
|
|
202
195
|
provisionerBotEmail,
|
|
203
|
-
registryCoreRequested,
|
|
204
196
|
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// scripts/gds/provision-repo-auth.js — the repo-identity + owner-credential leaf: parse a
|
|
2
|
+
// `target_ref` into { owner, name }, build an instance's SSH deploy remote, find the
|
|
3
|
+
// provisioning row that owns a checkout, and read that owner's stored GitHub token
|
|
4
|
+
// (task 1003081).
|
|
5
|
+
//
|
|
6
|
+
// WHY THIS FILE EXISTS AT ALL. scripts/gds/upgrade.js is inside the merge resolver's
|
|
7
|
+
// EXECUTED CLOSURE (modules/lifecycle/executed-closure.js; 2026-08-29 security audit ref
|
|
8
|
+
// B1) — the set of files the server may run out of a CLONED BUILDER BRANCH. That closure
|
|
9
|
+
// is a STATIC walk of string-literal requires, and it does not care whether a require is
|
|
10
|
+
// lazy or inside a function: upgrade.js reaching these helpers through `./provision`
|
|
11
|
+
// dragged provision.js plus provision-config/-repo/-units/-net.js into the closure with
|
|
12
|
+
// it — five more files a branch could edit into the live server's process, three of them
|
|
13
|
+
// carrying computed requires (provision-config 7, provision-repo 1, provision-units 1)
|
|
14
|
+
// that tests/executed_closure.mjs has no account of. Carving the helpers into this LEAF
|
|
15
|
+
// instead grows the closure by exactly ONE small file, whose computed-require count is
|
|
16
|
+
// pinned in that test.
|
|
17
|
+
//
|
|
18
|
+
// THE RULE THAT KEEPS IT THAT WAY. Top level requires node builtins ONLY. Every
|
|
19
|
+
// non-builtin require is lazy AND inside a function body, so nothing on the generator
|
|
20
|
+
// path ever resolves it. Never require provision-config.js (or any other provision file)
|
|
21
|
+
// from here — REPO_ROOT is recomputed below precisely so we don't have to.
|
|
22
|
+
//
|
|
23
|
+
// provision-repo.js requires this module and re-exports parseTargetRef,
|
|
24
|
+
// instanceRepoRemote and resolveOwnerGithubToken unchanged, so provision.js,
|
|
25
|
+
// provision-units.js, modules/provisioning/credential-preflight.js and their tests see
|
|
26
|
+
// exactly the surface they saw before.
|
|
27
|
+
|
|
28
|
+
'use strict';
|
|
29
|
+
|
|
30
|
+
const path = require('node:path');
|
|
31
|
+
|
|
32
|
+
// The same value provision-config.js exports as REPO_ROOT, recomputed here rather than
|
|
33
|
+
// imported: that module carries seven computed requires, and importing it would put all
|
|
34
|
+
// of them inside the executed closure (see the header).
|
|
35
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
36
|
+
|
|
37
|
+
// Parse an 'owner/repo' (or a github SSH/HTTPS URL) target_ref → { owner, name }.
|
|
38
|
+
// Null when unparseable, so the scaffold can fail with actionable guidance.
|
|
39
|
+
function parseTargetRef(ref) {
|
|
40
|
+
const m = String(ref || '').trim()
|
|
41
|
+
.replace(/^git@[^:]+:/, '')
|
|
42
|
+
.replace(/^https?:\/\/[^/]+\//, '')
|
|
43
|
+
.match(/^([A-Za-z0-9][A-Za-z0-9_.-]*)\/([A-Za-z0-9][A-Za-z0-9_.-]*?)(?:\.git)?$/);
|
|
44
|
+
return m ? { owner: m[1], name: m[2] } : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The git remote for an instance's OWN repo. Reuses the SAME SSH deploy alias the
|
|
48
|
+
// control-plane checkout uses (git@<alias>:<owner>/<repo>.git) — NO secret in the
|
|
49
|
+
// command (SSH-key auth, nothing to leak into journald), consistent with how the
|
|
50
|
+
// platform repo is wired. Standing up a new instance repo therefore needs only that
|
|
51
|
+
// the control plane's deploy key has WRITE access to that repo (ADR 0125). Alias is
|
|
52
|
+
// overridable via PROVISION_GIT_SSH_ALIAS.
|
|
53
|
+
function instanceRepoRemote(repo, { sshAlias = process.env.PROVISION_GIT_SSH_ALIAS || 'github-deploy' } = {}) {
|
|
54
|
+
return `git@${sshAlias}:${repo.owner}/${repo.name}.git`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The instance owner's stored `repo`-scoped OAuth token (ADR 0155), or null (none
|
|
58
|
+
// stored, org-owned repo, secret-box off). Shared by the scaffold push, the
|
|
59
|
+
// private-repo deploy-key wiring, the teardown revoke, and upgrade.js's pin push.
|
|
60
|
+
// `deps.getGithubToken` is injectable for tests; production resolves it lazily against
|
|
61
|
+
// the core db slice with the runner's pool.
|
|
62
|
+
async function resolveOwnerGithubToken(inst, deps, { tag = '[repo-auth]' } = {}) {
|
|
63
|
+
if (!inst.owner_builder_id) return null;
|
|
64
|
+
try {
|
|
65
|
+
const fetchTok = deps.getGithubToken
|
|
66
|
+
|| ((bid) => require(path.join(REPO_ROOT, 'src/bongos/db')).getGithubToken({ builderId: bid, p: deps.db })); // eslint-disable-line global-require
|
|
67
|
+
const r = await fetchTok(inst.owner_builder_id);
|
|
68
|
+
return r && r.token ? r.token : null;
|
|
69
|
+
} catch (e) {
|
|
70
|
+
(deps.log || (() => {}))(` ${tag} owner OAuth token lookup failed (${e && e.message})`);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// The provisioning row that owns a checkout — where `owner_builder_id` (whose token) and
|
|
76
|
+
// `target_ref` (which repo) come from. A control-plane caller hands it over as
|
|
77
|
+
// `deps.instance`; a CLI run on the control plane (`--instance /srv/bongos/<slug>`) finds
|
|
78
|
+
// it by the checkout's basename, which standaloneRoot makes the slug. An instance's own
|
|
79
|
+
// database has no provisioning table and answers null — that is the origin path, not an
|
|
80
|
+
// error, so every failure here is swallowed. The default lookup requires the core pool
|
|
81
|
+
// in-process (a CLI run exits right after; a programmatic caller passes `deps.db` or
|
|
82
|
+
// `deps.getInstanceBySlug`).
|
|
83
|
+
//
|
|
84
|
+
// It lives HERE rather than in its only caller, upgrade.js: both of its default requires
|
|
85
|
+
// are computed, and upgrade.js is in the executed closure with a pinned count of its own
|
|
86
|
+
// (see the header). Keeping them on this side leaves upgrade.js string-literal-only.
|
|
87
|
+
async function lookupInstanceRow(instanceDir, deps = {}) {
|
|
88
|
+
if (deps.instance) return deps.instance;
|
|
89
|
+
const slug = path.basename(instanceDir);
|
|
90
|
+
try {
|
|
91
|
+
const bySlug = deps.getInstanceBySlug || (async (s) => {
|
|
92
|
+
const provisioning = require(path.join(REPO_ROOT, 'modules/provisioning/provisioning')); // eslint-disable-line global-require
|
|
93
|
+
const db = deps.db || require(path.join(REPO_ROOT, 'src/bongos/pool')).pool; // eslint-disable-line global-require
|
|
94
|
+
return provisioning.getInstanceBySlug(db, s);
|
|
95
|
+
});
|
|
96
|
+
const row = await bySlug(slug);
|
|
97
|
+
return row && row.owner_builder_id && row.target_ref ? row : null;
|
|
98
|
+
} catch { return null; }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = {
|
|
102
|
+
instanceRepoRemote,
|
|
103
|
+
lookupInstanceRow,
|
|
104
|
+
parseTargetRef,
|
|
105
|
+
resolveOwnerGithubToken,
|
|
106
|
+
};
|