@bongos/core 1.19.831 → 1.19.832
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 +19 -14
- package/docs/adr/0300-a-missing-protected-surface-registry-is-a-broken-install.md +109 -0
- package/docs/adr/README.md +1 -0
- package/docs/module-api-changelog.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/bongos/permission-path-check.js +17 -2
- package/src/module-api.js +1 -1
- package/tests/government_protected_surfaces.mjs +49 -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.832",
|
|
6
|
+
"core_contract": "1.19.832",
|
|
7
|
+
"source_commit": "c8e1d760c29ed41a5d66af6b1c4f9936a7fd0f32",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-17T15:
|
|
9
|
+
"built_at": "2026-09-17T15:29:22.894Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 492,
|
|
13
13
|
"agent_docs_stubbed": 26,
|
|
14
14
|
"functional_verbatim": 2244,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2762,
|
|
20
|
+
"tree_sha256": "0795b0e4670053ed5c638dac4719252988b0dbfd0d863dafef3df5b466a11f45",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -1984,10 +1984,15 @@
|
|
|
1984
1984
|
"mode": "0000644",
|
|
1985
1985
|
"sha256": "f52bf2568d02d5cf8589dc394dc76cbf0ea3a7ba193275f300e320c2ab64a51c"
|
|
1986
1986
|
},
|
|
1987
|
+
{
|
|
1988
|
+
"path": "docs/adr/0300-a-missing-protected-surface-registry-is-a-broken-install.md",
|
|
1989
|
+
"mode": "0000644",
|
|
1990
|
+
"sha256": "7c5d4c49dc98d37a8a41aa0cddd158c3a46a60cdb4e79b018d68c6b5222b7e79"
|
|
1991
|
+
},
|
|
1987
1992
|
{
|
|
1988
1993
|
"path": "docs/adr/README.md",
|
|
1989
1994
|
"mode": "0000644",
|
|
1990
|
-
"sha256": "
|
|
1995
|
+
"sha256": "8af8b8c5ea9b368bb790280eadd167b99ed9f864350e101ba18a4b0ec8f5ed5e"
|
|
1991
1996
|
},
|
|
1992
1997
|
{
|
|
1993
1998
|
"path": "docs/api-reference.md",
|
|
@@ -2552,7 +2557,7 @@
|
|
|
2552
2557
|
{
|
|
2553
2558
|
"path": "docs/module-api-changelog.md",
|
|
2554
2559
|
"mode": "0000644",
|
|
2555
|
-
"sha256": "
|
|
2560
|
+
"sha256": "02d7156a9ee2a022e71835b03c120059c44b36cf9358085bf2c2d1fb91a72bd4"
|
|
2556
2561
|
},
|
|
2557
2562
|
{
|
|
2558
2563
|
"path": "docs/modules-contract.md",
|
|
@@ -7837,12 +7842,12 @@
|
|
|
7837
7842
|
{
|
|
7838
7843
|
"path": "package-lock.json",
|
|
7839
7844
|
"mode": "0000644",
|
|
7840
|
-
"sha256": "
|
|
7845
|
+
"sha256": "f833d910fdbed07c2c2a6a1597d5d6315780e4633c6015bde77629b7682f8c8d"
|
|
7841
7846
|
},
|
|
7842
7847
|
{
|
|
7843
7848
|
"path": "package.json",
|
|
7844
7849
|
"mode": "0000644",
|
|
7845
|
-
"sha256": "
|
|
7850
|
+
"sha256": "6fd9cd7067b463895344e6de0710ce6eda471f96d659161c56f9ce3cc9c0edb1"
|
|
7846
7851
|
},
|
|
7847
7852
|
{
|
|
7848
7853
|
"path": "public-docs/index.html",
|
|
@@ -9592,7 +9597,7 @@
|
|
|
9592
9597
|
{
|
|
9593
9598
|
"path": "src/bongos/permission-path-check.js",
|
|
9594
9599
|
"mode": "0000644",
|
|
9595
|
-
"sha256": "
|
|
9600
|
+
"sha256": "75264cfa68b2a8344c38565d96d16c28e101d286ad0b5794baa83ab774269d74"
|
|
9596
9601
|
},
|
|
9597
9602
|
{
|
|
9598
9603
|
"path": "src/bongos/platform-visibility-gate.js",
|
|
@@ -9762,7 +9767,7 @@
|
|
|
9762
9767
|
{
|
|
9763
9768
|
"path": "src/module-api.js",
|
|
9764
9769
|
"mode": "0000644",
|
|
9765
|
-
"sha256": "
|
|
9770
|
+
"sha256": "8e0d57c0e24d78b95565b3c81fdae6ca50647ad6f84ea337eed213109594056e"
|
|
9766
9771
|
},
|
|
9767
9772
|
{
|
|
9768
9773
|
"path": "src/module-loader/catalog.js",
|
|
@@ -11377,7 +11382,7 @@
|
|
|
11377
11382
|
{
|
|
11378
11383
|
"path": "tests/government_protected_surfaces.mjs",
|
|
11379
11384
|
"mode": "0000644",
|
|
11380
|
-
"sha256": "
|
|
11385
|
+
"sha256": "1d8502e8d25eedb54ed0fcbe3ce1a0fe87495d03bd8bda7322e55d6004669a63"
|
|
11381
11386
|
},
|
|
11382
11387
|
{
|
|
11383
11388
|
"path": "tests/government_rank_sync.mjs",
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# ADR 0300 — A missing protected-surface registry is a broken install, not a module opt-out
|
|
2
|
+
|
|
3
|
+
**Status:** accepted · **Date:** 2026-09-17 · **Task:** [task 1003662](https://cloudbongos.com/builders#/task/1003662) (goal 1000091 — BONGOS-V2)
|
|
4
|
+
|
|
5
|
+
**Related:** [ADR 0043](0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md) (the protected-surface boundary this registry enforces) · [ADR 0083](0083-modular-architecture-strangler-build-plan.md) (core never depends on a module's behavior) · [ADR 0108](0108-instance-composition-configurable-root.md) §1 (the core is consumed as a versioned npm package, and `modules/` ships inside it)
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
[task 1003662](https://cloudbongos.com/builders#/task/1003662) was filed as a bug with a
|
|
10
|
+
concrete remedy attached:
|
|
11
|
+
|
|
12
|
+
> `require('./module-scope-map')` throws on any checkout where
|
|
13
|
+
> `modules/government/protected-surfaces.json` is absent … This breaks any checkout that has
|
|
14
|
+
> disabled or not yet materialized the government module … Make `module-scope-map.js` degrade
|
|
15
|
+
> gracefully (empty protected set + a loud warning) when the government module isn't present,
|
|
16
|
+
> matching the `resolveOptional` pattern used elsewhere for optional module capabilities.
|
|
17
|
+
|
|
18
|
+
The throw is real and reproduces exactly as described. Moving the registry aside and requiring
|
|
19
|
+
the kernel file gives:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
protected-surface registry unreadable at <root>/modules/government/protected-surfaces.json:
|
|
23
|
+
ENOENT: no such file or directory. Refusing to run with an empty protected set (ADR 0043).
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Everything after that first sentence is wrong, in four separate ways — and the remedy it
|
|
27
|
+
proposes would disable the ADR 0043 boundary on exactly the installs least able to notice.
|
|
28
|
+
|
|
29
|
+
**1. The throw is not in the file the task names.** `module-scope-map.js` contains no `throw`
|
|
30
|
+
and no file read. It does `require('./permission-path-check')` at line 39, and
|
|
31
|
+
`permission-path-check.js`'s `loadRegistry()` throws at module scope. Any "fix" applied to the
|
|
32
|
+
file in the title would be applied to the wrong file; the behavior is owned by its sibling.
|
|
33
|
+
|
|
34
|
+
**2. The throw is a written contract, not an oversight.** `permission-path-check.js` states it
|
|
35
|
+
in its own header, in capitals, above the code:
|
|
36
|
+
|
|
37
|
+
> FAIL-CLOSED: a missing/empty/malformed registry THROWS. It never degrades to "nothing is
|
|
38
|
+
> protected" — an unreadable registry is a broken install, not an opted-out module.
|
|
39
|
+
|
|
40
|
+
The remedy asks for the one behavior the header names and refuses. That header also pre-answers
|
|
41
|
+
the architectural worry the title gestures at (a kernel file reaching into `modules/`): the read
|
|
42
|
+
is `fs`, never `require`, because ADR 0083's one-way rule forbids core from requiring a module's
|
|
43
|
+
*code* — and this is not code, it is a static declarative data file. The sibling trust primitive
|
|
44
|
+
`route-rank-check.js` reads `modules/*/routes/*.js` off disk the same way. Code still flows one
|
|
45
|
+
way; nothing in core requires the module.
|
|
46
|
+
|
|
47
|
+
**3. Disabling the government module cannot produce the ENOENT.** Module enablement is a
|
|
48
|
+
runtime flag, not a file deletion — disabling a module leaves its files on disk untouched. The
|
|
49
|
+
registry read never consults enablement, deliberately: per the same header, a module's
|
|
50
|
+
enable/disable state must never be something the kernel depends on. So the "disabled" half of
|
|
51
|
+
the premise describes a state that cannot occur.
|
|
52
|
+
|
|
53
|
+
**4. The "not yet materialized" half does not occur either, because the file ships with the
|
|
54
|
+
core.** `package.json` declares no `files[]` allowlist, so the whole directory ships.
|
|
55
|
+
`npm pack --dry-run` on this commit puts `modules/government/protected-surfaces.json` in the
|
|
56
|
+
tarball at 13,274 bytes, one of 3,419 files. Per [ADR 0108](0108-instance-composition-configurable-root.md)
|
|
57
|
+
§1 the core is consumed as a versioned npm dependency and `resolveCoreRoot()` resolves the core
|
|
58
|
+
package's own `modules/` inside `node_modules`. You cannot have `@bongos/core` without this
|
|
59
|
+
file. If it is absent, the install is damaged — a partial extract, a lossy copy, a `.gitignore`
|
|
60
|
+
that ate a JSON file — and that is a fact worth surfacing loudly, not smoothing over.
|
|
61
|
+
|
|
62
|
+
**The `resolveOptional` precedent does not transfer.** `resolveOptional` is real and used in
|
|
63
|
+
roughly a dozen places, but it is the module **seam/port resolver**: `api.resolveOptional('grade')`
|
|
64
|
+
lets one module degrade when another module's *runtime capability* is absent, which is exactly
|
|
65
|
+
right for an optional capability. A protected-surface registry is not a capability being
|
|
66
|
+
resolved at a seam. It is the static data the trust boundary is made of, read synchronously from
|
|
67
|
+
disk by enforcers that run where there is no server and no database — a git hook on a laptop, a
|
|
68
|
+
CI job. Borrowing the seam pattern here reads as consistency, but it silently converts "the
|
|
69
|
+
boundary is unreadable" into "there is no boundary".
|
|
70
|
+
|
|
71
|
+
## Decision
|
|
72
|
+
|
|
73
|
+
**Fail-closed stays. The registry is not optional, and a missing registry is a broken install.**
|
|
74
|
+
|
|
75
|
+
Concretely:
|
|
76
|
+
|
|
77
|
+
1. **No behavior change to `permission-path-check.js` or `module-scope-map.js`.** The throw is
|
|
78
|
+
correct, its placement is correct, and the header already documents why.
|
|
79
|
+
2. **The task's described remedy is rejected**, on the grounds above — chiefly that an empty
|
|
80
|
+
protected set is not a degraded mode of a trust boundary, it is the absence of one.
|
|
81
|
+
3. **The error message gains a remediation line.** This is the one genuine defect the report
|
|
82
|
+
surfaced: the message states the refusal and cites ADR 0043, but never tells the reader what
|
|
83
|
+
to *do*, which is precisely how a reader concludes the module must be optional and files this
|
|
84
|
+
task. It now names the likely cause and the fix.
|
|
85
|
+
|
|
86
|
+
The asymmetry is the whole point. A false "nothing is protected" is silent, permanent, and
|
|
87
|
+
grants authority; a false "this install is broken" is loud, immediate, and grants nothing. Only
|
|
88
|
+
one of those is safe to be wrong about.
|
|
89
|
+
|
|
90
|
+
## Consequences
|
|
91
|
+
|
|
92
|
+
**A damaged install fails loudly at require time, and now says what to do about it.** That was
|
|
93
|
+
already true of the first half; the second half is new. The reader who hits this next gets
|
|
94
|
+
pointed at reinstalling the core rather than at weakening the check.
|
|
95
|
+
|
|
96
|
+
**The report will recur in some form, and that is acceptable.** Anyone meeting this ENOENT
|
|
97
|
+
cold sees a kernel file reaching into `modules/` and reasonably suspects a layering bug. This
|
|
98
|
+
ADR is the durable answer to that suspicion, which is why it exists rather than a won't-fix
|
|
99
|
+
note: the next agent to wonder can be pointed at a number.
|
|
100
|
+
|
|
101
|
+
**If a legitimate need for a core-only checkout without `modules/` ever appears, this decision
|
|
102
|
+
is the thing to revisit** — and the revisit is "where should the registry live", not "should it
|
|
103
|
+
be optional". Relocating the canonical data into `src/` and having the government module
|
|
104
|
+
contribute to it would preserve fail-closed while removing the cross-directory read. Nothing
|
|
105
|
+
today needs that, and doing it speculatively would move a trust primitive for no gain.
|
|
106
|
+
|
|
107
|
+
**What this does not decide:** whether other `modules/` reads from core (`route-rank-check.js`
|
|
108
|
+
reading `modules/*/routes/*.js`) should follow the same rule. They share the mechanism and the
|
|
109
|
+
justification, but each is its own trust surface and none is currently in question.
|
package/docs/adr/README.md
CHANGED
|
@@ -391,3 +391,4 @@ This keeps the decision history honest and traceable.
|
|
|
391
391
|
| 0297 | [**A task’s creator may retire it, and that is the ownership axis’s first route** ([task 1002443](https://cloudbongos.com/builders#/task/1002443), goal 1000095 — owner request 2026-07-25: *"the creator of that task should be able to abandon that task directly inside of the UI"*). Attribution itself shipped earlier and elsewhere (`core_227`, [task 1003278](https://cloudbongos.com/builders#/task/1003278)); what had not moved is that **retiring a task was a rank power and nothing else** — `task.abandon` floors at `metic` (ADR 0152), so a sub-Metic builder could file a task and then could not withdraw their own proposal. The model already had the vocabulary and had never used it: ADR 0151 §3 defines an **ownership axis** where a `scope:'own'` key authorizes nothing until a handler supplies the row's owner, R100 made `requirePermission` refuse such a key outright so `.own` can never degrade into `.any`, and it recorded that **zero call sites passed one**. This is that axis’s first real consumer. **Decision: two authorities, only one rank-shaped** — `task.abandon` (metic, any task) OR new `task.abandon.own` (xenos, a task you created, while it is still backlog/ready/blocked). Route becomes `requireBuilder` + an in-handler decision (the `POST /goals/:id/members` shape), pin `metic+archon`→`any-builder` so it now catches an accidental **tightening** as loudly as a gate removal. **The two facts resolve in two places on purpose:** the RANK fact is request-scoped and resolves before the transaction (uncached, ADR 0016); the OWNER fact is a column that can change under us, so `created_by` joins `abandonTask`’s existing `FOR UPDATE` SELECT and a new optional `authorize` callback decides under that lock. Reading `created_by` in the route would be a TOCTOU window; resolving permissions inside the transaction would hold a row lock across a second pool acquire. The decision is a PURE module (`abandon-authz.js`, the `goal-route-authz.js` precedent) so every branch is pinned DB-free and the hall hint and server verdict provably read one rule. **A creator’s window stops at the grader** — completed/confirmed stay cross-owner, because past the grader a retire is a ledger correction, not an author changing their mind; nothing is subtracted from `task.abandon`, pinned by a test. `counterpart: ['task.abandon']` is declared not derived, verb `act` not `manage` — `manage` would silently dominate every future narrower `task.*.own` key (the verb-blind escalation task 1003195 closed). **A null creator belongs to nobody and that is load-bearing:** ~1,357 pre-`core_227` tasks plus seed/restock/discord vectors are unattributed, so the owner comparison is the government resolver’s own — injected, never re-implemented, since a duplicated ownership comparison is how one quietly widens — and one slip would open every historical task to a xenos grant at once. Fail-closed throughout: a missing `government` port, a throwing resolver, and anything but an explicit `{ok:true}` all deny, and `authorize` runs BEFORE the idempotent already-abandoned return and the shipped/active-claim guards so an unauthorized caller cannot probe a row through a 200. No guard removed. Does NOT widen edit/promote/dependency (still Metic), does not touch `task.merge`, and does not backfill attribution — the control simply does not appear on tasks predating `core_227`.](0297-a-tasks-creator-may-retire-it.md) | authority / lifecycle / ownership |
|
|
392
392
|
| 0298 | [**The grader gets a reader lens: advisory, scoped to copy-bearing diffs, and it waits on a written voice** ([task 1003937](https://cloudbongos.com/builders#/task/1003937), goal 1000095 — Working area 6; owner decision 2026-09-17). Every artist review the cascade files opened with *“REVIEW THE GRADER … coach it”*, and it could not be followed: the v4 panel runs narc, quality, hacker and efficiency — completion, code quality, security, runtime cost — and not one of them reads a sentence for whether it is true, clear, in the project’s voice or worth a person’s attention. Four consecutive reviews hit the same wall and all four graded ships deservedly PASSED on the axes that were measured; the gate is not lenient, it is not pointed at this. `grader-prompt.js` already injects the craft’s written responsibility, but only when `discipline === ‘artist’`, so the unjudged case is copy shipped by someone whose craft is not the copy. **Decision: a fifth ADVISORY `reader` worker, on three settled terms.** It runs only where `modules/lifecycle/cascade.js`’s existing `hasCopyOrVisualWork` says the ship is copy-bearing — the same predicate the after-the-fact review uses, so the two can never disagree about coverage, and every other ship costs exactly what it costs today. It records `issues[]` and a `copy_score` and can never verdict `fail` or drag the panel to `concerns`, because [ADR 0241](0241-the-artist-gate-is-a-per-project-deploy-gate-that-reads-a-state.md) put the artist’s judgement on the DEPLOY and off the ship path; gating would need that ADR amended first. And it is NOT BUILT until the instance writes a voice guide — today the whole written standard is one dash rule in `DESIGN.md`, a tagline, a registry of which 1,860 strings exist, and one responsibility sentence, so a lens built now would record taste and call it a finding. Until the reader exists, STEP 1 asks the artist to WRITE DOWN what the grade could not see rather than to coach a lens that is not listening — followable today, and the corpus both the guide and the lens get written from. Nothing in the rubric, the active panel or any existing worker changes.](0298-the-reader-lens-is-advisory-and-waits-on-a-voice-guide.md) | grading / the artist’s loop / voice |
|
|
393
393
|
| 0299 | [**The nod queue stays scoped to routed filings; the backlog walk is the widening** ([task 1003748](https://cloudbongos.com/builders#/task/1003748), goal 1000089). `listRoutedAwaitingNod` filters `origin='route'`, and on 2026-09-08 showed 11 of 232 backlog rows. Re-measured after `/backlog-review` (task 1003746) shipped: 115 backlog rows = 109 dep-gated + 2 spikes (not a person's decision) + 1 stranded + 3 awaiting a nod, all four walked by `/backlog-review`. **Decision: do not widen the card.** The omitted population already has a walkable review with the dependency predicate any widening needed; the copy says *reported* work, so the name is honest; a card slice would be a second copy of the walk's four-bucket partition. Revisit if the un-routed nod bucket outgrows a daily walk (then a count + link, derived from the same partition) or if the walk stops being run.](0299-the-nod-queue-stays-scoped-to-routed-filings.md) | ideas / backlog / attention |
|
|
394
|
+
| 0300 | [**A missing protected-surface registry is a broken install, not a module opt-out** ([task 1003662](https://cloudbongos.com/builders#/task/1003662), goal 1000091). Filed as a bug with a remedy attached: `require('./module-scope-map')` throws when `modules/government/protected-surfaces.json` is absent, so make it degrade to an empty protected set + a warning, matching `resolveOptional`. The throw reproduces, and the rest is wrong four times over. **The throw is not in the named file** — `module-scope-map.js` has no throw and no file read; it requires `permission-path-check.js`, whose `loadRegistry()` throws at module scope. **It is a written contract, not an oversight** — that file's header says FAIL-CLOSED in capitals and already pre-answers the layering worry (the read is `fs`, never `require`, so ADR 0083's one-way rule holds; static data, not code; `route-rank-check.js` reads `modules/*/routes/*.js` the same way). **Disabling the module cannot cause it** — enablement is a runtime flag, never a file deletion, and the read never consults it. **"Not yet materialized" cannot either** — no `files[]` allowlist, so `npm pack --dry-run` puts the 13,274-byte registry in the tarball; per ADR 0108 §1 you cannot have `@bongos/core` without it. The `resolveOptional` precedent does not transfer: that is the module **seam/port** resolver for a missing runtime *capability*, whereas this is the static data the boundary is made of, read where there is no server and no DB (a git hook, a CI job) — borrowing it converts "the boundary is unreadable" into "there is no boundary". **Decision: fail-closed stays, no behavior change, the remedy is rejected — and the one genuine defect is fixed: the message now names the cause and the fix** (reinstall the core; do not empty the set), because stating a refusal without a remedy is exactly how a reader concludes the module is optional and files this task. The asymmetry is the point: a false "nothing is protected" is silent, permanent and grants authority; a false "this install is broken" is loud and grants nothing. Revisit only if a core-only checkout without `modules/` becomes legitimate — and then the question is *where the registry lives*, not whether it is optional.](0300-a-missing-protected-surface-registry-is-a-broken-install.md) | governance / permissions |
|
|
@@ -2136,5 +2136,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2136
2136
|
landed since 1.19.829 with no explicit bump. run 35235005422. (task 1002620)
|
|
2137
2137
|
1.19.831 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2138
2138
|
landed since 1.19.830 with no explicit bump. run 35238952391. (task 1002620)
|
|
2139
|
+
1.19.832 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2140
|
+
landed since 1.19.831 with no explicit bump. run 35240512571. (task 1002620)
|
|
2139
2141
|
---------------------------------------------------------------------------
|
|
2140
2142
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.832",
|
|
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.832",
|
|
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.832",
|
|
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",
|
|
@@ -73,16 +73,31 @@ const RANK_TIER = { xenos: 1, thetes: 2, metic: 3, archon: 4 };
|
|
|
73
73
|
// hook.
|
|
74
74
|
const REGISTRY_PATH = path.join(__dirname, '..', '..', 'modules', 'government', 'protected-surfaces.json');
|
|
75
75
|
|
|
76
|
+
// WHY THE REFUSAL NAMES A FIX: the FAIL-CLOSED note above tells the reader what
|
|
77
|
+
// this throw refuses to do, but nothing told them what to DO — and a reader who
|
|
78
|
+
// meets this cold sees a core file reaching into modules/ and concludes the
|
|
79
|
+
// module must be optional. That is not hypothetical: task 1003662 was filed
|
|
80
|
+
// asking for exactly the degrade-to-empty-set this refuses, on that reading. The
|
|
81
|
+
// registry SHIPS INSIDE @bongos/core (modules/ is in the published tarball, ADR
|
|
82
|
+
// 0108 §1), so it cannot be absent on an intact install and disabling the
|
|
83
|
+
// government module cannot remove it — a module's enable/disable state is a
|
|
84
|
+
// runtime flag, never a file deletion. Absent therefore means the install is
|
|
85
|
+
// damaged, and reinstalling is the fix. Full reasoning: ADR 0300.
|
|
86
|
+
const REMEDIATION =
|
|
87
|
+
'modules/government/protected-surfaces.json ships inside @bongos/core, so an intact install always has it '
|
|
88
|
+
+ '— this is a damaged install (partial extract or lossy copy), not a disabled module. Reinstall/repair the '
|
|
89
|
+
+ 'core package; do NOT degrade to an empty protected set (ADR 0043, ADR 0300).';
|
|
90
|
+
|
|
76
91
|
function loadRegistry(file) {
|
|
77
92
|
let raw;
|
|
78
93
|
try {
|
|
79
94
|
raw = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
80
95
|
} catch (e) {
|
|
81
|
-
throw new Error(`protected-surface registry unreadable at ${file}: ${e.message}. Refusing to run with an empty protected set (ADR 0043)
|
|
96
|
+
throw new Error(`protected-surface registry unreadable at ${file}: ${e.message}. Refusing to run with an empty protected set (ADR 0043). ${REMEDIATION}`);
|
|
82
97
|
}
|
|
83
98
|
const surfaces = Array.isArray(raw && raw.surfaces) ? raw.surfaces : null;
|
|
84
99
|
if (!surfaces || surfaces.length === 0) {
|
|
85
|
-
throw new Error(`protected-surface registry ${file} declares no surfaces. Refusing to run with an empty protected set (ADR 0043)
|
|
100
|
+
throw new Error(`protected-surface registry ${file} declares no surfaces. Refusing to run with an empty protected set (ADR 0043). ${REMEDIATION}`);
|
|
86
101
|
}
|
|
87
102
|
const defaultFloor = raw.floor;
|
|
88
103
|
const defaultPermission = raw.permission;
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.832'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -742,6 +742,55 @@ test('a missing registry fails CLOSED — it never degrades to "nothing is prote
|
|
|
742
742
|
}, /unreadable|Refusing/i);
|
|
743
743
|
});
|
|
744
744
|
|
|
745
|
+
// The refusal text, not just the refusal. Run the kernel load in a cold subprocess
|
|
746
|
+
// with a patched read and hand back stderr, so a message assertion sees what an
|
|
747
|
+
// operator would actually read.
|
|
748
|
+
function refusalStderr(patch) {
|
|
749
|
+
try {
|
|
750
|
+
execFileSync(process.execPath, ['-e',
|
|
751
|
+
"const fs=require('node:fs');const real=fs.readFileSync;" + patch
|
|
752
|
+
+ "require('./src/bongos/permission-path-check');",
|
|
753
|
+
], { cwd: ROOT, stdio: 'pipe' });
|
|
754
|
+
} catch (e) {
|
|
755
|
+
return String(e.stderr || '');
|
|
756
|
+
}
|
|
757
|
+
return '';
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// WHY ASSERT ON THE WORDS (task 1003662, ADR 0300). The sibling probe above proves
|
|
761
|
+
// the load REFUSES; it cannot tell whether the refusal is actionable. It was not:
|
|
762
|
+
// the message stated the refusal and cited ADR 0043 but never named a fix, and a
|
|
763
|
+
// reader who meets it cold sees a core file reaching into modules/ and concludes
|
|
764
|
+
// the module must be optional. Task 1003662 was filed on exactly that reading,
|
|
765
|
+
// asking for the degrade-to-empty-set this refuses. The remediation line is the
|
|
766
|
+
// fix for that, so it is part of the contract and not decoration.
|
|
767
|
+
//
|
|
768
|
+
// The EMPTY-surfaces branch is covered here for the first time. Until now only the
|
|
769
|
+
// unreadable branch had a probe, so `surfaces: []` — the shape an out-of-band
|
|
770
|
+
// registry write most plausibly produces — could have degraded silently.
|
|
771
|
+
test('both refusal branches name the fix, not just the refusal (ADR 0300)', () => {
|
|
772
|
+
const gone = refusalStderr(
|
|
773
|
+
"fs.readFileSync=(p,...r)=>{ if(String(p).includes('protected-surfaces.json')) throw new Error('gone'); return real(p,...r); };",
|
|
774
|
+
);
|
|
775
|
+
const empty = refusalStderr(
|
|
776
|
+
"fs.readFileSync=(p,...r)=>(String(p).includes('protected-surfaces.json') ? '{\"surfaces\":[]}' : real(p,...r));",
|
|
777
|
+
);
|
|
778
|
+
|
|
779
|
+
assert.match(empty, /declares no surfaces/,
|
|
780
|
+
'an empty surfaces[] must refuse too — not only an unreadable file');
|
|
781
|
+
|
|
782
|
+
for (const [name, text] of [['unreadable', gone], ['empty', empty]]) {
|
|
783
|
+
assert.match(text, /Refusing to run with an empty protected set/,
|
|
784
|
+
`the ${name} branch must still state the refusal`);
|
|
785
|
+
assert.match(text, /ships inside @bongos\/core/,
|
|
786
|
+
`the ${name} branch must say the registry ships with the core (so absent means damaged, not disabled)`);
|
|
787
|
+
assert.match(text, /[Rr]einstall/,
|
|
788
|
+
`the ${name} branch must name the action that fixes it`);
|
|
789
|
+
assert.match(text, /ADR 0300/,
|
|
790
|
+
`the ${name} branch must cite the ADR that rejected degrading to an empty set`);
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
|
|
745
794
|
// ---------- the matcher, driven by registry data ----------
|
|
746
795
|
|
|
747
796
|
test('a known path is protected at the right floor; an unknown path is not', () => {
|