@bongos/core 1.19.369 → 1.19.370
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 +23 -13
- package/docs/adr/0193-pause-task-scoped-box-slices.md +60 -0
- package/docs/adr/README.md +1 -0
- package/modules/dev-box/routes/box.js +22 -5
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +3 -1
- package/tests/box_task_scope_pause.mjs +66 -0
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.370",
|
|
6
|
+
"core_contract": "1.19.370",
|
|
7
|
+
"source_commit": "ebdccda3c80527bbbaaf2aa022f22c30b8353d93",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-
|
|
9
|
+
"built_at": "2026-08-25T22:03:32.779Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 334,
|
|
13
13
|
"agent_docs_stubbed": 22,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1544,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1900,
|
|
20
|
+
"tree_sha256": "8d54fad99843ef1feeef5e52a4e23dd1dca2c6f5ba1e72eff5ce6aee06684aa3",
|
|
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": "
|
|
1470
|
+
"sha256": "cf4a81346983c4247f711855d8eae3939b3cbeb37b12ad7cf2449d2e896b3ade"
|
|
1466
1471
|
},
|
|
1467
1472
|
{
|
|
1468
1473
|
"path": "docs/api-reference.md",
|
|
@@ -2942,7 +2947,7 @@
|
|
|
2942
2947
|
{
|
|
2943
2948
|
"path": "modules/dev-box/routes/box.js",
|
|
2944
2949
|
"mode": "0000644",
|
|
2945
|
-
"sha256": "
|
|
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": "
|
|
4905
|
+
"sha256": "9dff8f392336cdd431ed666c04b21e660115a6fdba3040fd0f6c280aafbfe0a7"
|
|
4901
4906
|
},
|
|
4902
4907
|
{
|
|
4903
4908
|
"path": "package.json",
|
|
4904
4909
|
"mode": "0000644",
|
|
4905
|
-
"sha256": "
|
|
4910
|
+
"sha256": "416d8ecc99b8e45e6d71bda6f0be7f1a57fdb24cd1a08f11093184e4b8e8622d"
|
|
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": "
|
|
6795
|
+
"sha256": "4844a8a3d51f23328f5c31766f10a194579f779b225d024bdb20f62f0ee4f0c1"
|
|
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.
|
package/docs/adr/README.md
CHANGED
|
@@ -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`.)
|
|
@@ -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
|
-
|
|
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.
|
|
3
|
+
"version": "1.19.370",
|
|
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.370",
|
|
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.370",
|
|
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,10 @@ 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)
|
|
1171
1173
|
// ---------------------------------------------------------------------------
|
|
1172
|
-
const CORE_VERSION = '1.19.
|
|
1174
|
+
const CORE_VERSION = '1.19.370'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
1173
1175
|
|
|
1174
1176
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
1175
1177
|
// 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
|
+
});
|