@bongos/core 1.19.369 → 1.19.371

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.369",
6
- "core_contract": "1.19.369",
7
- "source_commit": "486cdf89329ccbf01188586ff918d22390620cba",
5
+ "core_version": "1.19.371",
6
+ "core_contract": "1.19.371",
7
+ "source_commit": "587eb4ed0b68da54bf63fb01b69fab937450bd66",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-25T21:52:45.667Z",
9
+ "built_at": "2026-08-25T22:08:11.031Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
- "docs_redacted": 333,
12
+ "docs_redacted": 334,
13
13
  "agent_docs_stubbed": 22,
14
- "functional_verbatim": 1543,
14
+ "functional_verbatim": 1544,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1898,
20
- "tree_sha256": "e4c29c0518ebcdb414195829f5e8efd9f175df0feb556f1eb8114b2f921e2336",
19
+ "file_count": 1900,
20
+ "tree_sha256": "55cb6adeadbe8a337b32bba8352f2dbc38d655ec2cdd807c550d527b9f49822d",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -1459,10 +1459,15 @@
1459
1459
  "mode": "0000644",
1460
1460
  "sha256": "caf369cc73f2a997df7dd12b074a5e7c815b8f717cb864ce49ce533d74008d75"
1461
1461
  },
1462
+ {
1463
+ "path": "docs/adr/0193-pause-task-scoped-box-slices.md",
1464
+ "mode": "0000644",
1465
+ "sha256": "38af373e191ebf9d9788884315f90d439ec5a719f08afc596039d722153c8396"
1466
+ },
1462
1467
  {
1463
1468
  "path": "docs/adr/README.md",
1464
1469
  "mode": "0000644",
1465
- "sha256": "632a5dd2b047437eca5201d0e572378a2a70d0b5d3ff10e97285ee00eec49618"
1470
+ "sha256": "cf4a81346983c4247f711855d8eae3939b3cbeb37b12ad7cf2449d2e896b3ade"
1466
1471
  },
