@bongos/core 1.19.80 → 1.19.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.bongos-core.json CHANGED
@@ -2,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.80",
6
- "core_contract": "1.19.80",
7
- "source_commit": "e9830526a46cd238c6ea856406a1bcde6fcb6e4f",
5
+ "core_version": "1.19.81",
6
+ "core_contract": "1.19.81",
7
+ "source_commit": "c19697303b3961520ebb175928cde6beddf00896",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-12T23:02:51.567Z",
9
+ "built_at": "2026-08-12T23:08:05.391Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 295,
13
13
  "agent_docs_stubbed": 21,
14
- "functional_verbatim": 1226,
14
+ "functional_verbatim": 1227,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1542,
20
- "tree_sha256": "8efe860e490a9f9bca1f3739c652e205e29d50643e86361a1eb631f7474a1ba9",
19
+ "file_count": 1543,
20
+ "tree_sha256": "799b7337a46edabc3eb3adca6a29fb43c274e1ab27a9b130d09a986d4177cd9b",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -37,7 +37,7 @@
37
37
  {
38
38
  "path": ".claude/skills/builder-box/SKILL.md",
39
39
  "mode": "0000644",
40
- "sha256": "d8bfc9298bbb136742147e83d2500b9cb0961fec35ca15dc3cb773c9ecdf8b4b"
40
+ "sha256": "e131788fccfd87e882ef4e03798513cf7ff41047a2908fca8cf6314ff12e0524"
41
41
  },
