@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 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.736",
6
- "core_contract": "1.19.736",
7
- "source_commit": "917ef53331537c93796fb9db9a8b77be72cffe22",
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-13T17:30:35.415Z",
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": 2108,
14
+ "functional_verbatim": 2107,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2611,
20
- "tree_sha256": "4cd2b02aa80c6f20919a54413b4983dd572af10e199bf5401e6e75ee8914b543",
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": "8dc183ec4dc010d5897f8cca67dd0f55bb6abe5620e358388e9ed07a2f6521a8"
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": "36e3c53a8bbe0b987e19333f12cac4dc0951822686759623e3273843e313188e"
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": "014dc18dcf764c17d7927fe5475436f4d6e2acf18e7baa6f16b7852306dc89e0"
7540
+ "sha256": "35ace7cf502387465f75cea83b1159bb82349d019555503f51769a264fd19c3e"
7541
7541
  },
7542
7542
  {
7543
7543
  "path": "package.json",
7544
7544
  "mode": "0000644",
7545
- "sha256": "96f124d7dee1ff76d006c102798790ab7b59b27c0fbacdb36c545d658c185f39"
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": "95c5109be4ab748c5ee59f22489ba5e3de45bafccf1c543d8cf174fc89d75dab"
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": "e81ab71bcd7d2f06b5aaf462dd099776c966f9840a109846a9753f12482b5281"
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": "359674dd4ba6c7c4dd446882ec88f7d9feffd474300e17736d3129c2526082c1"
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": "3612ebe8f54da7498a6c4998e0d1d1c4596a80d54f70b5543b4a46dfca2ffbcf"
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": "007c5cfa3b42567df0016038fc7ec5c7dbdab60ec6ec8ecd9d06386933e5641d"
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": "1bfa9d27bc91836f636f57466876d40c56e13bdb1038451e8298c8a32cb674d6"
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": "db54fba6065c8b662f574aa9443e686df4e5a152865342999b1f20d50ece01ed"
8900
+ "sha256": "1aeeb6171244e1d42ee7189a3aa2ae5d83fd175bfe1ce89300d6ca7511819f0b"
8901
8901
  },
8902
8902
  {
8903
8903
  "path": "scripts/gds/upgrade.js",
8904
8904
  "mode": "0000644",
8905
- "sha256": "4f30ee3036846f449f6fc8143da32b3767376352743d373c39ace2f50f66e913"
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": "ad6748bac62f397b729ead21e7f591bc8e03fe771fff22855eb3813b066663da"
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": "8c0ed9407dd3f19dab03f45fd2778fae0d2a5595bcd0daefc0e3e5a63137aa04"
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": "2fc62d14a1c7e4fbd63fe4a18a4d1be85dfc481bc12a913cdd0d3812984bd79f"
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": "3026220335d63e157a1732d03ee6e66f0509bf965cb8676a651cbb3e1780be34"
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": "0e177a4817094985e4aeec0c74883502762304d2174c1e13e77a87404b739088"
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": "9b0a7d7aeb761cb6fb0c5e1ae650e0848af12e92887ebd630814b35b434c1f41"
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": "492b9dc7efdd6f63b81f1662d5fdc9a7541b7f8ccc6c0ec77a456ad3f574ee74"
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`, needs `NPM_TOKEN`) → 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).
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 longer needs a hand-exported `NPM_TOKEN`** (task 1002754). The instance `.npmrc` reads `_authToken=<redacted>, and 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; the error blamed a "missing" credential the box actually held). `bongos upgrade --registry` now resolves the credential itself: env `NPM_TOKEN` wins, else the **user-level `~/.npmrc`'s literal `//registry.npmjs.org/:_authToken=…`** is injected into the install's environment (forward install and rollback reinstall both). A pin bump on a box whose `~/.npmrc` authenticates therefore succeeds with **no exported env var**. Resolver: `resolveNpmToken()` in [`scripts/gds/npmrc.js`](../../scripts/gds/npmrc.js).
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.736",
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.736",
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.736",
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",
@@ -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', 'npmEnvFile', 'runAs', 'backupDb', 'backupDir', 'env', 'registryPackage', 'pinMode'];
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
- // `needsNpm` wraps the command so it derives its own npm auth (see above) instead of
279
- // having a secret handed across the privilege boundary. Only NON-secret configuration
280
- // (PGDATABASE, HOME) travels in the plain `env K=V` list — seeing those in a log is
281
- // useful rather than harmful.
282
- function asOwner(target, cmd, { needsNpm = false } = {}) {
283
- const inner = needsNpm ? `sh -c ${shq(`${NPM_AUTH_PREAMBLE}; ${cmd}`)}` : cmd;
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 a PRIVATE package 404s without the root-only
627
- // NPM_TOKEN, and because the box's answer is the one that matters.
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`, { needsNpm: true })} 2>&1 | tail -1
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))}`, { needsNpm: true })}` : ''}
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
- ${src}
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, NPM_AUTH_PREAMBLE, refreshMain, isLocalTarget, gitRefExists, SUDO_PREAMBLE,
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
  };
@@ -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 (which now
63
- // resolves from the PRIVATE registry — see --registry-core).
64
- // bongos init --registry-core greenfield only: keep the caret pin + write an env-fed
65
- // .npmrc (`_authToken=${NPM_TOKEN}`) so `npm ci` pulls the
66
- // PRIVATE @bongos/core from the npm registry (ADR 0134).
67
- // With NPM_TOKEN set it also generates package-lock.json;
68
- // without a token it 404s (the expected tokenless outcome).
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 + env-fed .npmrc → `npm ci` pulls the PRIVATE core from the registry (ADR 0134, task 2090)
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 (ADR 0134, task 2090): keep the caret pin writeConfigs wrote
1034
- // and add an env-fed .npmrc so `npm ci` pulls the PRIVATE @bongos/core from the
1035
- // registry. With NPM_TOKEN present we also generate package-lock.json (a reproducible
1036
- // `npm ci` later); without a token we still write the caret pin + .npmrc so the
1037
- // scaffold is registry-shaped, and say plainly that the private package 404s until a
1038
- // token is provided — the expected tokenless outcome the ADR calls to verify.
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(`\n[dry-run] would write .npmrc (env-fed @bongos auth) and ${hasNpmToken()
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 (ADR 0134):');
1046
- writeNpmrc(dir, { log: console.log });
1047
- if (hasNpmToken()) {
1048
- const inst = upgrade.npmInstall(dir);
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(' • no NPM_TOKEN in the environment — wrote the caret pin + .npmrc but skipped lockfile generation.');
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
+ };