1467
1472
  {
1468
1473
  "path": "docs/api-reference.md",
@@ -1857,7 +1862,7 @@
1857
1862
  {
1858
1863
  "path": "docs/recipes/work-on-a-task-from-your-dev-box.md",
1859
1864
  "mode": "0000644",
1860
- "sha256": "15a8fa87b11a3d3ab86551f9bc54adaf84c2a7a2b8fb7fc6915c0d16e5f5199d"
1865
+ "sha256": "be4d67926e39c9f9c146f5f709d30595057058f5c4444c1e87ed92e37b3f3190"
1861
1866
  },
1862
1867
  {
1863
1868
  "path": "docs/research/cursor-competitive-analysis.md",
@@ -2942,7 +2947,7 @@
2942
2947
  {
2943
2948
  "path": "modules/dev-box/routes/box.js",
2944
2949
  "mode": "0000644",
2945
- "sha256": "4310c28d74085f2544f92e2191e7cbc4d646bbc4e304fec83099298e5f8d9c93"
2950
+ "sha256": "bbe6ed7344f7e5dd1d4dba79e636f4bc02806d25193edb0609f41c850ffbad4c"
2946
2951
  },
2947
2952
  {
2948
2953
  "path": "modules/discord/CLAUDE.md",
@@ -4897,12 +4902,12 @@
4897
4902
  {
4898
4903
  "path": "package-lock.json",
4899
4904
  "mode": "0000644",
4900
- "sha256": "7ecd5aa9734efaab265e867498834dae2a5a58e4c0d1616cddcb0cfcea5b17e3"
4905
+ "sha256": "9013a462765ac09e02093fd322534183ef67983cba7fb17b01ffd9e514c904ba"
4901
4906
  },
4902
4907
  {
4903
4908
  "path": "package.json",
4904
4909
  "mode": "0000644",
4905
- "sha256": "4463e81c49fd1c7476e9351255bb9d37516106d9d5e2dc88ad0baaabf521de5e"
4910
+ "sha256": "02ba6d7d5c371b3b07671bb84216d7a38a23b9ab97f3ab491d218fb31f9afa46"
4906
4911
  },
4907
4912
  {
4908
4913
  "path": "public-docs/index.html",
@@ -6787,7 +6792,7 @@
6787
6792
  {
6788
6793
  "path": "src/module-api.js",
6789
6794
  "mode": "0000644",
6790
- "sha256": "8f018f2f5c96b98e7e08bfedb50161b4b9ffb639afe2ab9f108541e951c3fc6e"
6795
+ "sha256": "acc072cf951d0eb479345626f462425a47d7f8a0247db473375fa614f64e7589"
6791
6796
  },
6792
6797
  {
6793
6798
  "path": "src/module-loader/catalog.js",
@@ -7174,6 +7179,11 @@
7174
7179
  "mode": "0000644",
7175
7180
  "sha256": "22a2dc8bdccb9900b642371b85e8cc1785ecfacaf8f7579b042951425d95d794"
7176
7181
  },
7182
+ {
7183
+ "path": "tests/box_task_scope_pause.mjs",
7184
+ "mode": "0000644",
7185
+ "sha256": "3a5083e6249c70d3142f2b9c7e0c0bc031b09ccd01425c45f52e91e362b64d0b"
7186
+ },
7177
7187
  {
7178
7188
  "path": "tests/box_terminal_ssrf.mjs",
7179
7189
  "mode": "0000644",
@@ -0,0 +1,60 @@
1
+ # 0193 — Pause task-scoped box slices: a box serves its rank scope (the full repo for Metic+)
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-08-25
5
+ - **Tasks:** [#1003285](https://cloudbongos.com/builders#/task/1003285) (the pause), from the owner's 2026-08-25 dev-box efficiency review; the review's other findings are [#1003286](https://cloudbongos.com/builders#/task/1003286)–[#1003291](https://cloudbongos.com/builders#/task/1003291) and shipped [#1003253](https://cloudbongos.com/builders#/task/1003253).
6
+ - **Pauses, does not repeal:** [ADR 0148](<redacted>.md) / goal 1000051 — the claim-driven slice. The machinery stays; the default flips.
7
+
8
+ ## 1. The decision
9
+
10
+ The owner reviewed the live dev-box experience and decided: **pause the serving
11
+ of a task-scoped slice and serve the entire repo instead.** A box now gets its
12
+ **rank scope** — the full repo for Metic+, the starter sparse surface below —
13
+ regardless of what the builder has claimed.
14
+
15
+ Implementation is a pause, not a deletion: `routes/box.js` skips the
16
+ lifecycle-port `scopeKeys` resolution unless `BOX_TASK_SCOPE=on` is set in the
17
+ web process env. With `scopeKeys` absent, `decideSourceAccess` takes its
18
+ documented pre-ADR-0148 rank fallback — a path that has existed, unit-tested,
19
+ since the feature shipped. `GET /box/scope` reports `task_scoping_paused` so the
20
+ hall and the desktop app can say why `task_scope` is false.
21
+
22
+ ## 2. Why
23
+
24
+ The slice was defense-in-depth, never a hard wall — ADR 0031 §6 records the
25
+ honest limit: any read credential for the repo can read all of it, and
26
+ sparse-checkout is client-side. What the 2026-08-25 review found is that the
27
+ narrowing's *costs* were real while that benefit was notional:
28
+
29
+ - **Sessions on a box misdiagnose.** A Claude session that cannot see a module
30
+ (or its nested CLAUDE.md) concludes the code is missing or itself stuck, and
31
+ burns the session repairing context instead of doing the task.
32
+ - **The scope machinery multiplied the moving parts** that already failed in the
33
+ field: the base-slice collapse (task 1002729, 247 of 248 grants), the empty
34
+ `modules/` cone, and per-claim re-sync churn on every claim change.
35
+ - **Every live box user is Metic+ anyway** — the rank fallback grants them full;
36
+ the slice was narrowing trusted builders while newcomers were already gated by
37
+ rank.
38
+
39
+ ## 3. What does NOT change
40
+
41
+ - **Allow/deny is untouched.** The live-rank + status check per request (ADR
42
+ 0016), the provision floor, and the clawback on demotion all stand.
43
+ - **The credential posture is untouched.** The box still holds no permanent
44
+ credential; the per-request credential is read-only and `git push` still 403s
45
+ (ADR 0055; re-verified on hardware 2026-08-25, task 1003253).
46
+ - **Sub-Metic boxes still get the starter surface.** The rank scope IS the
47
+ boundary that carries the trust posture; it was never the claim slice.
48
+
49
+ ## 4. Revert
50
+
51
+ Set `BOX_TASK_SCOPE=on` in the web env and restart — the ADR 0148 behavior
52
+ returns exactly as shipped. If the pause becomes permanent, delete the
53
+ lifecycle-port resolution in `routes/box.js` and retire this flag with a
54
+ follow-up ADR.
55
+
56
+ ## 5. Known staleness while paused
57
+
58
+ The desktop app's task-picker copy ("your box is now scoped to `<modules>` —
59
+ and nothing else", BV1.R100) overstates while paused; it can read
60
+ `task_scoping_paused` from `GET /box/scope`. Cosmetic, not gating.
@@ -270,5 +270,6 @@ This keeps the decision history honest and traceable.
270
270
  | 0191 | [**Majority, and an author who may vote but never decide alone** ([task 1003273](https://cloudbongos.com/builders#/task/1003273); diagnosis and companion in [task 1003271](https://cloudbongos.com/builders#/task/1003271)). Supersedes ADR 0175 §3's pass-rule set and §§7/11's flat anti-self rule. The owner reported voting as broken; nothing was broken. Nine items sat open under `first_ratifier` with four archons active — any one of three non-authors could have ratified any of them in a click — but every item was authored by the one person looking at them, and ADR 0175's anti-self rule was enforced by rendering NO BALLOT, so from the owner's seat the feature did not exist. The lesson: a rule enforced by removing the control is indistinguishable from a broken feature. Two decisions. (1) `majority` joins PASS_RULES: strictly more than half of the resolved MEMBERSHIP must vote yes — of the membership, not of the votes cast, so low turnout cannot quietly pass things; a tie is not a majority. The three rules are NOT a ladder of strictness, they differ on WHAT SILENCE MEANS — `first_ratifier` and `consent` pass an unvoted item, `majority` returns it — which is the axis that matters and now the comment in config.js. (2) An author MAY vote and may NEVER DECIDE ALONE: the rule moved from refusing the cast to the two places something is actually decided — `shouldCloseAfterVote` needs a non-author vote under every rule, and the majority tally needs a non-author YES. Their yes still counts toward the numerator; their `no` is excluded from the objection test (else silence would be an author's safest act); a self-cast earns no karma. The ADR 0175 §7 amendment carve-out survives as the author rule's SCOPE, not an exception: `authorRuleApplies` is full_idea-only, so a sole Archon still ratifies their own amendment. Also records why the CLOCK is a consequence of majority and not an independent fix — `tallyOutcome` passes on ZERO votes, so adding a deadline under `first_ratifier` would have closed all nine items as ratified-unread, paying karma and credits on ideas nobody read; `BOARD_DEFAULTS.window_minutes` therefore stays `null` in the neutral core. States the cost plainly: three affirmative votes are needed on a four-member board, and a solo instance still cannot ratify its own ideas. Discord stays outbound-only (ADR 0175 §8 re-affirmed).](<redacted>.md) | government / board room |
271
271
  | 0192 | [**A gated project refuses strangers at the app's front door, and the hub reads what the project runs from the project itself** ([task 1003043](https://cloudbongos.com/builders#/task/1003043), goal 1000058). Task 1003040 gave every project a `platform_visibility` setting and 1003139/1003140 carried it into the instance's `branding().project.platformVisibility` — resolved, enforced by nothing. Decision: ONE app-level middleware (`src/bongos/platform-visibility-gate.js`), mounted beside the pre-launch gate ahead of every human and public-read surface — the landing, the hub pages, the status host, the hall shell, `/public/*` and every module's `rank: public` route — rather than per-surface wiring, which misses the next surface nobody told it about. "Member" is a live, non-deactivated session on this instance, the same `resolveSession` lookup `requireBuilder` uses, per request, no cache; a lookup that throws fails CLOSED. The exempt list is the door, the manifest and the probes and nothing wider: `/api/*/auth/*` (a door that cannot be opened locks the members out too), `/api/*/instance` (what a stranger reads to learn how to sign in AND what the runner reads after a settings restart to learn whether the value was consumed — including the push that turns this gate on), `/healthz`, `/version`, `/metrics`; the pre-launch gate's blanket `/api/` exemption is deliberately not copied. A refused page gets a self-contained 401 door (no stylesheet, no branding bundle, no script) with one control — Sign in, returning (relatively) to the page knocked on; a refused API call gets `res.fail('unauthenticated', 401)`, exactly requireBuilder's envelope, which is why it mounts after `attachFail`. A member's answer leaves marked `private, no-store` whatever the route said — the public read API sends `public, max-age`, and a CDN edge would otherwise serve a member's copy to the next stranger. The exempt list covers every API prefix (the versioned contract too), `/downloads/*` and Caddy's TLS ask; a request's session is resolved once (`resolveSession` memoizes per request). The hub's manage page shows the CHOICE from the platform's row and what the project RUNS from the project's own public manifest (`brand.project`), and never confuses the two: `push.message` is rendered verbatim, `saved` moves only the radio, the "runs" line is written only from the manifest and says "doesn't report this setting yet" for a core that predates the channel. Rejected: per-surface wiring; a 302 into OAuth for a refused page (sends assets and crawlers to GitHub); copying the `/api/` exemption; painting the radio from the manifest or the runs line from `saved`; reusing the pre-launch gate.](<redacted>.md) | instance / project visibility |
272
272
  | 0192 | [**A work category orients a human and authorises nothing** ([task 1003275](https://cloudbongos.com/builders#/task/1003275), goal 1000072, criterion `legibility-categories`). A goal gains a human CATEGORY from an owner-named set of eight (Builders Hall, Context Management, Government (Decision Making), Modules, Security, Economics, Legal, User/ABI Experience) so a person scanning the board can tell at a glance which goals are about money, which about the walls, which about the hall — today each goal's own words say so, but only if you read all thirty-eight. The DECISION is the constraint, not the feature: the owner said twice that this organisation has no hierarchical purpose and no effect on the agentic management of the work, and the system ALREADY has a load-bearing hierarchy (ADR 0086 — the scope_modules wall, rank floors, goal membership). The goal's own prose predicts the failure: a second thing that looks like a hierarchy gets mistaken for one within a release, and the mistake arrives as ONE plausible line in a gate ('skip Legal goals in the feed'), after which work is refused because of a label nobody meant as a permission. So: no authority, claim-eligibility, feed or dependency surface may read a category — enforced by the `scripts/gds/category-advisory-guard.js` fitness check, which scans every decision surface with comments stripped (a gate may CITE the rule, never branch on it) AND asserts the feature still exists, so a renamed-away subject fails loudly instead of passing vacuously (the ADR 0022 assurance-of-assurance finding). Uncategorised is LEGAL — nullable, no default, required by no write — because the criterion is explicit that Claude failing to place a goal is a designed prompt, not an error. A task stores no category and INHERITS its goal's through `tasks.goal_id`; a stored copy would be a second answer to one question. The set is a table, not an enum (runtime-extensible without a migration per addition) and retiring is `active = false`, never a DELETE, so a goal keeps rendering a label someone tidied away. The three route-rank pins are a tripwire: if one ever needs a WIDER gate than its neighbours, the label has grown into a permission. Deliberately NOT built: a per-category page or filtered navigation view — a chip is a label, a drill-down destination is a tier. Making a category gate anything is an owner decision and needs its own ADR first.](<redacted>.md) | GDS / goals |
273
+ | 0193 | [**Pause task-scoped box slices: a box serves its rank scope (the full repo for Metic+)** ([task 1003285](https://cloudbongos.com/builders#/task/1003285), owner decision from the 2026-08-25 dev-box efficiency review). Pauses, does not repeal, ADR 0148 / goal 1000051: `routes/box.js` skips the lifecycle-port `scopeKeys` resolution unless `BOX_TASK_SCOPE=on`, so `decideSourceAccess` takes its documented rank fallback — full repo for Metic+, starter surface below. WHY: the slice was defense-in-depth git cannot hard-enforce (ADR 0031 §6's honest limit) while its costs were real on the live fleet — sessions on a box that cannot see a module (or its nested CLAUDE.md) misdiagnose themselves as stuck, the scope machinery kept failing in the field (task 1002729's base-slice collapse), and every live box user is Metic+ anyway, so the slice narrowed trusted builders while newcomers were already gated by rank. UNCHANGED: live-rank allow/deny per request (ADR 0016), the provision floor and demotion clawback, the read-only per-request credential (ADR 0055 — push still 403s, re-verified on hardware in task 1003253), and sub-Metic starter boxes. `GET /box/scope` now reports `task_scoping_paused` so the hall/app can say why `task_scope` is false; the app task-picker's "scoped to `<modules>` and nothing else" copy overstates while paused (cosmetic). Revert = set the env flag; make-permanent = delete the resolution + a follow-up ADR.](<redacted>.md) | dev-box |
273
274
 
274
275
  > ⚠️ **Numbering collision (known):** two ADRs share `0008`. The numbers were assigned in parallel sessions before the lock could be enforced. The files are kept under their existing filenames to preserve git history, but the table disambiguates them as `0008-a` / `0008-b`. New ADRs should resume at `0040` to avoid further collisions. (`0024` is also a real on-disk duplicate: the cloneable-repo/local-first-memory ADR and the multi-agent-system-architecture ADR were both numbered `0024` in parallel; both keep their filenames, disambiguated as `0024-a` / `0024-b` like `0008`. Likewise `0025` ended up with two on-disk files — the offsite-backup ADR and the structured-criterion↔task-link ADR — both kept under their existing filenames and disambiguated as `0025-a` / `0025-b`; separately, the BFG ADR had also claimed `0025` on 2026-05-30 and yielded to `0026`. A third happened 2026-06-06: `0036` was claimed in parallel by the builders-subdomain ADR ([#726](https://example.com/builders#/task/726)) and this Discord-channels ADR ([#727](https://example.com/builders#/task/727)); the Discord-channels ADR yielded to `0037`. A fourth landed 2026-06-10: `0044` was claimed in parallel by the palette-replacement ADR and the per-box game-preview ADR ([#897](https://example.com/builders#/task/897)); both keep their filenames, disambiguated as `0044-a` / `0044-b` like `0008`. A fifth was caught pre-land 2026-06-13: `0050` was claimed in parallel by the device-flow-admission ADR ([#1000](https://example.com/builders#/task/1000)) and the sandbox-for-everyone ADR ([#1001](https://example.com/builders#/task/1001)); the latter yielded to `0052` before merging, so there is no `0050` duplicate. A sixth was caught pre-land 2026-06-21: the tool-agnostic-design-layer ADR ([#1375](https://example.com/builders#/task/1375)) first collided at `0079` with the value-gated-autonomous-engagement ADR, then at `0080` with the llm-cache-api-transport ADR (both part of the 0075–0080 LLM-cost-reduction series, landed on `main` in parallel); it yielded to `0081` before merging, so there is no `0079`/`0080` design-layer duplicate. A seventh landed 2026-06-23: `0084` was claimed in parallel by the auto-derived-per-task-rank-floor ADR and the remove-default-spend-cap ADR (task 1499); the rank-floor ADR landed on `main` first, so the spend-cap ADR yielded to `0085`, leaving no `0084` duplicate. An eighth landed 2026-06-27: `0095` was claimed in parallel by the borrowed-memory-and-retrieval-concepts ADR (PR #459, landed first) and the cross-agent-context-management ADR ([#1651](https://example.com/builders#/task/1651), PR #460); both keep their filenames, disambiguated as `0095-a` / `0095-b` like `0008`. These two are companions — `0095-a` adopts the memory/retrieval concepts and explicitly defers the grep-vs-semantic question to `0095-b`.)
@@ -1,12 +1,13 @@
1
1
  # Work on a task from your dev box
2
2
 
3
- > Your dev box is **task-scoped**: claim a task and the box pulls **only the code
4
- > that task needs** — the module(s) its work touches, on top of a small base
5
- > instance — never the whole repo. Your GitHub token never leaves the server; the
6
- > box fetches its slice through the control plane. See
7
- > [ADR 0148](../adr/<redacted>.md) (the scope model) and
8
- > [ADR 0150](../adr/<redacted>.md) (how the box
9
- > comes up).
3
+ > Your dev box serves your **rank scope**: Metic+ boxes carry the **whole repo**;
4
+ > newcomer (starter) boxes carry the safe starter surface. Task-scoped slices are
5
+ > **paused** per [ADR 0193](../adr/<redacted>.md) (the
6
+ > machinery from [ADR 0148](../adr/<redacted>.md) remains;
7
+ > `BOX_TASK_SCOPE=on` re-enables it). Your GitHub token never leaves the server —
8
+ > see "How git works on a box" below, and
9
+ > [ADR 0150](../adr/<redacted>.md) for how the
10
+ > box comes up.
10
11
 
11
12
  ## TL;DR
12
13
 
@@ -15,32 +16,48 @@ In your dev box's terminal:
15
16
  ```bash
16
17
  /builder-start # see what you can claim
17
18
  /builder-claim <N> # claim task N (locks it to your session)
18
- /builder-sync # pull task N's code onto the box now (re-scopes in place)
19
+ /builder-sync # pull the latest main onto the box right now
19
20
  # …work in /workspace…
20
21
  /builder-ship # smoke + review, then land + deploy — awards credits
21
22
  ```
22
23
 
23
- That's the whole loop. `/builder-sync` is the new step: it materializes the claimed
24
- task's code within seconds instead of waiting for the box's ~10-minute refresh.
24
+ That's the whole loop. `/builder-sync` pulls fresh code within seconds instead of
25
+ waiting for the box's ~10-minute refresh.
26
+
27
+ ## How git works on a box — read this BEFORE diagnosing "stuck"
28
+
29
+ The box holds **no stored git credential, ever** — and that is the design, not a
30
+ fault ([ADR 0055](../adr/<redacted>.md)):
31
+
32
+ - **Reads are server-mediated, per request.** Every remote git operation goes
33
+ through `/usr/local/bin/otb-git-credential`, which asks the control plane
34
+ (`GET /box/source-access`) for a **short-lived, read-only** credential. The
35
+ server checks your **live** rank + status on every single request — demotion or
36
+ deactivation cuts off the next fetch instantly.
37
+ - **`git push` always fails with 403 here — by design.** The read credential
38
+ cannot write. Landing code is the server's job: `/builder-ship` uploads your
39
+ work, opens the PR, and lands it. Never try to fix the 403 by embedding a
40
+ token or editing git config.
41
+ - **Source refreshes from `main` every ~10 minutes** (the `box-source-fetch`
42
+ cron; log at `/tmp/box-source-fetch.log`). `/builder-sync` runs the same fetch
43
+ on demand.
44
+ - **If a remote git op says `could not read Username`, or the sync log shows
45
+ 401:** the box's GDS session expired. Run `/builder-reauth`, then
46
+ `/builder-sync`. That is the whole fix.
47
+
48
+ The box writes these same facts to `/workspace/CLAUDE.local.md` on every sync, so
49
+ a Claude session on the box loads them automatically (the file is gitignored and
50
+ box-written — don't edit it).
25
51
 
26
52
  ## The model — what's on your box
27
53
 
28
- A task-scoped box carries two layers:
29
-
30
- 1. **The base instance** — always present, whatever you've claimed: the methodology
31
- (`CLAUDE.md`, `.claude/`), the build tooling (`scripts/`, `src/bongos/`),
32
- `docs/`, `infra/`, and the **test suite** (`tests/`). Enough for Claude to hold
33
- context and run the `/builder-*` CLI, and to run your module's tests.
34
- 2. **Your claimed task's code** — the module directories the task's goal scopes
35
- (`goals.scope_modules` → globs, [ADR 0086](../adr/<redacted>.md)). Claim a `dev-box` task → you get
36
- `modules/dev-box/`; claim a `game` task → you get the game world too. Nothing
37
- else.
38
-
39
- What you do **not** get: other builders' modules, the product/game runtime (unless
40
- your task is game-scoped), and — the point — **no GitHub credential on the box**.
41
- The box asks the control plane (`GET /box/source-access`) for its slice on each
42
- fetch; the server holds the token and returns a scoped, cone-mode sparse-checkout
43
- spec. Lose the claim or a rank, and the next fetch is cut off at the server.
54
+ - **Metic and above:** the full repo in `/workspace` — same tree as a laptop
55
+ clone, refreshed from `main` by the sync cron.
56
+ - **Below Metic (starter):** the starter surface — methodology (`CLAUDE.md`,
57
+ `.claude/`), build tooling (`scripts/`, `src/bongos/`), `docs/`, `infra/`,
58
+ `tests/`, and the sandbox-runnable product code. Rank, checked live by the
59
+ server, is what bounds a box — not the task you claimed
60
+ ([ADR 0193](../adr/<redacted>.md)).
44
61
 
45
62
  ## Step by step
46
63
 
@@ -51,11 +68,10 @@ If you haven't yet, run **`/builder-connect`** (or follow the
51
68
  box, then **`/builder-setup`** once on the box to sign the Bongos CLI in. The browser
52
69
  terminal drops you into `/workspace` — the one working folder.
53
70
 
54
- > **`bongos shell` / `bongos code` re-scope on connect (BV1.R101).** Connecting with
55
- > either verb runs the sync for you first, so you land in `/workspace` already scoped
56
- > to your active claim — no manual `/builder-sync` needed just to arrive. (The browser
57
- > terminal drops you straight into `/workspace`; run `/builder-sync` there if you
58
- > claimed or switched since your last sync.)
71
+ > **`bongos shell` / `bongos code` sync on connect (BV1.R101).** Connecting with
72
+ > either verb runs the source fetch for you first, so you land in `/workspace`
73
+ > already fresh. (The browser terminal drops you straight into `/workspace`; run
74
+ > `/builder-sync` there if you want the latest immediately.)
59
75
 
60
76
  ### 2. See what you can claim — `/builder-start`
61
77
 
@@ -64,21 +80,18 @@ claims. Pick one.
64
80
 
65
81
  ### 3. Claim it — `/builder-claim <N>`
66
82
 
67
- Locks task N to your session. **This is what drives your box's scope:** source
68
- access is computed from your **active claim** — its task → goal → `scope_modules`.
83
+ Locks task N to your session so no parallel session collides with your work.
69
84
 
70
- ### 4. Pull the code now — `/builder-sync`
85
+ ### 4. Freshen the code — `/builder-sync`
71
86
 
72
- Re-runs the box's source-fetch immediately, so `/workspace` re-scopes to task N's
73
- modules within seconds. (A ~10-minute cron does this on its own as a backstop — you
74
- just don't want to wait for it.) Off a box there's nothing to sync — your source is
75
- your local checkout — and the command tells you so.
87
+ Re-runs the box's source fetch immediately so `/workspace` is on the current
88
+ `main` before you branch. (The ~10-minute cron does this on its own as a
89
+ backstop.) Off a box there's nothing to sync — your source is your local
90
+ checkout — and the command tells you so.
76
91
 
77
92
  ### 5. Work — in `/workspace`
78
93
 
79
- `/workspace` now holds the base instance **plus** task N's module code. One folder:
80
- when you switch tasks it **re-scopes in place**, so there are no per-task folders to
81
- manage.
94
+ One folder, always. When you switch tasks, keep working in place.
82
95
 
83
96
  ### 6. Ship — `/builder-ship`
84
97
 
@@ -90,19 +103,20 @@ One box, one working folder, one active claim at a time (the server enforces
90
103
  one-claim-per-session). To move to a different task:
91
104
 
92
105
  1. `/builder-ship` (or `/builder-release`) the current claim.
93
- 2. `/builder-claim <M>` the new one.
94
- 3. `/builder-sync` — the box re-scopes to task M in place.
106
+ 2. `/builder-claim <M>` the new one — and keep working in `/workspace`.
95
107
 
96
108
  ## Notes + gotchas
97
109
 
98
- - **`tests/` is always in the base** — so a module's tests run on the box (they
99
- live in the top-level `tests/`, not inside `modules/<key>/`).
100
- - **Idle boxes rest.** A box with no activity is reclaimed after ~10–15 min; the
101
- next connect re-provisions a fresh one and re-fetches your scope. **Uncommitted
102
- work on a reaped box is lost** — ship or commit before you step away.
110
+ - **`tests/` is on every box** — a starter box can run the suite for the code it
111
+ holds (tests live in the top-level `tests/`, not inside `modules/<key>/`).
112
+ - **Idle boxes park, they don't die.** After ~2 hours of no activity the box is
113
+ **parked**: a snapshot is kept and the droplet is destroyed, so compute billing
114
+ stops and a wake restores your filesystem. Only weeks of staying parked lead to
115
+ reclaim (with a warning first). Still: **ship or commit before you step away** —
116
+ a snapshot is a safety net, not a workflow.
103
117
  - **`/builder-sync` is idempotent** — safe to re-run; it just does, on demand, what
104
118
  the cron does on its schedule.
105
- - **A fresh box comes up with the base already cloned.** If a box ever comes up
119
+ - **A fresh box comes up with the code already cloned.** If a box ever comes up
106
120
  *bare* (no code, no terminal), that's the packaging bug
107
121
  [ADR 0150](../adr/<redacted>.md) fixed on the
108
122
  control plane — flag it rather than working around it.
@@ -55,6 +55,17 @@ const REPO_ROOT = path.resolve(__dirname, '..', '..', '..');
55
55
  const PUBLIC_ORIGIN = process.env.GDS_PUBLIC_ORIGIN
56
56
  || (() => { try { return api.branding().domains.publicOrigin; } catch { return 'http://localhost:3000'; } })();
57
57
 
58
+ // Task-scoping PAUSE (task 1003285 / ADR 0193 — owner decision, 2026-08-25).
59
+ // While paused, the claim-derived slice is never resolved: scopeKeys stays
60
+ // undefined and decideSourceAccess takes its documented rank fallback — full
61
+ // repo for Metic+, the starter surface below. Rank floor, ADR 0016 live-rank
62
+ // checks, and the ADR 0055 read-only credential are all unchanged; only the
63
+ // module BREADTH stops narrowing. Paused by default; set BOX_TASK_SCOPE=on
64
+ // to re-enable the ADR 0148 claim-driven slice.
65
+ function taskScopingPaused() {
66
+ return String(process.env.BOX_TASK_SCOPE || '').trim().toLowerCase() !== 'on';
67
+ }
68
+
58
69
  // The instance's world name + dev-box apex + SSH zone come from the branding pack
59
70
  // (ADR 0062 §3), never a hardcoded host brand — a non-OTB instance uses its own
60
71
  // configured identity/domains. Passed into the PURE box-onboard renderers.
@@ -444,10 +455,12 @@ module.exports = function buildBoxRouter() {
444
455
  // - undefined → decideSourceAccess falls back to the RANK scope (the
445
456
  // pre-ADR-0148 behavior, so a port-less instance is unchanged).
446
457
  // NEVER fail the source fetch on a scope-read hiccup — degrade to rank scope.
458
+ // While task-scoping is paused (taskScopingPaused above), skip the claim
459
+ // resolution entirely: undefined scopeKeys IS the rank-fallback signal.
447
460
  const lifecycle = api.resolveOptional('lifecycle');
448
461
  let scopeKeys;
449
462
  let hasActiveClaims;
450
- if (lifecycle && typeof lifecycle.moduleScopeForActiveClaims === 'function') {
463
+ if (!taskScopingPaused() && lifecycle && typeof lifecycle.moduleScopeForActiveClaims === 'function') {
451
464
  try {
452
465
  scopeKeys = await lifecycle.moduleScopeForActiveClaims(req.builder.id);
453
466
  } catch (scopeErr) {
@@ -560,11 +573,13 @@ module.exports = function buildBoxRouter() {
560
573
  const status = builder ? builder.status : 'inactive';
561
574
 
562
575
  // Resolve the claim-driven module scope exactly as source-access does
563
- // (optional lifecycle port; degrade to rank scope on any hiccup).
576
+ // (optional lifecycle port; degrade to rank scope on any hiccup; skipped
577
+ // entirely while task-scoping is paused, so this read-only companion can
578
+ // never report a slice the real fetch would no longer grant).
564
579
  const lifecycle = api.resolveOptional('lifecycle');
565
580
  let scopeKeys;
566
581
  let hasActiveClaims;
567
- if (lifecycle && typeof lifecycle.moduleScopeForActiveClaims === 'function') {
582
+ if (!taskScopingPaused() && lifecycle && typeof lifecycle.moduleScopeForActiveClaims === 'function') {
568
583
  try {
569
584
  scopeKeys = await lifecycle.moduleScopeForActiveClaims(req.builder.id);
570
585
  } catch (scopeErr) {
@@ -593,8 +608,9 @@ module.exports = function buildBoxRouter() {
593
608
  }
594
609
  const meta = boxAccess.scopeMetadata({ decision, scopeKeys });
595
610
  // task_scope true only when a claim actually drove the breadth (scopeKeys is
596
- // an array). false → the rank fallback (no active claim / no lifecycle port).
597
- return res.json({ ...meta, task_scope: Array.isArray(scopeKeys) });
611
+ // an array). false → the rank fallback (no active claim / no lifecycle port /
612
+ // task-scoping paused — the paused flag says which, so the app can explain).
613
+ return res.json({ ...meta, task_scope: Array.isArray(scopeKeys), task_scoping_paused: taskScopingPaused() });
598
614
  } catch (err) {
599
615
  log.error('[gds] GET /box/scope', err);
600
616
  res.fail('box_scope_failed', { status: 500, message: 'internal error' });
@@ -1088,3 +1104,4 @@ module.exports = function buildBoxRouter() {
1088
1104
  // (tests/box_terminal_ssrf.mjs) without a server, DB, or network.
1089
1105
  module.exports.isSafeTerminalUrl = isSafeTerminalUrl;
1090
1106
  module.exports.isTunnelReadyStatus = isTunnelReadyStatus;
1107
+ module.exports.taskScopingPaused = taskScopingPaused;
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.369",
3
+ "version": "1.19.371",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.369",
9
+ "version": "1.19.371",
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.369",
3
+ "version": "1.19.371",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
package/src/module-api.js CHANGED
@@ -1168,8 +1168,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
1168
1168
  // landed since 1.19.367 with no explicit bump. run 32902864988. (task 1002620)
1169
1169
  // 1.19.369 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1170
1170
  // landed since 1.19.368 with no explicit bump. run 32903289072. (task 1002620)
1171
+ // 1.19.370 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1172
+ // landed since 1.19.369 with no explicit bump. run 32904231221. (task 1002620)
1173
+ // 1.19.371 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1174
+ // landed since 1.19.370 with no explicit bump. run 32904582252. (task 1002620)
1171
1175
  // ---------------------------------------------------------------------------
1172
- const CORE_VERSION = '1.19.369'; // CI auto-patch carrier (ADR 0161); see changelog above
1176
+ const CORE_VERSION = '1.19.371'; // CI auto-patch carrier (ADR 0161); see changelog above
1173
1177
 
1174
1178
  // A namespaced logger so a module's log lines are attributable + consistent.
1175
1179
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -0,0 +1,66 @@
1
+ // tests/box_task_scope_pause.mjs
2
+ //
3
+ // Regression guard for task 1003285 / ADR 0193 — the task-scoping PAUSE.
4
+ // The owner decision: a box serves its RANK scope (full repo for Metic+),
5
+ // not the claim-derived slice, unless BOX_TASK_SCOPE=on re-enables ADR 0148.
6
+ //
7
+ // Two layers, per the goal-1000077 lesson (a guard must assert the wiring,
8
+ // not just the helper): (1) taskScopingPaused()'s env semantics — paused by
9
+ // DEFAULT, only an explicit "on" re-enables; (2) the route source keeps BOTH
10
+ // lifecycle scope-resolution sites (source-access + /box/scope) behind the
11
+ // helper, and /box/scope keeps reporting task_scoping_paused — so deleting a
12
+ // gate or the report fails here instead of silently reviving the slice.
13
+ //
14
+ // Run: node --test tests/box_task_scope_pause.mjs
15
+
16
+ import { strict as assert } from 'node:assert';
17
+ import { test } from 'node:test';
18
+ import { createRequire } from 'node:module';
19
+ import fs from 'node:fs';
20
+ import path from 'node:path';
21
+ import { fileURLToPath } from 'node:url';
22
+
23
+ const require = createRequire(import.meta.url);
24
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
25
+ const { taskScopingPaused } = require('../modules/dev-box/routes/box.js');
26
+
27
+ const withEnv = (value, fn) => {
28
+ const prev = process.env.BOX_TASK_SCOPE;
29
+ if (value === undefined) delete process.env.BOX_TASK_SCOPE;
30
+ else process.env.BOX_TASK_SCOPE = value;
31
+ try { return fn(); }
32
+ finally {
33
+ if (prev === undefined) delete process.env.BOX_TASK_SCOPE;
34
+ else process.env.BOX_TASK_SCOPE = prev;
35
+ }
36
+ };
37
+
38
+ test('paused by DEFAULT — an instance that sets nothing serves the rank scope', () => {
39
+ withEnv(undefined, () => assert.equal(taskScopingPaused(), true));
40
+ withEnv('', () => assert.equal(taskScopingPaused(), true));
41
+ });
42
+
43
+ test('only an explicit "on" re-enables the ADR 0148 slice (trim + case-insensitive)', () => {
44
+ withEnv('on', () => assert.equal(taskScopingPaused(), false));
45
+ withEnv(' ON ', () => assert.equal(taskScopingPaused(), false));
46
+ });
47
+
48
+ test('anything that is not "on" stays paused — no accidental re-enable', () => {
49
+ for (const v of ['off', 'true', '1', 'yes', 'paused', 'On!']) {
50
+ withEnv(v, () => assert.equal(taskScopingPaused(), true, `value ${JSON.stringify(v)} must stay paused`));
51
+ }
52
+ });
53
+
54
+ test('both lifecycle scope-resolution sites stay behind the pause gate', () => {
55
+ const src = fs.readFileSync(path.join(ROOT, 'modules', 'dev-box', 'routes', 'box.js'), 'utf8');
56
+ const gated = src.match(/!taskScopingPaused\(\) && lifecycle && typeof lifecycle\.moduleScopeForActiveClaims === 'function'/g) || [];
57
+ const resolutionSites = src.match(/typeof lifecycle\.moduleScopeForActiveClaims === 'function'/g) || [];
58
+ assert.equal(resolutionSites.length, 2, 'expected exactly the two known resolution sites (source-access + /box/scope); a new one must take the gate too');
59
+ assert.equal(gated.length, 2, 'every moduleScopeForActiveClaims resolution must be gated by !taskScopingPaused()');
60
+ });
61
+
62
+ test('/box/scope keeps telling the truth about the pause', () => {
63
+ const src = fs.readFileSync(path.join(ROOT, 'modules', 'dev-box', 'routes', 'box.js'), 'utf8');
64
+ assert.ok(src.includes('task_scoping_paused: taskScopingPaused()'),
65
+ '/box/scope must report task_scoping_paused so the hall/app can explain a false task_scope');
66
+ });