42
42
  {
43
43
  "path": ".claude/skills/builder-claim/SKILL.md",
@@ -4057,12 +4057,12 @@
4057
4057
  {
4058
4058
  "path": "package-lock.json",
4059
4059
  "mode": "0000644",
4060
- "sha256": "b7e3e581321a968a2978affc8766e5bc9ec8f412f0c3e7a94c47916138724de9"
4060
+ "sha256": "845771b9d06a05dcb4eb8b90a0002b26d1fc105c2d27d24f870f89640fb09769"
4061
4061
  },
4062
4062
  {
4063
4063
  "path": "package.json",
4064
4064
  "mode": "0000644",
4065
- "sha256": "283e5a12a702be6937db0eb28a69775e3cb6b760ae217b3824d65ba7feb6743b"
4065
+ "sha256": "04febc37475484098b13f52462cda84d2a120c5ef64b7c2edf733e14ac6b3e1f"
4066
4066
  },
4067
4067
  {
4068
4068
  "path": "public-docs/index.html",
@@ -4552,7 +4552,7 @@
4552
4552
  {
4553
4553
  "path": "scripts/gds/box-sync.js",
4554
4554
  "mode": "0000644",
4555
- "sha256": "8169a34dcef766360d10c7d27c7404a776aeb0b8a43f8b91fc9bc12af35a8b1b"
4555
+ "sha256": "981544bfd56d486c2017edd744c86089659771a74399b4780455821c45e3b3a9"
4556
4556
  },
4557
4557
  {
4558
4558
  "path": "scripts/gds/box.js",
@@ -5687,7 +5687,7 @@
5687
5687
  {
5688
5688
  "path": "src/module-api.js",
5689
5689
  "mode": "0000644",
5690
- "sha256": "e85465ed2f13494396cf2d74bad0c8a2b6e52d42d48c493a3d0dcf4694f6072e"
5690
+ "sha256": "3e4f5f5d1cb49b46416308dc7a4fa145b8260ca8dd0c63ed1a16ba499b622bc2"
5691
5691
  },
5692
5692
  {
5693
5693
  "path": "src/module-loader/loader.js",
@@ -6029,6 +6029,11 @@
6029
6029
  "mode": "0000644",
6030
6030
  "sha256": "93bc891850e77350161214749954ddb257169ae323b7242f35becc4e6a9cabcf"
6031
6031
  },
6032
+ {
6033
+ "path": "tests/box_sync_scope_report.mjs",
6034
+ "mode": "0000644",
6035
+ "sha256": "010525c500aa1d5133f85a1a2ea7c23e9a680b08493a6f3b78c69a5108501468"
6036
+ },
6032
6037
  {
6033
6038
  "path": "tests/box_task_scope.mjs",
6034
6039
  "mode": "0000644",
@@ -16,12 +16,18 @@ The per-builder box lifecycle (ADR 0031 §2) auto-suspends when idle (snapshot-a
16
16
 
17
17
  ## How to use
18
18
 
19
- 1. **Fetch the box + keys** (own-scoped; uses the builder's session token):
19
+ 1. **Fetch the box + keys + the resolved scope** (own-scoped; uses the builder's session token):
20
20
  ```bash
21
- node -e 'const{apiCall}=require("./scripts/gds/cli-lib");(async()=>{for(const p of ["/api/gds/box/me","/api/gds/box/ssh-key"]){const r=await apiCall("GET",p);console.log(p,r.status,JSON.stringify(r.data));}})()'
21
+ node -e 'const{apiCall}=require("./scripts/gds/cli-lib");(async()=>{for(const p of ["/api/gds/box/me","/api/gds/box/ssh-key","/api/gds/box/scope"]){const r=await apiCall("GET",p);console.log(p,r.status,JSON.stringify(r.data));}})()'
22
22
  ```
23
23
  (If the script returns "no GDS session", run `/builder-setup` first.)
24
24
 
25
+ **Always report the scope, not just the state** (task 1002731). `/box/scope` returns `{scope, modules, sparse_paths, task_scope}`, and a too-narrow box is otherwise a *silent* failure — the builder sees a working box that is quietly missing the code they need. Read it out:
26
+ - `scope: 'task'` → name the modules: *"your box holds dev-box, hall-ui — and nothing else."*
27
+ - `scope: 'base'` → **say plainly that the box has no `modules/` code at all.** With no active claim that is expected; claiming a task and running `/builder-sync` fixes it.
28
+ - `scope: 'full'` with `modules: []` → the task-1002729 fallback fired: they hold claims, but those claims' goals declare no `scope_modules`, so the box fell back to their rank. Usable, but task-scoping is not actually doing anything — setting the goal's wall would give a tighter box.
29
+ - `scope: 'starter'` → the fixed newcomer slice, not scoped to their claim.
30
+
25
31
  2. **Relay it in plain language:**
26
32
  - **state = `none`** → they have no box yet. On a Mac/Windows: run `/builder-connect` (registers their SSH key + installs the Desktop file). To actually create the box hands-off, `POST /api/gds/box/ensure` queues the auto-provision (no operator CLI needed). *(The old path — ask an operator to `box.js onboard <login>` — still works but is no longer required.)*
27
33
  - **state = `active`** → box is up at `hostname` (`ip`). They can connect from Claude Desktop → Code → Environment → "Example Dev Box". For a browser/Chromebook (ADR 0038), the terminal comes from the web: `GET /api/gds/box/terminal` returns a `url` + a basic-auth `credential` (username `otb`, password = <redacted>; open the url, enter them, then type `claude` in the terminal. No SSH needed.
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.80",
3
+ "version": "1.19.81",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.80",
9
+ "version": "1.19.81",
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.80",
3
+ "version": "1.19.81",
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",
@@ -57,10 +57,77 @@ function main(argv = process.argv.slice(2)) {
57
57
  console.error(`sync: could not run the fetch (${r.error.message})`);
58
58
  process.exit(1);
59
59
  }
60
- if (r.status === 0) console.log('sync: done — your box now holds the code for your active claim.');
61
- process.exit(r.status == null ? 1 : r.status);
60
+ if (r.status !== 0) process.exit(r.status == null ? 1 : r.status);
61
+ console.log('sync: done — your box now holds the code for your active claim.');
62
+ // Then say WHAT it holds, so a base-only box is visible instead of silent.
63
+ reportScope().then(() => process.exit(0), () => process.exit(0));
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // task 1002731 — say what scope the box ACTUALLY got
68
+ // ---------------------------------------------------------------------------
69
+
70
+ // The failure this exists for is a SILENT one: a box comes up holding only the
71
+ // base slice (no modules/ at all) and nothing anywhere says so. Live, that was
72
+ // 247 of 248 source grants — including an Archon with 7 active claims — and the
73
+ // only way to see it was reading box_events on the control plane. GET /box/scope
74
+ // already returns the resolved decision; nothing ever showed it to a human.
75
+ //
76
+ // PURE so it is unit-testable without a box or a network: takes the /box/scope
77
+ // body, returns the lines to print. null/garbage in → one honest line rather
78
+ // than a crash, because this is diagnostic output on an already-degraded path
79
+ // and must never be the thing that fails.
80
+ function formatScopeReport(scope) {
81
+ if (!scope || typeof scope !== 'object' || Array.isArray(scope)) {
82
+ return ['scope: could not read your box scope (GET /box/scope did not answer).'];
83
+ }
84
+ const modules = Array.isArray(scope.modules) ? scope.modules : null;
85
+ const paths = Array.isArray(scope.sparse_paths) ? scope.sparse_paths : [];
86
+ const out = [];
87
+
88
+ if (scope.scope === 'task' && modules && modules.length) {
89
+ out.push(`scope: task — your box holds ${modules.length} module(s): ${modules.join(', ')}`);
90
+ } else if (scope.scope === 'base') {
91
+ out.push('scope: BASE ONLY — your box holds no modules/ code at all.');
92
+ out.push(scope.task_scope
93
+ ? ' Why: you hold no active claim, so there is no work to scope the box to.'
94
+ : ' Why: this instance resolved no claim scope.');
95
+ out.push(' Fix: claim a task (/builder-claim N), then re-run this sync.');
96
+ } else if (scope.scope === 'full') {
97
+ out.push('scope: full — your box holds the whole repo (your rank allows it).');
98
+ if (modules && modules.length === 0) {
99
+ // The task-1002729 fallback fired: claims held, but their goals declare no
100
+ // wall. Worth naming — the box is usable, but task-scoping is doing nothing.
101
+ out.push(' Note: your claimed work declares no module scope, so this fell back to');
102
+ out.push(" your rank rather than scoping to the task. Setting the goal's");
103
+ out.push(' scope_modules would give you a tighter box.');
104
+ }
105
+ } else if (scope.scope === 'starter') {
106
+ out.push('scope: starter — a fixed slice for newcomers, not scoped to your claim.');
107
+ } else {
108
+ out.push(`scope: ${String(scope.scope || 'unknown')}`);
109
+ }
110
+ if (paths.length) out.push(` paths: ${paths.length} sparse entr${paths.length === 1 ? 'y' : 'ies'}`);
111
+ return out;
112
+ }
113
+
114
+ // Read the resolved scope and print it. Never throws and never changes the exit
115
+ // status — a diagnostic that could fail the sync it reports on would be worse
116
+ // than no diagnostic.
117
+ async function reportScope() {
118
+ try {
119
+ const { apiCall } = require('./cli-lib');
120
+ const r = await apiCall('GET', '/api/gds/box/scope');
121
+ if (!r || r.status !== 200) {
122
+ console.log('scope: could not read your box scope right now (that does not affect the sync above).');
123
+ return;
124
+ }
125
+ for (const line of formatScopeReport(r.data)) console.log(line);
126
+ } catch (_) {
127
+ console.log('scope: could not read your box scope right now (that does not affect the sync above).');
128
+ }
62
129
  }
63
130
 
64
131
  if (require.main === module) main();
65
132
 
66
- module.exports = { resolveFetchScript, FETCH_SCRIPT };
133
+ module.exports = { resolveFetchScript, FETCH_SCRIPT, formatScopeReport };
package/src/module-api.js CHANGED
@@ -587,8 +587,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
587
587
  // landed since 1.19.78 with no explicit bump. run 31648954641. (task 1002620)
588
588
  // 1.19.80 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
589
589
  // landed since 1.19.79 with no explicit bump. run 31649366675. (task 1002620)
590
+ // 1.19.81 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
591
+ // landed since 1.19.80 with no explicit bump. run 31649705242. (task 1002620)
590
592
  // ---------------------------------------------------------------------------
591
- const CORE_VERSION = '1.19.80'; // CI auto-patch carrier (ADR 0161); see changelog above
593
+ const CORE_VERSION = '1.19.81'; // CI auto-patch carrier (ADR 0161); see changelog above
592
594
 
593
595
  // A namespaced logger so a module's log lines are attributable + consistent.
594
596
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -0,0 +1,81 @@
1
+ // tests/box_sync_scope_report.mjs
2
+ //
3
+ // Task 1002731 — the scope read-out that makes a too-narrow box VISIBLE.
4
+ //
5
+ // The failure being guarded is a silent one: a box comes up holding only the
6
+ // base slice (no modules/ at all) and nothing tells the builder. Live, that was
7
+ // 247 of 248 source grants on the cloudbongos fleet — including an Archon
8
+ // holding 7 active claims — and the only way to see it was reading box_events
9
+ // on the control plane.
10
+ //
11
+ // formatScopeReport is PURE (takes the GET /box/scope body, returns lines), so
12
+ // this needs no box, no network and no DB.
13
+ //
14
+ // Run: node tests/box_sync_scope_report.mjs
15
+
16
+ import { strict as assert } from 'node:assert';
17
+ import { createRequire } from 'node:module';
18
+
19
+ const require = createRequire(import.meta.url);
20
+ const { formatScopeReport } = require('../scripts/gds/box-sync.js');
21
+
22
+ let passed = 0;
23
+ let failed = 0;
24
+ function t(name, fn) {
25
+ try { fn(); passed++; console.log(` PASS ${name}`); }
26
+ catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
27
+ }
28
+ const joined = (scope) => formatScopeReport(scope).join('\n');
29
+
30
+ t('base-only is stated in plain words, with a why and a fix', () => {
31
+ const out = joined({ scope: 'base', mode: 'sparse', modules: [], sparse_paths: ['docs', 'scripts'], task_scope: true });
32
+ assert.match(out, /BASE ONLY/, 'the degraded state is named, not buried');
33
+ assert.match(out, /no modules\/ code at all/, 'says what is actually missing');
34
+ assert.match(out, /Why:/, 'explains the cause');
35
+ assert.match(out, /builder-claim/, 'gives the builder the actual next step');
36
+ });
37
+
38
+ t('task scope names the modules the box holds', () => {
39
+ const out = joined({ scope: 'task', mode: 'sparse', modules: ['dev-box', 'hall-ui'], sparse_paths: ['a', 'b', 'c'], task_scope: true });
40
+ assert.match(out, /scope: task/);
41
+ assert.match(out, /dev-box, hall-ui/, 'names them rather than just counting');
42
+ assert.match(out, /3 sparse entries/);
43
+ });
44
+
45
+ t('full + EMPTY modules surfaces the 1002729 fallback rather than looking healthy', () => {
46
+ // The exact live shape: claims held, but their goals declare no scope_modules.
47
+ // The box works, so nothing complains — but task-scoping is not doing anything,
48
+ // and that is worth telling someone.
49
+ const out = joined({ scope: 'full', mode: 'full', modules: [], sparse_paths: null, task_scope: true });
50
+ assert.match(out, /scope: full/);
51
+ assert.match(out, /declares no module scope/, 'names why it fell back');
52
+ assert.match(out, /scope_modules/, 'points at the thing to fix');
53
+ });
54
+
55
+ t('full with real modules does NOT nag about the fallback', () => {
56
+ const out = joined({ scope: 'full', mode: 'full', modules: ['kernel'], sparse_paths: null, task_scope: false });
57
+ assert.match(out, /scope: full/);
58
+ assert.ok(!/declares no module scope/.test(out), 'no false alarm when nothing is wrong');
59
+ });
60
+
61
+ t('starter is distinguished from a claim-scoped box', () => {
62
+ const out = joined({ scope: 'starter', mode: 'sparse', modules: null, sparse_paths: ['x'], task_scope: false });
63
+ assert.match(out, /starter/);
64
+ assert.match(out, /not scoped to your claim/);
65
+ });
66
+
67
+ t('a missing or malformed body degrades to one honest line, never a throw', () => {
68
+ // This runs on an already-degraded path; it must not be the thing that fails.
69
+ for (const bad of [null, undefined, 'nope', 42, []]) {
70
+ const lines = formatScopeReport(bad);
71
+ assert.ok(Array.isArray(lines) && lines.length >= 1, `handled ${JSON.stringify(bad)}`);
72
+ }
73
+ assert.match(joined(null), /could not read/);
74
+ });
75
+
76
+ t('an unknown scope value is reported verbatim rather than swallowed', () => {
77
+ assert.match(joined({ scope: 'something-new', sparse_paths: [] }), /something-new/);
78
+ });
79
+
80
+ console.log(`\nbox_sync_scope_report.mjs: ${passed} passed, ${failed} failed`);
81
+ process.exit(failed === 0 ? 0 : 1);