@bongos/core 1.19.735 → 1.19.737
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 -21
- package/docs/adr/0134-private-first-npm-distribution.md +1 -1
- package/docs/adr/0283-the-core-publishes-publicly-because-access-was-never-the-thing-protecting-it.md +53 -0
- package/docs/adr/README.md +1 -0
- package/docs/module-api-changelog.md +4 -0
- package/docs/recipes/private-npm-distribution.md +1 -1
- package/package-lock.json +2 -2
- package/package.json +2 -2
- package/scripts/gds/package-core.js +30 -16
- package/scripts/gds/provision-repo-auth.js +106 -0
- package/scripts/gds/provision-repo.js +8 -37
- package/scripts/gds/upgrade.js +187 -12
- package/src/module-api.js +1 -1
- package/tests/executed_closure.mjs +8 -0
- package/tests/package_core.mjs +27 -10
- package/tests/upgrade.mjs +284 -1
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.737",
|
|
6
|
+
"core_contract": "1.19.737",
|
|
7
|
+
"source_commit": "1ceb8f6a2ad0321079098910ad8a721dffa67454",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-14T01:10:42.991Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 479,
|
|
13
13
|
"agent_docs_stubbed": 24,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2109,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2612,
|
|
20
|
+
"tree_sha256": "c34cdc2f87b36f12f71f276e47ce740238e94841fab60b4245613189e0c527d2",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -1157,7 +1157,7 @@
|
|
|
1157
1157
|
{
|
|
1158
1158
|
"path": "docs/adr/0134-private-first-npm-distribution.md",
|
|
1159
1159
|
"mode": "0000644",
|
|
1160
|
-
"sha256": "
|
|
1160
|
+
"sha256": "47b4d52804d291c8fd8952d8a2e8ad673f173a965cf0b5fe100aa6b93d850cd6"
|
|
1161
1161
|
},
|
|
1162
1162
|
{
|
|
1163
1163
|
"path": "docs/adr/0135-module-upstream-submission-interim-queue.md",
|
|
@@ -1919,10 +1919,15 @@
|
|
|
1919
1919
|
"mode": "0000644",
|
|
1920
1920
|
"sha256": "1cd5710392b4226c27af4ce4f661ee6228f80ce16e9f9adb1fb43c3ebb9b5d24"
|
|
1921
1921
|
},
|
|
1922
|
+
{
|
|
1923
|
+
"path": "docs/adr/0283-the-core-publishes-publicly-because-access-was-never-the-thing-protecting-it.md",
|
|
1924
|
+
"mode": "0000644",
|
|
1925
|
+
"sha256": "7060acdff9fde6dbe4831ea1873e5f75bc25acf6e3207a084ac3ddb33ee1a2ff"
|
|
1926
|
+
},
|
|
1922
1927
|
{
|
|
1923
1928
|
"path": "docs/adr/README.md",
|
|
1924
1929
|
"mode": "0000644",
|
|
1925
|
-
"sha256": "
|
|
1930
|
+
"sha256": "872911d695cd9e3bf27b5bb4995acb2e814c73bbb7cabac2f7852ad10e3e1710"
|
|
1926
1931
|
},
|
|
1927
1932
|
{
|
|
1928
1933
|
"path": "docs/api-reference.md",
|
|
@@ -2487,7 +2492,7 @@
|
|
|
2487
2492
|
{
|
|
2488
2493
|
"path": "docs/module-api-changelog.md",
|
|
2489
2494
|
"mode": "0000644",
|
|
2490
|
-
"sha256": "
|
|
2495
|
+
"sha256": "d1b64255f150e8c8a1b88cffa7326633b7e5bc22e22744f7b1d9ee47410f9e13"
|
|
2491
2496
|
},
|
|
2492
2497
|
{
|
|
2493
2498
|
"path": "docs/modules-contract.md",
|
|
@@ -2697,7 +2702,7 @@
|
|
|
2697
2702
|
{
|
|
2698
2703
|
"path": "docs/recipes/private-npm-distribution.md",
|
|
2699
2704
|
"mode": "0000644",
|
|
2700
|
-
"sha256": "
|
|
2705
|
+
"sha256": "33e4f7f3083b8ac5d10f117d4d93e78b9d8b7b4d082087051b69698cb67b03b1"
|
|
2701
2706
|
},
|
|
2702
2707
|
{
|
|
2703
2708
|
"path": "docs/recipes/redteam-patrol.md",
|
|
@@ -7532,12 +7537,12 @@
|
|
|
7532
7537
|
{
|
|
7533
7538
|
"path": "package-lock.json",
|
|
7534
7539
|
"mode": "0000644",
|
|
7535
|
-
"sha256": "
|
|
7540
|
+
"sha256": "fc4587a7fbaaa14aad1e0296015adb626e89da62f4a90a95647f1bbb55a61f1e"
|
|
7536
7541
|
},
|
|
7537
7542
|
{
|
|
7538
7543
|
"path": "package.json",
|
|
7539
7544
|
"mode": "0000644",
|
|
7540
|
-
"sha256": "
|
|
7545
|
+
"sha256": "d7595c39c515821f76d0b5045c3a9cbb400f2c36a663046d19dfc7cab098cf69"
|
|
7541
7546
|
},
|
|
7542
7547
|
{
|
|
7543
7548
|
"path": "public-docs/index.html",
|
|
@@ -8327,7 +8332,7 @@
|
|
|
8327
8332
|
{
|
|
8328
8333
|
"path": "scripts/gds/package-core.js",
|
|
8329
8334
|
"mode": "0000644",
|
|
8330
|
-
"sha256": "
|
|
8335
|
+
"sha256": "89ec2a080f397e1af5c0dede40a7eb22a2e3343d219da4b4a4ba903337bcc420"
|
|
8331
8336
|
},
|
|
8332
8337
|
{
|
|
8333
8338
|
"path": "scripts/gds/paste-token.js",
|
|
@@ -8364,10 +8369,15 @@
|
|
|
8364
8369
|
"mode": "0000644",
|
|
8365
8370
|
"sha256": "502b1aeb571d463aea02ef4db3e848d23fdd8c865e7bfc37f66679c09cc203a9"
|
|
8366
8371
|
},
|
|
8372
|
+
{
|
|
8373
|
+
"path": "scripts/gds/provision-repo-auth.js",
|
|
8374
|
+
"mode": "0000644",
|
|
8375
|
+
"sha256": "b08032667c9f06495c750e4dd8d41fdc2f5aa587093cf7b4de8e7a46390f0724"
|
|
8376
|
+
},
|
|
8367
8377
|
{
|
|
8368
8378
|
"path": "scripts/gds/provision-repo.js",
|
|
8369
8379
|
"mode": "0000644",
|
|
8370
|
-
"sha256": "
|
|
8380
|
+
"sha256": "77ca585d99f4058427f1abbe629d66ce5ec9f87b6c974aebc114d1c0e734d8e2"
|
|
8371
8381
|
},
|
|
8372
8382
|
{
|
|
8373
8383
|
"path": "scripts/gds/provision-units.js",
|
|
@@ -8897,7 +8907,7 @@
|
|
|
8897
8907
|
{
|
|
8898
8908
|
"path": "scripts/gds/upgrade.js",
|
|
8899
8909
|
"mode": "0000644",
|
|
8900
|
-
"sha256": "
|
|
8910
|
+
"sha256": "64b2571770a5be9dc046f6c64adf45a09642c6961a6036c9405e1c933be6c3b7"
|
|
8901
8911
|
},
|
|
8902
8912
|
{
|
|
8903
8913
|
"path": "scripts/gds/validate-design.js",
|
|
@@ -9337,7 +9347,7 @@
|
|
|
9337
9347
|
{
|
|
9338
9348
|
"path": "src/module-api.js",
|
|
9339
9349
|
"mode": "0000644",
|
|
9340
|
-
"sha256": "
|
|
9350
|
+
"sha256": "9ad95d6241cc8f3dfda5d02445246c1f7fd62b75b5014a15a9254c15a620aef7"
|
|
9341
9351
|
},
|
|
9342
9352
|
{
|
|
9343
9353
|
"path": "src/module-loader/catalog.js",
|
|
@@ -10427,7 +10437,7 @@
|
|
|
10427
10437
|
{
|
|
10428
10438
|
"path": "tests/executed_closure.mjs",
|
|
10429
10439
|
"mode": "0000644",
|
|
10430
|
-
"sha256": "
|
|
10440
|
+
"sha256": "1e1a899e8838c7c84bdad9ea9d8533aca9173ed1e72f15e13c3616493e11900f"
|
|
10431
10441
|
},
|
|
10432
10442
|
{
|
|
10433
10443
|
"path": "tests/explore_visible_states.mjs",
|
|
@@ -11747,7 +11757,7 @@
|
|
|
11747
11757
|
{
|
|
11748
11758
|
"path": "tests/package_core.mjs",
|
|
11749
11759
|
"mode": "0000644",
|
|
11750
|
-
"sha256": "
|
|
11760
|
+
"sha256": "a8278101d0de34bb8214f4c7a5a68760b5546099df27044c20d5c360fdc4442f"
|
|
11751
11761
|
},
|
|
11752
11762
|
{
|
|
11753
11763
|
"path": "tests/parallel-load.mjs",
|
|
@@ -12912,7 +12922,7 @@
|
|
|
12912
12922
|
{
|
|
12913
12923
|
"path": "tests/upgrade.mjs",
|
|
12914
12924
|
"mode": "0000644",
|
|
12915
|
-
"sha256": "
|
|
12925
|
+
"sha256": "010c9ac8b05e25c10b9d43c7efd02454facf0d5663df11df555ea913697e23e8"
|
|
12916
12926
|
},
|
|
12917
12927
|
{
|
|
12918
12928
|
"path": "tests/upgrade_persist_pin.mjs",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 0134 — Private-first npm distribution for the core + client (paid scoped, then public at launch)
|
|
2
2
|
|
|
3
|
-
- **Status:**
|
|
3
|
+
- **Status:** **Superseded.** The **core half** — the private-by-default posture — is superseded by [ADR 0283](0283-the-core-publishes-publicly-because-access-was-never-the-thing-protecting-it.md) ([task 1003872](https://cloudbongos.com/builders#/task/1003872), 2026-09-13): `@bongos/core` now publishes **public**. This ADR's own "public at launch" step is the thing that happened; what remains accurate here is the *reasoning* for the pre-launch posture and the scoped-package mechanics. The **client half** was already superseded by [task 1003742](https://cloudbongos.com/builders#/task/1003742) (2026-09-09) — see the note under Decision. Publish path built in task 2089.
|
|
4
4
|
- **Date:** 2026-07-07
|
|
5
5
|
- **Deciders:** example-owner (owner/Archon), Claude. Owner asked to have the npm/CLI onboarding commands ready but kept private pre-launch, choosing paid private npm over the free GitHub Packages option.
|
|
6
6
|
- **Task:** [#2089](https://example.com/builders#/task/2089) (publish path + private config) · [#2092](https://example.com/builders#/task/2092) (the `@cloudbongos` → `@bongos` scope rename + first real publish) · **Goal:** 35 (project-creation flow), criterion 165.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# ADR 0283 — The core publishes publicly, because access was never the thing protecting it
|
|
2
|
+
|
|
3
|
+
- **Status:** accepted
|
|
4
|
+
- **Date:** 2026-09-13
|
|
5
|
+
- **Task:** [1003872](https://cloudbongos.com/builders#/task/1003872) (goal 1000090, working area 4 — Bongos Core distribution). Gated on [1003869](https://cloudbongos.com/builders#/task/1003869) (sweep the literals), [1003870](https://cloudbongos.com/builders#/task/1003870) (fail-closed leak scan), [1003871](https://cloudbongos.com/builders#/task/1003871) (fixture confirmation). Records the owner decision behind [1003502](https://cloudbongos.com/builders#/task/1003502) ("public repo, public npm, or wait for the mirror" — **public npm**). That task is *not* closed by this ADR: its remaining work is repointing the gate's open-source link, which lives in a separate static site with no auto-deploy, and should follow once the registry flip is live.
|
|
6
|
+
- **Supersedes:** [ADR 0134](0134-private-first-npm-distribution.md)'s **core half** — the pre-launch private-by-default posture. (Its client half was already superseded by task 1003742.)
|
|
7
|
+
- **Adjacent:** [ADR 0258](0258-the-public-cli-is-a-generated-client-package-not-the-published-core.md) (the public CLI, which exists *because* the core was private) · [ADR 0136](0136-update-channel-subscription-policy.md) (the subscription lane this unblocks) · [ADR 0099](0099-delayed-redacted-mirror-export.md) (the mirror pipeline this path deliberately does not use) · [ADR 0161](0161-publish-on-merge.md) (the lane that performs the publish).
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
ADR 0134 chose **private-first**: `@bongos/core` publishes as a restricted scoped package on a paid npm org, with "public at launch" named as a later, deliberate step. That was the right call pre-launch. This ADR is that later step.
|
|
12
|
+
|
|
13
|
+
The posture had a cost that grew quietly, because every symptom looked like a different bug:
|
|
14
|
+
|
|
15
|
+
- **A tokenless `npm install` returns a flat E404** — indistinguishable from "no such package". Every co-tenant and downstream instance upgrade needs a credential to move at all.
|
|
16
|
+
- **The nightly auto-upgrade sweep died on 2026-08-25 and reported `failed=0`.** Its systemd unit passed no `NPM_TOKEN`, the instance's env-fed `.npmrc` expanded to empty, and the private read 404'd. A skip is not a failure in that script's accounting, so nothing anywhere complained while the instance stopped receiving core updates.
|
|
17
|
+
- **Any Anthropic-hosted cloud session** working on an instance repo cannot install the dependency at all.
|
|
18
|
+
- **A newly scaffolded instance's README step 1 is `npm install`** — which is also what installs the `bongos` command. Without a token the owner does not get a stale instance; they get no working instance.
|
|
19
|
+
- **[ADR 0258](0258-the-public-cli-is-a-generated-client-package-not-the-published-core.md) exists only because of this.** `@cloudbongos/cli` was built, and its require-closure painstakingly delazified, so that a new builder could reach the board without a private-registry credential.
|
|
20
|
+
|
|
21
|
+
The decisive point is that **access control was never what protected this artifact.** Three gates decide what is *in* the tarball, and all three now hold:
|
|
22
|
+
|
|
23
|
+
1. The in-build **no-leak gate** (branding-derived literals + live credential shapes) — fail-closed, 2,624 files clean.
|
|
24
|
+
2. The **prior-instance leak scan** — fatal by default since task 1003870, with `--no-leak-scan` the only escape; a real build passes with `✓ no prior-instance literals in the built artifact`.
|
|
25
|
+
3. The **fixture confirmation** of task 1003871 — all 14 credential-shape files checked against each provider's real key format (11 too short to have ever been valid, 3 alphabet sequences or Amazon's own published documentation example).
|
|
26
|
+
|
|
27
|
+
`--access restricted` decides *who may fetch* the artifact. Those three decide *what is in it*. Conflating them is what made the private posture feel like a security control rather than a launch-timing choice.
|
|
28
|
+
|
|
29
|
+
## Decision
|
|
30
|
+
|
|
31
|
+
**`@bongos/core` publishes as a public scoped package.**
|
|
32
|
+
|
|
33
|
+
- `buildPackageJson` sets `publishConfig.access: "public"`, and `publishArgs` passes `--access public`. Both, deliberately: the belt-and-braces of the restricted era, pointed the other way, so neither drifting alone can silently take the package back to private.
|
|
34
|
+
- **`.github/workflows/publish.yml` passes `--access public` too.** This is the site that actually releases — `package-core --publish` is the manual owner path. Flipping only `package-core.js` would have left every real release restricted while the code claimed otherwise: precisely the failure shape of task 1003870, where the workflow did not arm the gate its own header comment advertised. A test now reads the workflow and fails if `restricted` reappears anywhere in the lane.
|
|
35
|
+
- **The one-time registry flip is a separate, deliberate owner step:** `npm access set status=public @bongos/core`. `--access` on publish governs what *that publish* declares, and has historically been a no-op when re-publishing an already-private package. The package read `private` on the registry when this ADR was written, so the flag alone cannot be assumed sufficient — the flip is verified by a tokenless install from a clean `HOME`, not by the flag's presence.
|
|
36
|
+
- **Provenance stays off.** The *package* is public; the *source repository* is not. npm refuses to generate provenance from a private repo, so `NPM_CONFIG_PROVENANCE: 'false'` remains required until the source repository itself goes public. Turning it on before then fails every publish.
|
|
37
|
+
- **The npm path does not run ADR 0099's mirror pipeline, by design.** `package-core` performs its own redaction (476 docs redacted, 24 agent-docs stubbed, `package.json` synthesized). That is why gates 1–3 carry the whole safety burden here, and why they had to land first.
|
|
38
|
+
|
|
39
|
+
## Consequences
|
|
40
|
+
|
|
41
|
+
- `npm install @bongos/core` works from any machine with no credential. Co-tenant upgrades, the nightly sweep, cloud sessions and fresh scaffolds all stop needing a token they were never given.
|
|
42
|
+
- The sweep's `NPM_TOKEN` dependency becomes vestigial for reads. It should be removed from that path rather than left as a trap that appears to work.
|
|
43
|
+
- **[ADR 0258](0258-the-public-cli-is-a-generated-client-package-not-the-published-core.md) is largely redundant and should be revisited, not deleted reflexively.** `@cloudbongos/cli` is published, works, and has a deliberately narrow verb set; the question is whether a public core makes it a maintenance burden or a still-useful thin entry point.
|
|
44
|
+
- **This does not fix the 22 co-tenants.** Twenty of them pin `file:vendor/bongos-core-<v>.tgz` — a local path with no registry channel to follow. Public access is necessary but not sufficient; converting them is separate work.
|
|
45
|
+
- The source repository stays private. Public package ≠ public repo, and nothing here changes who can read the source repository.
|
|
46
|
+
- Going public is effectively **one-way**. A published tarball is mirrored and cached beyond our control; `npm unpublish` has a 72-hour window and does not reach mirrors. This is why the flip is an owner action and why gates 1–3 were non-negotiable prerequisites.
|
|
47
|
+
|
|
48
|
+
## Rejected
|
|
49
|
+
|
|
50
|
+
- **A rank-gated registry proxy** — the server holds the npm token, builders authenticate with their existing Bongos CLI session, and the server proxies to npmjs.org after a per-request rank check. This is the correct design *if the core must stay private*: nothing is handed out, revocation is instant and per-builder, and every fetch is auditable. It was rejected because it manages the problem that going public deletes, and because it would entrench the private posture the owner chose to leave.
|
|
51
|
+
- **Handing builders the npm read token, gated by rank.** Rank-gating the handout does not rank-gate the use: a token, once held, is held forever and can be passed on, and revocation means rotating it for everyone. It is access control in name only.
|
|
52
|
+
- **Keeping the vendored-tarball path as the answer.** It is what co-tenants do today, and it is exactly why twenty of them are frozen on the version current at their provisioning date.
|
|
53
|
+
- **Making the repository public in the same change.** A larger decision with its own review, and not required for any of the benefits above. Keeping them separate also keeps the provenance question honest.
|
package/docs/adr/README.md
CHANGED
|
@@ -374,3 +374,4 @@ This keeps the decision history honest and traceable.
|
|
|
374
374
|
| 0280 | [**The unattended lane may drive a co-tenant, because the roster entry IS the per-instance decision** ([task 1003843](https://cloudbongos.com/builders#/task/1003843) · goal 1000106; from [task 1003521](https://cloudbongos.com/builders#/task/1003521), which built co-tenant mode into `go-live.js` and deliberately left this caller alone). `subscribe.js` keeps a SECOND COPY of go-live's target contract, and it drifted: `GO_LIVE_REQUIRED` still listed `deployTimer` after 1003521 made it optional, so `goLiveTargetFor()` returned `missing: ['deployTimer']` for every provisioned co-tenant, and `rawTopologyFor()`'s fixed field list dropped `pinMode` before the target was built — the same stale-normalizer trap that function's own comment was written to fix, one field later. **1003521 deferred this as a policy judgement call; reading the fallback shows the premise was wrong.** `mapped.ok === false` never meant "skip this instance" — it meant "deploy it the WEAKER way": a bare `bongos upgrade`, no backup, no deploy-timer guard, and a pin COMMITTED AND PUSHED into the co-tenant's own repo, which is exactly what `pinMode` exists to prevent. The lane was already auto-deploying co-tenants; the only open question was whether it did so well. A THIRD defect sat behind the other two and would have turned the silent downgrade into a hard failure had only the first been fixed: the normalizer yields `deployTimer: null`, an explicit null SURVIVES `JSON.stringify` into `--target-json`, and go-live's `validateTarget` runs `isSafeToken(null)` on it and rejects the whole target. A FOURTH sat PAST the go-live call and is what separates "reachable" from "safe": after any successful upgrade the lane runs its own pin net, `commitPin()`, which commits the pin files and PUSHES them to `origin` — a net that predates `pinMode`. Under `local-commit` go-live leaves the tree clean so it no-ops by luck; under **`leave-dirty`** the pin is in the working tree BY DESIGN, so the net committed it and pushed it into the customer's own repo — the lane honouring `pinMode` right up to the moment it undid it. **Decision: the lane may drive a co-tenant and needs no new gate, because listing an instance in `config/update-subscriptions.json` already IS the explicit per-instance operator decision** — the roster ships empty, nothing is touched until an operator lists it, and the routine is autonomy-gated (`requiresAutonomy: true`, default OFF) on top of that; a second consent gate asks the same person the same question twice. So: `GO_LIVE_REQUIRED` now MIRRORS go-live's `REQUIRED_FIELDS`, with a test reading BOTH source files and failing on drift; `deployTimer` moves to the optional-field loop so a co-tenant OMITS the key rather than carrying a null; `rawTopologyFor` carries `pinMode`; `resolveGoLive` gains a `needsCoTenant` probe leg; and `goLiveEngineFor()` makes the decision ONCE for both the dry run and the apply path, which had re-derived it separately. The pin net now obeys the same `pinMode` go-live was given — `commitPin()` takes `push` (default true, so the platform instance is unchanged), `local-commit` commits without pushing, `leave-dirty` skips the net, the rollback tidy follows the same rule, and the "not durable" advice stops telling an operator to `git push` a repo we do not own; its refusal to touch a worktree carrying NON-pin changes is unchanged in every mode. **The probe is CONDITIONAL on purpose** — demanding `pinMode` of every target would strip the go-live path from any instance between the `--target-json` release and 1003521's, trading a live regression for a hypothetical one. Rejected: a second consent gate (an `allowCoTenant` flag); deferring to the unspecced fleet control plane ([task 1001948](https://cloudbongos.com/builders#/task/1001948)) — which does not leave co-tenants alone, it leaves them pushing pins into customer repos; dropping `deployTimer` from `GO_LIVE_REQUIRED` and nothing else (the fix as proposed — trades a silent downgrade for a hard rejection); probing unconditionally; reaching co-tenant mode WITHOUT teaching the durability net about it (nominal support that breaks its own promise seconds later); and teaching `loadSubscriptions()` about `pinMode` instead, since that normalizer ships in the control plane's vendored core that nobody bumps — the exact trap `rawTopologyFor` routes around.](0280-the-unattended-lane-may-drive-a-co-tenant-because-the-roster-is-the-decision.md) | core distribution / unattended deploy |
|
|
375
375
|
| 0281 | [**An instance’s identity is its own unix account and its own password-authed Postgres role** ([task 1003369](https://cloudbongos.com/builders#/task/1003369) · goal 1000094, audit ref `B3` of the 2026-08-29 security audit). [ADR 0111](0111-instance-hosting-provisioning-module.md) made standup automatic and [ADR 0128](0128-provisioning-runner-privilege-and-db-auth-model.md) settled how the RUNNER escalates; neither settled who the INSTANCE is once it runs, and the answer was that everybody was the same person. `serviceUnit` hardcoded `User=lars`, and **co-tenant is the default shape** — so on a shared box every instance’s process ran under one uid, and `pool.js` connected by **passwordless peer auth**. Two consequences, neither a bug in any route: a unix user may read `/proc/<pid>/environ` of its own processes, so every instance could read every sibling’s `GITHUB_APP_PRIVATE_KEY`, hub client secret and OAuth secret; and peer auth answers “which unix user are you”, so any code as `lars` could open a pool against **any** database on the box by naming it — including the control plane’s own `builders` and `builder_sessions`. The `0600` `web.env` and the per-instance DB were already correct: sound walls with one door, and every instance had the key. **Decision: each instance gets its own unix account and its own password-authed PG role, both derived from its slug, both created by the provision run.** The account is `bongos-<slug>`, hash-folded past utmp’s 32 chars — prefixed because a project may legally be called `postgres`, and hash-suffixed because two slugs sharing a 16-char prefix would otherwise fold onto ONE account, which is the shared identity being removed. Its home is `/var/lib/<user>` and deliberately **not** under `/home`, since the unit sets `ProtectHome=read-only` and the server writes `builder-secret.key` inside its config home on first boot — a `/home` account would make that `EROFS`. The unit gains `ProtectSystem=strict` / `ProtectHome=read-only` / `PrivateTmp=true` with `ReadWritePaths` as the whole allow-list; **the checkout the service runs is deliberately not writable by it**. The role is `LOGIN` + password, owns its own DB, and is NOT superuser/CREATEDB/CREATEROLE — with **`REVOKE CONNECT … FROM PUBLIC` as the half that actually isolates**, because PG grants `CONNECT` to `PUBLIC` by default and per-role passwords alone leave a sibling one peer-auth hop away. Grants run in a SECOND statement **after** the migrate, since the privileged migrate runs as `postgres` and owns every table it creates; `ALTER DEFAULT PRIVILEGES` aims the same grant forward. The password is minted per run and never stored — the same run sets it on the role and rewrites the `0600` env file, so **every provision rotates it** — and `pool.js` reads `PGUSER`/`PGPASSWORD` **explicitly** rather than via `pg`’s env defaults, because the whole defect was a connection whose identity nothing in the code stated. **Absence is the old behavior:** no password ⇒ no credential lines and no pool keys, so an instance provisioned before this keeps its peer auth and the rollout cannot brick a box. Two consequences worth the record. (1) **The generated SQL contains no double quote, and that is load-bearing** — every literal is dollar-quoted and every identifier goes through `format(%I)`, because these commands are embedded in the dedicated droplet’s cloud-init as `- [ bash, -lc, "…" ]`, a YAML DOUBLE-QUOTED scalar: a plain `"role"` identifier reads fine in `psql` and silently truncates the line. A test asserts the absence rather than leaving it to habit. (2) **A partial, stated honestly** — the account is added to the app-user group so it can read the checkout it runs from (without it the unit fails at exec with `EACCES`), so FILE-level co-tenant separation is not what this delivers; the process and database boundaries are. Rejected: a `DATABASE_URL` in `web.env` (needs no pool change, but trades a stated, assertable identity for a socket-path parse that is `pg-connection-string`’s business); per-role passwords WITHOUT the `PUBLIC` revoke (reads like isolation, and peer auth walks around it); `ProtectSystem=full`, or `strict` with the working directory writable (makes the code the service executes writable by the service, in a change whose subject is containment); keeping `User=lars` and isolating with a second mechanism (the shared uid IS the `/proc` vector); storing the password in `provisioning_instances` (a live credential in plaintext, to buy stability the rotation does not need).](0281-an-instance-identity-is-its-own-unix-account-and-pg-role.md) | tenant isolation / provisioning / DB auth |
|
|
376
376
|
| 0282 | [**A module that spends money for its callers DECLARES so, and the core refuses to enable it where anyone can sign up and nobody is on the hook** ([task 1003884](https://cloudbongos.com/builders#/task/1003884), owner decision 2026-09-13). A fire from `modules/agents` costs ~$0.90, `POST /agents/:name/invoke` answers **202** so the caller does not even wait, and the route is gated at `requireBuilder` and nothing else — deliberately, because a v1 agent is hard-read-only by plumbing. That bounds CAPABILITY, not SPEND, and `lib/fire-budget.js` says so in its own header: it is a blast radius, explicitly NOT a budget, and it defers the real cap to `agents_runs.cost_usd`. The cap did not exist. The exposure is three reasonable facts composed: a fire is expensive and async; invoke admits any **any-builder**; and with the join door `open`, any GitHub account IS one seconds after sign-up. **Decision: the MODULE declares `"spend": { "requiresPayer": true }` and the CORE refuses to enable it when the composed join door (ADR 0247 — the door, never the `joinability` knob alone) is `open` with no configured payer.** The declaration lives in the manifest because fitness Check 10 forbids the kernel from naming a discovered module key — a constraint that turns out to be the better design, since the next metered module inherits the guard by declaring one line rather than by someone remembering to widen a list. The refusal is at **config load**, so the routes never mount and the migrations never run: a per-request check would let the module mount, advertise its routes and read as enabled everywhere the UI looks, then refuse callers one at a time — a broken feature, not a closed door. It is applied AFTER config, env and default alike, because `<PREFIX>_MODULE_<KEY>` is exactly the knob a hurried deploy reaches for and a wall env could outrank is a suggestion. An UNREADABLE posture refuses too, inverting this file’s usual fail-to-unchanged default: "I could not tell whether strangers can sign up" has to mean "assume they can", and the cost of being wrong is one module resolving off. `spend_payer` (`none|project|builder`) joins `SETTINGS_VOCAB` on ADR 0241’s transport, and `agents_runs` gains `payer_kind` + `payer_builder_id` — **stored, not derived**, because the setting is a live knob and deriving would make last month’s bill change when an owner flips it. `none` is NOT a third payer: a run always has a real one and `none` attributes to the project, so a CLOSED-door instance is unchanged and `none` is fatal only where it is also dangerous. An EVENT fire has no caller, so it bills the project rather than nobody — otherwise the event path is the one unbounded route. The per-payer monthly ceiling (`lib/spend-ceiling.js`) is a SECOND wall that must never merge into the first: a rate ceiling cannot bound a bill (~$650/month/builder at 10 fires an hour) and a spend ceiling cannot bound a loop (it reads spend already landed). It records NOTHING — the ledger is the tally, and a second tally is the disagreeing answer fire-budget’s header refuses to become. It honours the finding of abandoned [task 1001393](https://cloudbongos.com/builders#/task/1001393), which caught the autonomy brake reading month-to-date spend from a DB it could not reach and **failing open**: every unknown refuses (no cap, non-finite cap, no payer, a read that threw, a sum absent or unparseable), answering **402** rather than fire-budget’s 429 so "the money is gone" and "too fast" stay distinguishable. Writing the absent-sum clause down was not paranoia — the first draft admitted the fire on a null sum because `Number(null)` is `0`, and the test for that clause is what caught it. The overshoot is STATED: in-flight fires are invisible to the read, bounded by concurrency × ~$0.90, and what bounds concurrency is fire-budget — each wall’s blind spot is the other’s window. The cost-needs-a-payer constraint is added `NOT VALID` so it binds every future write without failing the migration of an instance whose history predates the column. **What it does not do:** nobody is billed; `builder` attributes spend, it does not collect.](0282-a-module-that-spends-for-its-callers-declares-a-payer-and-the-core-refuses-it-without-one.md) | modules / spend attribution / agents |
|
|
377
|
+
| 0283 | [**The core publishes publicly, because access was never the thing protecting it** ([task 1003872](https://cloudbongos.com/builders#/task/1003872) · goal 1000090 — *Working area 4, Bongos Core distribution*; owner decision 2026-09-13, resolving [task 1003502](https://cloudbongos.com/builders#/task/1003502)). [ADR 0134](0134-private-first-npm-distribution.md) chose private-first and named "public at launch" as a later deliberate step; this is that step. The restricted posture had a cost that grew quietly because every symptom read as a different bug: a tokenless `npm install` returns a flat **E404**, indistinguishable from "no such package", so every co-tenant and downstream upgrade needed a credential to move at all; the nightly auto-upgrade sweep **died on 2026-08-25 and reported `failed=0`** (its unit passed no `NPM_TOKEN`, the env-fed `.npmrc` expanded to empty, and a skip is not a failure in that script's accounting, so nothing complained while the instance stopped receiving core updates); an Anthropic-hosted cloud session could not install the dependency at all; and a fresh scaffold's README step 1 IS `npm install`, which is also what installs the `bongos` command — so without a token an owner got no working instance rather than a stale one. [ADR 0258](0258-the-public-cli-is-a-generated-client-package-not-the-published-core.md)'s `@cloudbongos/cli`, require-closure delazification and all, exists ONLY because of this. **The decisive point is that access control was never what protected the artifact:** three gates decide what is IN the tarball — the in-build no-leak gate (branding literals + credential shapes, fail-closed, 2,624 files clean), the prior-instance leak scan (**fatal by default since [task 1003870](https://cloudbongos.com/builders#/task/1003870)**, `--no-leak-scan` the only escape), and the fixture confirmation of [task 1003871](https://cloudbongos.com/builders#/task/1003871) (all 14 credential-shape files checked against each provider's real key format: 11 too short to have ever been valid, 3 alphabet sequences or Amazon's own published doc example). `--access restricted` decides who may FETCH it; those three decide what is IN it, and conflating them is what made a launch-timing choice feel like a security control. **Decision: publish public — at all three sites.** `publishConfig.access` and `publishArgs` both say `public` (belt-and-braces of the restricted era pointed the other way, so neither drifting alone reverts it), **and so does `publish.yml`** — the site that ACTUALLY releases, `package-core --publish` being the manual owner path. Flipping only `package-core.js` would have left every real release restricted while the code claimed otherwise: exactly task 1003870's shape, where the workflow did not arm the gate its own header comment advertised. A test reads the workflow and fails if `restricted` reappears anywhere in the lane. **The one-time registry flip stays a separate owner step** (`npm access set status=public`): `--access` governs what THAT publish declares and has historically been a no-op when re-publishing an already-private package — the package read `private` on the registry when this was written — so it is verified by a tokenless install from a clean `HOME`, never by the flag's presence. **Provenance stays off:** the PACKAGE is public, the SOURCE REPO is not, and npm blocks provenance on the repo's visibility — turning it on before the source repository goes public fails every publish. The npm path deliberately does not run [ADR 0099](0099-delayed-redacted-mirror-export.md)'s mirror pipeline (`package-core` runs its own redaction: 476 docs redacted, 24 agent-docs stubbed), which is why gates 1–3 carry the whole safety burden and had to land first. **Going public is effectively one-way** — a published tarball is mirrored and cached beyond our control and `npm unpublish` has a 72-hour window that does not reach mirrors. **Does NOT fix the 22 co-tenants:** twenty pin `file:vendor/bongos-core-<v>.tgz`, a local path with no registry channel to follow, so public access is necessary but not sufficient. Rejected: a **rank-gated registry proxy** (server holds the token, builders authenticate with their existing Bongos session, per-request rank check — the RIGHT design if the core must stay private, rejected because it manages the problem going public deletes); **handing builders the read token gated by rank** (rank-gating the handout does not rank-gate the use — a token once held is held forever and revocation means rotating it for everyone: access control in name only); keeping the vendored tarball as the answer (it is why twenty co-tenants are frozen at their provisioning date); making the REPO public in the same change (a larger decision, not required for any of this, and separating them keeps the provenance question honest).](0283-the-core-publishes-publicly-because-access-was-never-the-thing-protecting-it.md) | core distribution / npm access |
|
|
@@ -1944,5 +1944,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1944
1944
|
landed since 1.19.733 with no explicit bump. run 34767120578. (task 1002620)
|
|
1945
1945
|
1.19.735 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1946
1946
|
landed since 1.19.734 with no explicit bump. run 34768136326. (task 1002620)
|
|
1947
|
+
1.19.736 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
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)
|
|
1947
1951
|
---------------------------------------------------------------------------
|
|
1948
1952
|
```
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **This is one leg of three.** For the whole path from a change on core `main` to a live instance — and for when publishing to npm is *not* what moves an instance — start at [the core release pipeline](core-release-pipeline.md).
|
|
4
4
|
>
|
|
5
|
-
>
|
|
5
|
+
> **⚠️ The private half of this recipe is HISTORY as of 2026-09-13.** `@bongos/core` publishes **public** — [ADR 0283](../adr/0283-the-core-publishes-publicly-because-access-was-never-the-thing-protecting-it.md) ([task 1003872](https://cloudbongos.com/builders#/task/1003872)) supersedes [ADR 0134](../adr/0134-private-first-npm-distribution.md)'s core half. A tokenless `npm install @bongos/core` now works, so **anything below telling you to set `NPM_TOKEN` for a READ is obsolete**; a token is still needed to *publish*. The build, redaction, manifest-pin and no-leak-gate mechanics are unchanged and still accurate. The daily command is `bongos` regardless of package name.
|
|
6
6
|
>
|
|
7
7
|
> **`@bongos/client` is no longer part of this.** It was published once (2026-07-07, 0.0.2), never updated while the API moved 500+ core versions, and had **zero** consumers the whole time — every consumer vendors a copy instead. The owner removed it from npm on 2026-09-09; ADR 0134's client half is superseded ([task 1003742](https://cloudbongos.com/builders#/task/1003742)). The generated client still exists and still regenerates — it is simply not a package.
|
|
8
8
|
>
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.737",
|
|
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.737",
|
|
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.737",
|
|
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",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"node": ">=22"
|
|
22
22
|
},
|
|
23
23
|
"publishConfig": {
|
|
24
|
-
"access": "
|
|
24
|
+
"access": "public"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"express": "^4.21.2",
|
|
@@ -68,10 +68,11 @@
|
|
|
68
68
|
// dist/bongos-core-<version>/ the redacted core tree (inspectable)
|
|
69
69
|
// dist/bongos-core-<version>.tgz the installable unit
|
|
70
70
|
// dist/bongos-core-<version>.manifest.json the PIN (tree hash + per-file hashes)
|
|
71
|
-
// --publish after building, `npm publish` the .tgz as a
|
|
72
|
-
// package (--access
|
|
73
|
-
// (task
|
|
74
|
-
//
|
|
71
|
+
// --publish after building, `npm publish` the .tgz as a PUBLIC scoped
|
|
72
|
+
// package (--access public). DRY-RUN unless --live is added
|
|
73
|
+
// (task 1003872 / ADR 0283, superseding ADR 0134's restricted
|
|
74
|
+
// posture). Needs `npm login` as a member of the @bongos org;
|
|
75
|
+
// this script holds no token.
|
|
75
76
|
|
|
76
77
|
const fs = require('node:fs');
|
|
77
78
|
const os = require('node:os');
|
|
@@ -306,13 +307,18 @@ function buildPackageJson({ version, sourceDeps, sourceOverrides }) {
|
|
|
306
307
|
'./module-seams': './src/module-seams.js',
|
|
307
308
|
},
|
|
308
309
|
engines: { node: '>=22' },
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
|
|
310
|
+
// Distribution posture: PUBLIC (task 1003872, ADR 0283 — supersedes ADR 0134's
|
|
311
|
+
// pre-launch `restricted`). The private posture existed so a pre-launch artifact
|
|
312
|
+
// could not leak; what it actually cost was that a tokenless `npm install` 404s,
|
|
313
|
+
// which broke every co-tenant upgrade, the nightly auto-upgrade sweep (silently,
|
|
314
|
+
// since 2026-08-25), and every newly scaffolded instance.
|
|
315
|
+
//
|
|
316
|
+
// The safety this replaces is NOT "nobody can read it" — it is the three gates
|
|
317
|
+
// that now run on every build: the branding/credential no-leak gate, the
|
|
318
|
+
// prior-instance leak scan (fatal by default since task 1003870), and the
|
|
319
|
+
// fixture confirmation of task 1003871. Those decide what is IN the tarball;
|
|
320
|
+
// access decides who may fetch it. Do not re-conflate them.
|
|
321
|
+
publishConfig: { access: 'public' },
|
|
316
322
|
dependencies,
|
|
317
323
|
};
|
|
318
324
|
if (sourceOverrides && sourceOverrides.undici) pkg.overrides = { undici: sourceOverrides.undici };
|
|
@@ -554,12 +560,20 @@ function arg(name, fallback = null) {
|
|
|
554
560
|
// npm is `npm.cmd` on Windows and can't be execFile'd bare; unix uses `npm`.
|
|
555
561
|
const NPM_BIN = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
556
562
|
|
|
557
|
-
// Pure — the exact `npm publish` argv for the built core tarball.
|
|
558
|
-
//
|
|
559
|
-
// pass `--access
|
|
560
|
-
//
|
|
563
|
+
// Pure — the exact `npm publish` argv for the built core tarball. The package
|
|
564
|
+
// publishes PUBLIC via publishConfig.access:"public" (buildPackageJson); we ALSO
|
|
565
|
+
// pass `--access public` so the posture is stated at the call site and a stray
|
|
566
|
+
// `publishConfig` edit can't silently take it back to restricted — the same
|
|
567
|
+
// belt-and-braces the restricted era used, pointed the other way (task 1003872).
|
|
568
|
+
//
|
|
569
|
+
// NOTE for whoever reads this after a failed rollout: `--access` on publish governs
|
|
570
|
+
// what THIS publish declares. A package that is already `private` on the registry
|
|
571
|
+
// also needs the one-time, registry-side `npm access set status=public @bongos/core`
|
|
572
|
+
// — the flag alone has historically been a no-op on re-publish. ADR 0283 records
|
|
573
|
+
// that flip as a separate, deliberate owner step for exactly that reason.
|
|
574
|
+
// `--dry-run` unless `live` (the permanent owner step).
|
|
561
575
|
function publishArgs(tgzPath, live) {
|
|
562
|
-
const a = ['publish', tgzPath, '--access', '
|
|
576
|
+
const a = ['publish', tgzPath, '--access', 'public'];
|
|
563
577
|
if (!live) a.push('--dry-run');
|
|
564
578
|
return a;
|
|
565
579
|
}
|
|
@@ -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
|
+
};
|
|
@@ -15,6 +15,10 @@ const fs = require('node:fs');
|
|
|
15
15
|
const os = require('node:os');
|
|
16
16
|
const path = require('node:path');
|
|
17
17
|
const { CONFIG, REPO_ROOT, coreVersionSafe, hasNpmToken, provisionerBotEmail, registryCoreRequested } = require('./provision-config.js');
|
|
18
|
+
// The repo-identity + owner-credential leaf (task 1003081). Carved OUT of this file so
|
|
19
|
+
// upgrade.js can reach these three without pulling provision*.js into the merge
|
|
20
|
+
// resolver's executed closure; re-exported below so every existing caller is unchanged.
|
|
21
|
+
const { instanceRepoRemote, parseTargetRef, resolveOwnerGithubToken } = require('./provision-repo-auth.js');
|
|
18
22
|
|
|
19
23
|
function dbName(inst) { return inst.db_name || inst.slug; }
|
|
20
24
|
|
|
@@ -288,25 +292,10 @@ function dbRoleGrantsCmd(inst, { privileged = false } = {}) {
|
|
|
288
292
|
// THROW if the instance repo wasn't already cloned. These generate + run the scaffold
|
|
289
293
|
// that clones an EMPTY target_ref and turns it into an installable, bootable instance.
|
|
290
294
|
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
.replace(/^git@[^:]+:/, '')
|
|
296
|
-
.replace(/^https?:\/\/[^/]+\//, '')
|
|
297
|
-
.match(/^([A-Za-z0-9][A-Za-z0-9_.-]*)\/([A-Za-z0-9][A-Za-z0-9_.-]*?)(?:\.git)?$/);
|
|
298
|
-
return m ? { owner: m[1], name: m[2] } : null;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// The git remote for an instance's OWN repo. Reuses the SAME SSH deploy alias the
|
|
302
|
-
// control-plane checkout uses (git@<alias>:<owner>/<repo>.git) — NO secret in the
|
|
303
|
-
// command (SSH-key auth, nothing to leak into journald), consistent with how the
|
|
304
|
-
// platform repo is wired. Standing up a new instance repo therefore needs only that
|
|
305
|
-
// the control plane's deploy key has WRITE access to that repo (ADR 0125). Alias is
|
|
306
|
-
// overridable via PROVISION_GIT_SSH_ALIAS.
|
|
307
|
-
function instanceRepoRemote(repo, { sshAlias = process.env.PROVISION_GIT_SSH_ALIAS || 'github-deploy' } = {}) {
|
|
308
|
-
return `git@${sshAlias}:${repo.owner}/${repo.name}.git`;
|
|
309
|
-
}
|
|
295
|
+
// parseTargetRef + instanceRepoRemote (and resolveOwnerGithubToken, below the scaffold)
|
|
296
|
+
// now live in the ./provision-repo-auth.js leaf and are re-exported unchanged from here —
|
|
297
|
+
// upgrade.js needs them and is inside the merge resolver's executed closure, which a require of
|
|
298
|
+
// this file would have widened by five (task 1003081; see that module's header).
|
|
310
299
|
|
|
311
300
|
// A minimal, VALID `bongos init` spec (init.validateSpec greenfield) from the
|
|
312
301
|
// provisioning row + the owner's github login. The scaffold produces a bootable,
|
|
@@ -711,24 +700,6 @@ async function scaffoldStandaloneRepo(inst, deps) {
|
|
|
711
700
|
|
|
712
701
|
// ── private-repo deploy key (ADR 0176, task 1002735; walk finding N4) ────────────
|
|
713
702
|
|
|
714
|
-
// The instance owner's stored `repo`-scoped OAuth token (ADR 0155), or null (none
|
|
715
|
-
// stored, org-owned repo, secret-box off). Shared by the scaffold push, the
|
|
716
|
-
// private-repo deploy-key wiring, and the teardown revoke. `deps.getGithubToken` is
|
|
717
|
-
// injectable for tests; production resolves it lazily against the core db slice
|
|
718
|
-
// with the runner's pool.
|
|
719
|
-
async function resolveOwnerGithubToken(inst, deps, { tag = '[repo-auth]' } = {}) {
|
|
720
|
-
if (!inst.owner_builder_id) return null;
|
|
721
|
-
try {
|
|
722
|
-
const fetchTok = deps.getGithubToken
|
|
723
|
-
|| ((bid) => require(path.join(REPO_ROOT, 'src/bongos/db')).getGithubToken({ builderId: bid, p: deps.db }));
|
|
724
|
-
const r = await fetchTok(inst.owner_builder_id);
|
|
725
|
-
return r && r.token ? r.token : null;
|
|
726
|
-
} catch (e) {
|
|
727
|
-
(deps.log || (() => {}))(` ${tag} owner OAuth token lookup failed (${e && e.message})`);
|
|
728
|
-
return null;
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
|
|
732
703
|
function deployKeyTitle(inst) { return `cloudbongos-deploy-${inst.slug}`; }
|
|
733
704
|
// Beside the checkout's own metadata: untracked by definition, survives the deploy's
|
|
734
705
|
// `git reset --hard`, and dies with the checkout rather than accreting in ~/.ssh.
|