@bongos/core 1.19.576 → 1.19.578
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 +37 -17
- package/docs/adr/0258-the-public-cli-is-a-generated-client-package-not-the-published-core.md +140 -0
- package/docs/adr/README.md +1 -0
- package/docs/file-map.md +1 -0
- package/docs/module-api-changelog.md +4 -0
- package/modules/public-landing/public/projects.html +94 -59
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/box-connect-lib.js +1 -1
- package/scripts/gds/build-cli-package.js +517 -0
- package/src/module-api.js +1 -1
- package/tests/cli_package.mjs +217 -0
- package/tests/manage_manifest_shared_read.mjs +205 -0
- package/tests/projects_hub.mjs +31 -4
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.578",
|
|
6
|
+
"core_contract": "1.19.578",
|
|
7
|
+
"source_commit": "a3f42dff6ab98712306ee86eaedf8191cafa3340",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-07T17:15:54.070Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 451,
|
|
13
13
|
"agent_docs_stubbed": 24,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2047,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2522,
|
|
20
|
+
"tree_sha256": "96be19d76442ed1b3e663525652a70c6ef60434e80d4ad907df8e294be5f38fe",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -1804,10 +1804,15 @@
|
|
|
1804
1804
|
"mode": "0000644",
|
|
1805
1805
|
"sha256": "5ebfaa564a125f2c5bb0b391b8ff01914f147c15c9ef3f4caa91bafa8bd2e8ed"
|
|
1806
1806
|
},
|
|
1807
|
+
{
|
|
1808
|
+
"path": "docs/adr/0258-the-public-cli-is-a-generated-client-package-not-the-published-core.md",
|
|
1809
|
+
"mode": "0000644",
|
|
1810
|
+
"sha256": "28f3644d3d9dcd2985b7e3f42cf2135af86203eee35b6e509d1ff98f630f1228"
|
|
1811
|
+
},
|
|
1807
1812
|
{
|
|
1808
1813
|
"path": "docs/adr/README.md",
|
|
1809
1814
|
"mode": "0000644",
|
|
1810
|
-
"sha256": "
|
|
1815
|
+
"sha256": "30bb35e65ebd2dc8c5eb0aad7e30d5875d0c764265f398547abd545b62ee7d7c"
|
|
1811
1816
|
},
|
|
1812
1817
|
{
|
|
1813
1818
|
"path": "docs/api-reference.md",
|
|
@@ -2682,7 +2687,7 @@
|
|
|
2682
2687
|
{
|
|
2683
2688
|
"path": "docs/file-map.md",
|
|
2684
2689
|
"mode": "0000644",
|
|
2685
|
-
"sha256": "
|
|
2690
|
+
"sha256": "f43e8946ecb5c96dc7af0bcee49010245df577eee62381701d2aef1c67455662"
|
|
2686
2691
|
},
|
|
2687
2692
|
{
|
|
2688
2693
|
"path": "docs/handoff-template.md",
|
|
@@ -2692,7 +2697,7 @@
|
|
|
2692
2697
|
{
|
|
2693
2698
|
"path": "docs/module-api-changelog.md",
|
|
2694
2699
|
"mode": "0000644",
|
|
2695
|
-
"sha256": "
|
|
2700
|
+
"sha256": "072128c45a7036c0d01bc029a1267bf34ff7bd175226e83d691b8001e4a8eefa"
|
|
2696
2701
|
},
|
|
2697
2702
|
{
|
|
2698
2703
|
"path": "docs/modules-contract.md",
|
|
@@ -6807,7 +6812,7 @@
|
|
|
6807
6812
|
{
|
|
6808
6813
|
"path": "modules/public-landing/public/projects.html",
|
|
6809
6814
|
"mode": "0000644",
|
|
6810
|
-
"sha256": "
|
|
6815
|
+
"sha256": "22201cd63fde334156b40954707d836f8c65a8380a3f346fe7c77abb03ed3b51"
|
|
6811
6816
|
},
|
|
6812
6817
|
{
|
|
6813
6818
|
"path": "modules/public-landing/public/projects.probes.json",
|
|
@@ -7552,12 +7557,12 @@
|
|
|
7552
7557
|
{
|
|
7553
7558
|
"path": "package-lock.json",
|
|
7554
7559
|
"mode": "0000644",
|
|
7555
|
-
"sha256": "
|
|
7560
|
+
"sha256": "e5249bc21a0842ce0e47203e1b7052aa146161702db57b1c50740d409a7347b2"
|
|
7556
7561
|
},
|
|
7557
7562
|
{
|
|
7558
7563
|
"path": "package.json",
|
|
7559
7564
|
"mode": "0000644",
|
|
7560
|
-
"sha256": "
|
|
7565
|
+
"sha256": "d328d1b9a42a9003642815e8a17cc14f648d53a996c2c6db92a8d1250c570ea8"
|
|
7561
7566
|
},
|
|
7562
7567
|
{
|
|
7563
7568
|
"path": "public-docs/index.html",
|
|
@@ -7727,7 +7732,7 @@
|
|
|
7727
7732
|
{
|
|
7728
7733
|
"path": "scripts/gds/box-connect-lib.js",
|
|
7729
7734
|
"mode": "0000644",
|
|
7730
|
-
"sha256": "
|
|
7735
|
+
"sha256": "56c6f3eae23d427d9c74763521739bae5b704bcd02ad234e43a5434645657519"
|
|
7731
7736
|
},
|
|
7732
7737
|
{
|
|
7733
7738
|
"path": "scripts/gds/box-infra.js",
|
|
@@ -7754,6 +7759,11 @@
|
|
|
7754
7759
|
"mode": "0000644",
|
|
7755
7760
|
"sha256": "6a696f9291da2d071d8a8aa0f92bf0be08445569a73196f9204f696bf6cb6cb5"
|
|
7756
7761
|
},
|
|
7762
|
+
{
|
|
7763
|
+
"path": "scripts/gds/build-cli-package.js",
|
|
7764
|
+
"mode": "0000644",
|
|
7765
|
+
"sha256": "4b2428615e3c81c14a3b3144eca35fcb2c4523c4debef0f7d4596e5934857a6b"
|
|
7766
|
+
},
|
|
7757
7767
|
{
|
|
7758
7768
|
"path": "scripts/gds/bump-version.js",
|
|
7759
7769
|
"mode": "0000644",
|
|
@@ -9257,7 +9267,7 @@
|
|
|
9257
9267
|
{
|
|
9258
9268
|
"path": "src/module-api.js",
|
|
9259
9269
|
"mode": "0000644",
|
|
9260
|
-
"sha256": "
|
|
9270
|
+
"sha256": "71207830c631f2604b55d9e2e6a2db08b0d129499249c4db009c00e871fc2fd7"
|
|
9261
9271
|
},
|
|
9262
9272
|
{
|
|
9263
9273
|
"path": "src/module-loader/catalog.js",
|
|
@@ -9839,6 +9849,11 @@
|
|
|
9839
9849
|
"mode": "0000644",
|
|
9840
9850
|
"sha256": "d0ea5b273c24527190f6ed01555ddb6b022a6ea94f3efcc7bdbd17f834c7d2ad"
|
|
9841
9851
|
},
|
|
9852
|
+
{
|
|
9853
|
+
"path": "tests/cli_package.mjs",
|
|
9854
|
+
"mode": "0000644",
|
|
9855
|
+
"sha256": "a57516ed9e904cf1c86fe99688415f834dcae1a19f01b22e986f199fa5473258"
|
|
9856
|
+
},
|
|
9842
9857
|
{
|
|
9843
9858
|
"path": "tests/cli_token_reissue.mjs",
|
|
9844
9859
|
"mode": "0000644",
|
|
@@ -11164,6 +11179,11 @@
|
|
|
11164
11179
|
"mode": "0000644",
|
|
11165
11180
|
"sha256": "9a1813d6036886ea07d1563e71a466e66c28554b75b22605464c795fd9fa2ebb"
|
|
11166
11181
|
},
|
|
11182
|
+
{
|
|
11183
|
+
"path": "tests/manage_manifest_shared_read.mjs",
|
|
11184
|
+
"mode": "0000644",
|
|
11185
|
+
"sha256": "419234978bb7688f83ec14bb2db7fb4be1eec99205595795c705befe5620b89c"
|
|
11186
|
+
},
|
|
11167
11187
|
{
|
|
11168
11188
|
"path": "tests/manage_settings_shared_read.mjs",
|
|
11169
11189
|
"mode": "0000644",
|
|
@@ -11627,7 +11647,7 @@
|
|
|
11627
11647
|
{
|
|
11628
11648
|
"path": "tests/projects_hub.mjs",
|
|
11629
11649
|
"mode": "0000644",
|
|
11630
|
-
"sha256": "
|
|
11650
|
+
"sha256": "ec536c7bcfe2c9ea29d0df8a0dcbbb5c2764b4f60e0d14218aa0a10bf6afb469"
|
|
11631
11651
|
},
|
|
11632
11652
|
{
|
|
11633
11653
|
"path": "tests/projects_hub_module_picker.mjs",
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# ADR 0258 — The public CLI is a generated client-only package, and its file list is proven by running it
|
|
2
|
+
|
|
3
|
+
- **Status:** accepted
|
|
4
|
+
- **Date:** 2026-09-07
|
|
5
|
+
- **Task:** [task 1003679](https://cloudbongos.com/builders#/task/1003679) (BONGOS-V1, goal 1000054 — *A newcomer can build without the UI*)
|
|
6
|
+
- **Deciders:** Claude, under the Archon's standing scope
|
|
7
|
+
- **Related:** [ADR 0083](<redacted>.md) (a module may import only `src/module-api.js` — the reason a static closure cannot answer this question) · [ADR 0099](<redacted>.md) (the lagged, redacted public mirror — still dormant) · [ADR 0108](<redacted>.md) (the core ships as a private versioned dependency) · [ADR 0118](<redacted>.md) (the generated client this package vendors) · [ADR 0161](<redacted>.md) (why the core's version moves many times a day)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Context
|
|
12
|
+
|
|
13
|
+
The core ships as `@bongos/core`, which is **private**. That is deliberate (ADR 0108), and it
|
|
14
|
+
has an unintended consequence at the front door: a newcomer with no credential can install
|
|
15
|
+
nothing. There is no terminal path into an instance at all, so the web hall is the only way in.
|
|
16
|
+
|
|
17
|
+
The owner put it plainly: *"how am I supposed to easily take on tasks as a new builder? I should
|
|
18
|
+
be able to do everything without the UI."*
|
|
19
|
+
|
|
20
|
+
An open task, 1002025, proposed the obvious fix — publish the core to npm. That is the wrong
|
|
21
|
+
instrument on two counts. It publishes the **server**: routes, auth middleware, pool, migrations,
|
|
22
|
+
provisioning. And it bypasses ADR 0099's redaction pipeline, which is the mechanism that is
|
|
23
|
+
supposed to decide what leaves this repo and does not run until ~2026-11. The premise was also
|
|
24
|
+
stale (it named `@cloudbongos/core`; the package is `@bongos/core`).
|
|
25
|
+
|
|
26
|
+
Two refactors landed the night before this decision and are what make a narrower answer possible:
|
|
27
|
+
[task 1003676](https://cloudbongos.com/builders#/task/1003676) cut the one edge that dragged the
|
|
28
|
+
server into the CLI, and [task 1003677](https://cloudbongos.com/builders#/task/1003677) made
|
|
29
|
+
`src/module-api.js` resolve its kernel capabilities on read rather than on import.
|
|
30
|
+
|
|
31
|
+
## Decision
|
|
32
|
+
|
|
33
|
+
**Generate a separate, public, client-only package — `@cloudbongos/cli` — from the core, and do
|
|
34
|
+
not publish the core.** `scripts/gds/build-cli-package.js` emits it; `tests/cli_package.mjs`
|
|
35
|
+
proves it.
|
|
36
|
+
|
|
37
|
+
Four parts of this are load-bearing.
|
|
38
|
+
|
|
39
|
+
### 1. The name is scoped because the org already exists
|
|
40
|
+
|
|
41
|
+
The owner holds the npm org `cloudbongos`. On npm, org names and unscoped package names share one
|
|
42
|
+
namespace, so the bare name `cloudbongos` is **not publishable precisely because that org
|
|
43
|
+
exists** — npm reports it as invalid, which reads like the name is taken when in fact the owner
|
|
44
|
+
owns it. A public scoped package is free and `npx @cloudbongos/cli` is not a worse experience
|
|
45
|
+
than `npx cloudbongos`. (Registry checked 2026-09-07: `cloudbongos`, `@cloudbongos/cli` and
|
|
46
|
+
`@cloudbongos/core` all 404, search total 0.)
|
|
47
|
+
|
|
48
|
+
Related and worth writing down because it wasted the owner's time: **a package name cannot be
|
|
49
|
+
created or reserved on the npm website.** The name comes into existence on first `npm publish`.
|
|
50
|
+
Every field on the org settings pages operates on packages that already exist, which is why
|
|
51
|
+
"Add Existing Package" answered `Forbidden` for a package nobody had published.
|
|
52
|
+
|
|
53
|
+
### 2. The file list is DECLARED, and proven by behaviour — not computed
|
|
54
|
+
|
|
55
|
+
The tempting approach is to derive the package from a static require-closure of the CLI entry
|
|
56
|
+
points. **That instrument does not work here, and cannot be made to.** `src/module-api.js` is the
|
|
57
|
+
published module doorway (ADR 0083): a module may import *only* it, so any script that touches a
|
|
58
|
+
module file statically reaches the doorway, and the doorway by design *names* every kernel
|
|
59
|
+
capability. A static walk therefore sees the whole server — 33 `src/` files from
|
|
60
|
+
`scripts/gds/start.js` alone — even though every one of those names is behind a lazy getter that
|
|
61
|
+
never resolves. Measured at runtime the same set is **one** `src/bongos/` file
|
|
62
|
+
(`api-prefix.js`, which imports nothing).
|
|
63
|
+
|
|
64
|
+
This is the same trap as task 1003677's original done-when ("zero `src/bongos` in the
|
|
65
|
+
require-closure"), which was unsatisfiable for exactly this reason: a getter still *contains* the
|
|
66
|
+
require text.
|
|
67
|
+
|
|
68
|
+
So `FILES` in the build script is an explicit, grouped, auditable list, and the test packs the
|
|
69
|
+
tarball, installs it into an empty directory **with no repo present**, and runs every advertised
|
|
70
|
+
verb. A verb may fail for want of a session or a network; it may not fail because a file is
|
|
71
|
+
missing. That test is the only thing that can prove the list complete, and it earned its keep
|
|
72
|
+
immediately — the first build shipped without `clients/bongos-client/index.mjs`, which
|
|
73
|
+
`cli-lib.js` reaches by a dynamic `import()` that no `require()`-based reasoning would ever see.
|
|
74
|
+
The build now refuses any relative `import()` target the manifest omits, and the generated
|
|
75
|
+
`package.json` `files` array is derived from the manifest rather than hand-listed (the hand-listed
|
|
76
|
+
one had already dropped `clients/`, producing a tarball that installed cleanly and died on first
|
|
77
|
+
use).
|
|
78
|
+
|
|
79
|
+
### 3. `claim` and `ship` are absent, and the CLI says why
|
|
80
|
+
|
|
81
|
+
Writing code needs a real checkout and a worktree, so `claim`, `ship`, `dev`, `serve`, `module`,
|
|
82
|
+
`upgrade`, `onboard`, `doctor`, `exec` and `package-core` are not in the public CLI. A newcomer
|
|
83
|
+
who types one gets the reason and the next step — `bongos shell` opens a terminal on a cloud dev
|
|
84
|
+
box that already has the full CLI — rather than `unknown command`. A bare unknown-command error
|
|
85
|
+
teaches nothing, which is the precise failure this whole task exists to fix, so it would be
|
|
86
|
+
perverse to reintroduce it at the boundary.
|
|
87
|
+
|
|
88
|
+
The journey the package supports end to end is: `bongos login <instance>` → `bongos start` →
|
|
89
|
+
`bongos shell`. Steps one and two are what had no terminal path at all.
|
|
90
|
+
|
|
91
|
+
`onboard` is excluded for a second reason: it drags ten provisioning files that talk to
|
|
92
|
+
DigitalOcean and GitHub. It is owner tooling, and it is the widest redaction surface in the
|
|
93
|
+
candidate set.
|
|
94
|
+
|
|
95
|
+
### 4. A redaction gate runs on every build, and it fails closed
|
|
96
|
+
|
|
97
|
+
The package is public, so the build scans the emitted tree and **refuses to emit** on a hit:
|
|
98
|
+
non-loopback IPv4 (RFC 5737 documentation ranges and link-local exempted — those can never name
|
|
99
|
+
real infrastructure, so they are the correct thing to write in an example), GitHub/npm token
|
|
100
|
+
shapes, private-key blocks, AWS keys, plus every domain and owner login it can read out of the
|
|
101
|
+
instance's own `config/branding.json`.
|
|
102
|
+
|
|
103
|
+
The needles come from host config rather than a list in the core, so the core carries no instance
|
|
104
|
+
identity (the ADR 0062 §7 split). A neutral core has no `config/branding.json`, so that half is
|
|
105
|
+
inert there — which is correct: the gate exists to stop someone building this package from an
|
|
106
|
+
*instance* checkout. `cloudbongos.com` is allowlisted as public by design, the way
|
|
107
|
+
`registry.npmjs.org` is baked into npm.
|
|
108
|
+
|
|
109
|
+
The generated `package.json` carries **no `repository` field**: the core repo is private, so a
|
|
110
|
+
repository URL would 404 for every user *and* put the owner's login into a public artifact for no
|
|
111
|
+
benefit. It gains one when ADR 0099's mirror lands.
|
|
112
|
+
|
|
113
|
+
## Consequences
|
|
114
|
+
|
|
115
|
+
- 31 files, ~450 KB, 13 verbs, **one** runtime dependency (`undici`). Small enough to audit by
|
|
116
|
+
reading, which was the point.
|
|
117
|
+
- The package's version is independent of the core's. The core moves many times a day under
|
|
118
|
+
ADR 0161; a public CLI's contract should not.
|
|
119
|
+
- The generated tree is gitignored (`dist/`). It is rebuilt, never committed.
|
|
120
|
+
- **Publishing is the owner's action, not a builder's.** It is the owner's npm account, and a
|
|
121
|
+
public package name cannot be un-taken. The build stops at a packed tarball and hands over one
|
|
122
|
+
`npm publish --access public`.
|
|
123
|
+
- `@bongos/client` stays private and is **vendored** as a single file rather than declared as a
|
|
124
|
+
dependency — a public package that depended on a private one would be uninstallable for
|
|
125
|
+
everyone.
|
|
126
|
+
- Task 1002025 is superseded and should be closed against this ADR rather than done.
|
|
127
|
+
|
|
128
|
+
## Rejected
|
|
129
|
+
|
|
130
|
+
- **Publishing the core** (task 1002025 as written) — ships the server and bypasses ADR 0099.
|
|
131
|
+
- **Deriving the package from a static require-closure** — structurally impossible past the
|
|
132
|
+
module doorway; see §2.
|
|
133
|
+
- **Waiting for the ADR 0099 mirror** — it is dormant until ~2026-11 and the front door is broken
|
|
134
|
+
now. The two are independent: this package is a build product, not a source release.
|
|
135
|
+
- **An unscoped `cloudbongos` package** — unpublishable while the owner's org of that name exists.
|
|
136
|
+
- **Depending on `@bongos/client`** — private, so the public package would not install.
|
|
137
|
+
- **Including `claim`/`ship` in a degraded form** — a half-working claim that cannot materialize
|
|
138
|
+
into a checkout is worse than one that explains where to go.
|
|
139
|
+
- **Shipping all of `src/`** to make lazy getters safe — reintroduces the server surface and the
|
|
140
|
+
redaction problem the narrow list exists to avoid.
|
package/docs/adr/README.md
CHANGED
|
@@ -349,3 +349,4 @@ This keeps the decision history honest and traceable.
|
|
|
349
349
|
|
|
350
350
|
> ⚠️ **Numbering collisions are now machine-enforced, not narrated.** Twenty numbers are shared by two ADRs each — assigned in parallel sessions before anything checked. The files keep their filenames (renumbering would break every existing citation, and a number once assigned is never reused), and this table disambiguates each pair as `NNNN-a` / `NNNN-b`. The authoritative list is `LEGACY_DUPLICATE_ADRS` in [`scripts/gds/adr-namespace.js`](../../scripts/gds/adr-namespace.js), frozen by exact filename: a **new** collision — or a third file joining a legacy number, or a rename of either half — hard-fails the `unit` CI gate, as does an index row that points at no file or an ADR with no row. The list may only shrink. This paragraph used to narrate the collisions one by one and had fallen eight behind reality; see [ADR 0195](<redacted>.md) for why the check exists and why the pairs are grandfathered rather than renumbered.
|
|
351
351
|
| 0257 | [**Auth resolves before the hall mounts anything, and a widget's boot read may never navigate** ([task 1003673](https://cloudbongos.com/builders#/task/1003673) · goal 1000063 — *The front door*). An invite-only instance could not admit its FIRST builder, and the symptom lied about where the fault was: an owner saw an empty Access-requests queue and no approve button, because **signing in does not file a request** — only the landing's *Request access* form does, and that form was unreachable. A signed-out visitor to `/builders` was bounced to GitHub, refused as a first-timer ("request access first"), and pointed back at `/builders` to be bounced again. Reproduced against `main`, not just an old pin. `builders.js` was already careful — `/me` is read `softAuth` and a 401 there renders the landing — but `DOMContentLoaded` called `mountHallWidgets()` FIRST, synchronously, and `goals.js`'s mount-time read of `/goals` goes through a `getJSON` with no `softAuth`, which answers 401 by NAVIGATING. The widget's read raced `renderLanding()` and won. **Decision: the front door settles which page this is before anything else runs** — `boot()` reads `/me` (soft), then either renders the landing and stops (nothing mounts, nothing else fetches) or mounts the hall, handing the resolved payload to `loadAll()` so the page still asks once. Widgets are member surfaces (`renderLanding` hid them all after the fact anyway), so not mounting them while signed out removes the CLASS rather than the one instance of it that was found; **a widget's own boot read may never navigate** is the belt beside it (`goals.js` reads soft and renders nothing without a session). A 401 during boot is not an instruction to go and sign in — only a user action is. `tests/hall_landing_boot.mjs` executes the real `builders.js` in a DOM stub and was confirmed to FAIL against the pre-fix file. Rejected: patching `goals.js` alone (it was merely first); making the 401 redirect soft everywhere (an expired session mid-visit SHOULD be sent to sign in — the line is boot vs user action); open enrollment as the cure (removes the gate [ADR 0050](<redacted>.md) chose deliberately instead of repairing the door).](<redacted>.md) | hall-ui / admission / front door |
|
|
352
|
+
| 0258 | [**The public CLI is a generated client-only package, and its file list is proven by running it** ([task 1003679](https://cloudbongos.com/builders#/task/1003679) · goal 1000054 — *A newcomer can build without the UI*). The core ships private as `@bongos/core` ([ADR 0108](<redacted>.md)), so a newcomer with no credential can install NOTHING and the web hall is the only way in — the owner's words: "how am I supposed to easily take on tasks as a new builder? I should be able to do everything without the UI." Open task 1002025 proposed publishing the core, which ships the SERVER and bypasses [ADR 0099](<redacted>.md)'s redaction pipeline (dormant until ~2026-11). **Decision: generate a separate public `@cloudbongos/cli` from the core and do not publish the core.** Four load-bearing parts. (1) SCOPED NAME, because npm shares one namespace between org names and unscoped packages — the bare `cloudbongos` is unpublishable *precisely because the owner owns that org*, which npm reports as "invalid" and reads like the name is taken; and a package name cannot be created on the npm website at all (it exists on first publish, which is why "Add Existing Package" answered `Forbidden`). (2) THE FILE LIST IS DECLARED AND PROVEN BY BEHAVIOUR, never computed — a static require-closure CANNOT answer this, because `src/module-api.js` is the doorway a module may only import ([ADR 0083](<redacted>.md)) and it *names* every kernel capability, so a static walk sees 33 `src/` files from `start.js` alone where runtime resolves **one** (`api-prefix.js`, which imports nothing) — the same trap as task 1003677's unsatisfiable done-when. So the test packs the tarball, installs it into an empty dir with NO repo, and runs every verb: it may fail for want of a session, never for a missing file. That caught two real holes on its first strengthened run — `clients/bongos-client/index.mjs`, reached by a dynamic `import()` no `require()` walk can see, and a hand-listed `files` array that dropped `clients/` and produced a tarball which installed cleanly then died on first use; both are now build-time refusals, and the `files` array is derived from the manifest. (3) `claim`/`ship`/`dev`/`serve`/`module`/`upgrade`/`onboard`/`doctor`/`exec`/`package-core` are ABSENT and the CLI says WHY plus the next step (`bongos shell` → a cloud box with the full CLI) — a bare `unknown command` teaches nothing, which is the exact failure being fixed. The supported journey is `login` → `start` → `shell`. (4) A REDACTION GATE fails the build closed on non-loopback IPv4 (RFC 5737 doc ranges exempt), token/key shapes, and every domain + owner login readable from the instance's own `config/branding.json` — needles come from host config so the core carries no instance identity ([ADR 0062 §7](<redacted>.md)); `cloudbongos.com` is allowlisted as public by design. No `repository` field while the core repo is private (it would 404 for every user and publish the owner's login for nothing). 31 files, one dependency (`undici`), version independent of the core's ([ADR 0161](<redacted>.md)). `@bongos/client` is VENDORED, not depended on — a public package depending on a private one is uninstallable. **The owner runs `npm publish`; a builder must not.** Task 1002025 is superseded. Rejected: publishing the core; deriving from a static closure (structurally impossible past the doorway); waiting for the mirror (dormant, and this is a build product not a source release); an unscoped name; depending on `@bongos/client`; a degraded `claim`; shipping all of `src/` to make lazy getters safe.](<redacted>.md) | cli / distribution / public surface |
|
package/docs/file-map.md
CHANGED
|
@@ -167,6 +167,7 @@
|
|
|
167
167
|
│ ├── ship.js ← resolve claim as shipped, award credits (`/builder-ship`); also uploads a secret-scrubbed session digest to the corpus (6D.1, ADR 0027); on a dev box runs the sandbox-first review gate before resolving (#927, ADR 0046)
|
|
168
168
|
│ ├── ship-visual.js ← the OPTIONAL `--visual <image> [--visual-alt "…"]` leg of a ship (task 1003109): screens the file BEFORE the claim resolves (bad input costs no claim), uploads it AFTER (a failed picture never fails a ship)
|
|
169
169
|
│ ├── package-core.js ← package the core as a versioned, installable artifact + pin manifest (`bongos package-core`; R84, ADR 0100 §1, [#1688](https://example.com/builders#/task/1688)): isPublishable() selection + mirror redaction + fail-closed no-leak gate → dist/bongos-core-<version>.{tgz,manifest.json}; version = src/module-api CORE_VERSION. Recipe: docs/recipes/packaging-the-core.md
|
|
170
|
+
│ ├── build-cli-package.js ← generate the PUBLIC, client-only `@cloudbongos/cli` npm package from this repo (task 1003679, ADR 0258): declared FILES manifest (no server/routes/auth/pool/provisioning) + generated dispatcher, README and package.json whose `files` array is DERIVED from the manifest; refuses to emit on a relative `import()` the manifest omits, and on a redaction hit (non-loopback IPv4, token/key shapes, instance domains + owner login read from config/branding.json). Sibling of package-core.js, opposite audience: that one packages the private core for an instance, this one packages the client surface for the public. Proven by tests/cli_package.mjs, which installs the packed tarball into an empty dir and runs every verb. `npm publish` is the OWNER's step, never a builder's → dist/cloudbongos-cli/
|
|
170
171
|
│ ├── sandbox-stage.js ← stage the working tree on the live game preview (box OR local) for browser review before ship; resolvePreviewContext picks the context (`/builder-stage`; #927, ADR 0046; local: task 1056)
|
|
171
172
|
│ ├── local-preview.js ← builder CLI for the LOCAL sandbox: start/stop/status/restart/logs/url of the game-only preview at http://localhost:3100 (`npm run preview`; task 1056)
|
|
172
173
|
│ ├── local-preview-lib.js ← core of the local sandbox launcher (detached `node src/preview-server.js`, pid/health/port mgmt); shared by local-preview.js + sandbox-stage.js (task 1056)
|
|
@@ -1601,5 +1601,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1601
1601
|
landed since 1.19.574 with no explicit bump. run 34076465465. (task 1002620)
|
|
1602
1602
|
1.19.576 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1603
1603
|
landed since 1.19.575 with no explicit bump. run 34132732488. (task 1002620)
|
|
1604
|
+
1.19.577 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1605
|
+
landed since 1.19.576 with no explicit bump. run 34142767912. (task 1002620)
|
|
1606
|
+
1.19.578 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1607
|
+
landed since 1.19.577 with no explicit bump. run 34146835487. (task 1002620)
|
|
1604
1608
|
---------------------------------------------------------------------------
|
|
1605
1609
|
```
|
|
@@ -5590,6 +5590,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5590
5590
|
three cards below share ONE read of the row (task 1003582), so
|
|
5591
5591
|
dropping the memo here is what keeps each entry a fresh read */
|
|
5592
5592
|
if (!keepRepo) forgetSettings();
|
|
5593
|
+
if (!keepRepo) forgetManifest();
|
|
5593
5594
|
if (!keepRepo) loadVisibility(inst);
|
|
5594
5595
|
if (!keepRepo) loadJoinability(inst);
|
|
5595
5596
|
if (!keepRepo) loadDoor();
|
|
@@ -5892,6 +5893,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5892
5893
|
if (!inst || !$('mVisRuns')) return;
|
|
5893
5894
|
if (visPushOpen(inst)) {
|
|
5894
5895
|
mVisPushSeen = true;
|
|
5896
|
+
forgetManifest(); /* stale from the moment a push is queued — and by the
|
|
5897
|
+
time it settles both cards share one fresh read */
|
|
5895
5898
|
visRuns(inst.open_intent.state === 'running'
|
|
5896
5899
|
? 'Restarting the project to apply a settings change…'
|
|
5897
5900
|
: 'A settings change is queued — the project restarts to pick it up. Until then it keeps its current setting.');
|
|
@@ -5900,43 +5903,81 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5900
5903
|
if (mVisPushSeen) { mVisPushSeen = false; loadVisRuns(inst); }
|
|
5901
5904
|
}
|
|
5902
5905
|
|
|
5906
|
+
/* ── the project's own manifest, read ONCE per manage entry (task 1003554) ─
|
|
5907
|
+
Two cards render off the SAME cross-origin GET of the project's manifest —
|
|
5908
|
+
how the project is reached (brand.project.platformVisibility) and how
|
|
5909
|
+
people join (brand.project.joinability) — and each used to ask for itself:
|
|
5910
|
+
two identical round trips to somebody else's server on every entry.
|
|
5911
|
+
|
|
5912
|
+
Memoised like ensureSettings() and keyed on the project the same way, so a
|
|
5913
|
+
switch is never served the last one's answer. The difference that matters
|
|
5914
|
+
is WHEN it is dropped. The settings row is read once per entry and a memo
|
|
5915
|
+
that lives for that entry is right; this answer instead goes stale the
|
|
5916
|
+
moment a settings push is queued, and the re-read after that push settles
|
|
5917
|
+
is the ONLY thing that can say the new value took. So forgetManifest() is
|
|
5918
|
+
called from the push-open branch of renderVisState/renderJoinState, which
|
|
5919
|
+
runs on every repaint while the push is open and reads nothing itself: by
|
|
5920
|
+
the time it settles the memo is already empty, and the two cards' re-reads
|
|
5921
|
+
then share one read rather than racing two. A lifetime memo here would
|
|
5922
|
+
freeze both lines at their pre-restart value — an efficiency cleanup
|
|
5923
|
+
turned into a wrong answer on screen.
|
|
5924
|
+
|
|
5925
|
+
It RESOLVES with every outcome rather than throwing, so one unreachable
|
|
5926
|
+
project fans out to both lines and neither goes silent: status 0 means it
|
|
5927
|
+
could not be reached at all, any other status is the project's own
|
|
5928
|
+
refusal. The 8s budget and its abort belong to the shared read now — one
|
|
5929
|
+
timer for the pair, not one each. */
|
|
5930
|
+
var manifestPromise = null;
|
|
5931
|
+
var manifestFor = null; /* which project the memo holds — a switch must not reuse it */
|
|
5932
|
+
|
|
5933
|
+
function ensureManifest(domain) {
|
|
5934
|
+
if (manifestPromise && manifestFor === manageId) return manifestPromise;
|
|
5935
|
+
manifestFor = manageId;
|
|
5936
|
+
var ctl = typeof AbortController === 'function' ? new AbortController() : null;
|
|
5937
|
+
var timer = ctl ? setTimeout(function () { ctl.abort(); }, 8000) : null;
|
|
5938
|
+
manifestPromise = fetch('https://' + domain + '/api/gds/instance', { mode: 'cors', signal: ctl ? ctl.signal : undefined })
|
|
5939
|
+
.then(function (res) {
|
|
5940
|
+
/* a project that ANSWERED but refused (its own pre-launch gate, a proxy
|
|
5941
|
+
error page) is not an older core — carry the status, don't guess */
|
|
5942
|
+
if (!res.ok) return { ok: false, status: res.status, data: null };
|
|
5943
|
+
return res.json().then(function (m) { return { ok: true, status: res.status, data: m }; });
|
|
5944
|
+
})
|
|
5945
|
+
.catch(function () { return { ok: false, status: 0, data: null }; })
|
|
5946
|
+
.then(function (ans) { if (timer) clearTimeout(timer); return ans; });
|
|
5947
|
+
return manifestPromise;
|
|
5948
|
+
}
|
|
5949
|
+
|
|
5950
|
+
/* Drop the memo so the next ensureManifest() is a real read — at each manage
|
|
5951
|
+
entry, and for the whole time a settings push is open. */
|
|
5952
|
+
function forgetManifest() { manifestPromise = null; manifestFor = null; }
|
|
5953
|
+
|
|
5903
5954
|
/* what the project RUNS: its own public manifest, read cross-origin (the
|
|
5904
5955
|
same GET /instance the platform's runner reads after a restart) */
|
|
5905
5956
|
function loadVisRuns(inst) {
|
|
5906
5957
|
if (!inst || inst.status !== 'active' || !inst.domain) { visRuns(''); return; }
|
|
5907
5958
|
var asked = manageId;
|
|
5908
5959
|
visRuns('Checking what the project is running…');
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
error page) is not an older core — say what happened, not a guess */
|
|
5915
|
-
if (!res.ok) throw new Error('http:' + res.status);
|
|
5916
|
-
return res.json();
|
|
5917
|
-
})
|
|
5918
|
-
.then(function (m) {
|
|
5919
|
-
if (asked !== manageId) return;
|
|
5920
|
-
var p = m && m.brand && m.brand.project;
|
|
5921
|
-
var v = p && p.platformVisibility;
|
|
5922
|
-
if (!p || typeof p !== 'object') {
|
|
5923
|
-
visRuns('This project’s server doesn’t report this setting yet — it runs an older platform version, so the setting can’t take effect until the project is updated.');
|
|
5924
|
-
return;
|
|
5925
|
-
}
|
|
5926
|
-
var o = VIS_OPTIONS[v];
|
|
5927
|
-
if (!o) { visRuns('This project’s server reports a setting this page doesn’t recognise (' + esc(String(v)) + ').'); return; }
|
|
5928
|
-
var want = mVisSettings && mVisSettings.value;
|
|
5929
|
-
visRuns('Right now the project runs as <b>' + esc(o.label) + '</b>' +
|
|
5930
|
-
(want && want !== v && VIS_OPTIONS[want] ? ' — the saved choice (' + esc(VIS_OPTIONS[want].label) + ') hasn’t taken effect there yet.' : '.'));
|
|
5931
|
-
})
|
|
5932
|
-
.catch(function (err) {
|
|
5933
|
-
if (asked !== manageId) return;
|
|
5934
|
-
var http = /^http:(\d+)$/.exec(String(err && err.message));
|
|
5935
|
-
visRuns(http
|
|
5936
|
-
? 'The project answered but didn’t share what it’s running (it replied ' + esc(http[1]) + ').'
|
|
5960
|
+
ensureManifest(inst.domain).then(function (ans) {
|
|
5961
|
+
if (asked !== manageId) return;
|
|
5962
|
+
if (!ans.ok) {
|
|
5963
|
+
visRuns(ans.status
|
|
5964
|
+
? 'The project answered but didn’t share what it’s running (it replied ' + esc(String(ans.status)) + ').'
|
|
5937
5965
|
: 'Couldn’t reach the project to check what it’s running right now.');
|
|
5938
|
-
|
|
5939
|
-
|
|
5966
|
+
return;
|
|
5967
|
+
}
|
|
5968
|
+
var m = ans.data;
|
|
5969
|
+
var p = m && m.brand && m.brand.project;
|
|
5970
|
+
var v = p && p.platformVisibility;
|
|
5971
|
+
if (!p || typeof p !== 'object') {
|
|
5972
|
+
visRuns('This project’s server doesn’t report this setting yet — it runs an older platform version, so the setting can’t take effect until the project is updated.');
|
|
5973
|
+
return;
|
|
5974
|
+
}
|
|
5975
|
+
var o = VIS_OPTIONS[v];
|
|
5976
|
+
if (!o) { visRuns('This project’s server reports a setting this page doesn’t recognise (' + esc(String(v)) + ').'); return; }
|
|
5977
|
+
var want = mVisSettings && mVisSettings.value;
|
|
5978
|
+
visRuns('Right now the project runs as <b>' + esc(o.label) + '</b>' +
|
|
5979
|
+
(want && want !== v && VIS_OPTIONS[want] ? ' — the saved choice (' + esc(VIS_OPTIONS[want].label) + ') hasn’t taken effect there yet.' : '.'));
|
|
5980
|
+
});
|
|
5940
5981
|
}
|
|
5941
5982
|
|
|
5942
5983
|
/* ── the settings row, read ONCE per manage entry (task 1003582) ─────────
|
|
@@ -6305,6 +6346,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6305
6346
|
if (!inst || !$('mJoinRuns')) return;
|
|
6306
6347
|
if (visPushOpen(inst)) {
|
|
6307
6348
|
mJoinPushSeen = true;
|
|
6349
|
+
forgetManifest(); /* the same one settle, the same one drop */
|
|
6308
6350
|
joinRuns(inst.open_intent.state === 'running'
|
|
6309
6351
|
? 'Restarting the project to apply a settings change…'
|
|
6310
6352
|
: 'A settings change is queued — the project restarts to pick it up. Until then it keeps its current setting.');
|
|
@@ -6313,40 +6355,33 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6313
6355
|
if (mJoinPushSeen) { mJoinPushSeen = false; loadJoinRuns(inst); }
|
|
6314
6356
|
}
|
|
6315
6357
|
|
|
6316
|
-
/* what the project RUNS:
|
|
6358
|
+
/* what the project RUNS: the SAME shared manifest read the reach card uses
|
|
6359
|
+
(task 1003554) — a different key out of one answer, not a second GET */
|
|
6317
6360
|
function loadJoinRuns(inst) {
|
|
6318
6361
|
if (!inst || inst.status !== 'active' || !inst.domain) { joinRuns(''); return; }
|
|
6319
6362
|
var asked = manageId;
|
|
6320
6363
|
joinRuns('Checking what the project is running…');
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
return res.json();
|
|
6327
|
-
})
|
|
6328
|
-
.then(function (m) {
|
|
6329
|
-
if (asked !== manageId) return;
|
|
6330
|
-
var p = m && m.brand && m.brand.project;
|
|
6331
|
-
var v = p && p.joinability;
|
|
6332
|
-
if (!p || typeof p !== 'object') {
|
|
6333
|
-
joinRuns('This project’s server doesn’t report this setting yet — it runs an older platform version, so the setting can’t take effect until the project is updated.');
|
|
6334
|
-
return;
|
|
6335
|
-
}
|
|
6336
|
-
var o = JOIN_OPTIONS[v];
|
|
6337
|
-
if (!o) { joinRuns('This project’s server reports a setting this page doesn’t recognise (' + esc(String(v)) + ').'); return; }
|
|
6338
|
-
var want = mJoinSettings && mJoinSettings.value;
|
|
6339
|
-
joinRuns('Right now the project runs as <b>' + esc(o.label) + '</b>' +
|
|
6340
|
-
(want && want !== v && JOIN_OPTIONS[want] ? ' — the saved choice (' + esc(JOIN_OPTIONS[want].label) + ') hasn’t taken effect there yet.' : '.'));
|
|
6341
|
-
})
|
|
6342
|
-
.catch(function (err) {
|
|
6343
|
-
if (asked !== manageId) return;
|
|
6344
|
-
var http = /^http:(\d+)$/.exec(String(err && err.message));
|
|
6345
|
-
joinRuns(http
|
|
6346
|
-
? 'The project answered but didn’t share what it’s running (it replied ' + esc(http[1]) + ').'
|
|
6364
|
+
ensureManifest(inst.domain).then(function (ans) {
|
|
6365
|
+
if (asked !== manageId) return;
|
|
6366
|
+
if (!ans.ok) {
|
|
6367
|
+
joinRuns(ans.status
|
|
6368
|
+
? 'The project answered but didn’t share what it’s running (it replied ' + esc(String(ans.status)) + ').'
|
|
6347
6369
|
: 'Couldn’t reach the project to check what it’s running right now.');
|
|
6348
|
-
|
|
6349
|
-
|
|
6370
|
+
return;
|
|
6371
|
+
}
|
|
6372
|
+
var m = ans.data;
|
|
6373
|
+
var p = m && m.brand && m.brand.project;
|
|
6374
|
+
var v = p && p.joinability;
|
|
6375
|
+
if (!p || typeof p !== 'object') {
|
|
6376
|
+
joinRuns('This project’s server doesn’t report this setting yet — it runs an older platform version, so the setting can’t take effect until the project is updated.');
|
|
6377
|
+
return;
|
|
6378
|
+
}
|
|
6379
|
+
var o = JOIN_OPTIONS[v];
|
|
6380
|
+
if (!o) { joinRuns('This project’s server reports a setting this page doesn’t recognise (' + esc(String(v)) + ').'); return; }
|
|
6381
|
+
var want = mJoinSettings && mJoinSettings.value;
|
|
6382
|
+
joinRuns('Right now the project runs as <b>' + esc(o.label) + '</b>' +
|
|
6383
|
+
(want && want !== v && JOIN_OPTIONS[want] ? ' — the saved choice (' + esc(JOIN_OPTIONS[want].label) + ') hasn’t taken effect there yet.' : '.'));
|
|
6384
|
+
});
|
|
6350
6385
|
}
|
|
6351
6386
|
|
|
6352
6387
|
function loadJoinability(inst) {
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.578",
|
|
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.578",
|
|
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.578",
|
|
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",
|
|
@@ -124,7 +124,7 @@ function knownHostsLineNamesAny(line, names) {
|
|
|
124
124
|
// line for the same box.
|
|
125
125
|
//
|
|
126
126
|
// It used to compare field 1 to the hostname with `!==`, which missed the two forms
|
|
127
|
-
// OpenSSH actually writes — `host,
|
|
127
|
+
// OpenSSH actually writes — `host,192.0.2.4` (what `StrictHostKeyChecking=accept-new`
|
|
128
128
|
// records) and the hashed `|1|salt|hash`. Those lines survived the "replace", so
|
|
129
129
|
// every re-pin ACCUMULATED another entry and the client kept verifying against the
|
|
130
130
|
// first, now-wrong one: "Host denied (verification failed)" on a box that had just
|