@bongos/core 1.19.917 → 1.19.918
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 +20 -15
- package/docs/adr/0314-module-protection-is-per-path-not-per-module.md +10 -5
- package/docs/adr/0318-listing-a-file-for-task-affinity-is-not-owning-it.md +101 -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/module-scope-map.js +14 -4
- package/src/module-api.js +1 -1
- package/tests/module-scope-map.mjs +100 -7
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.918",
|
|
6
|
+
"core_contract": "1.19.918",
|
|
7
|
+
"source_commit": "d9c3f86944e01f8135ce13bc9476f2ef3c86c00a",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-20T18:32:13.181Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 507,
|
|
13
13
|
"agent_docs_stubbed": 26,
|
|
14
14
|
"functional_verbatim": 2298,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2831,
|
|
20
|
+
"tree_sha256": "7ecf5ebe792ee91db6d6d40ef4e6b49ea2d375af7a1c100885d66c7e266e5171",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2062,7 +2062,7 @@
|
|
|
2062
2062
|
{
|
|
2063
2063
|
"path": "docs/adr/0314-module-protection-is-per-path-not-per-module.md",
|
|
2064
2064
|
"mode": "0000644",
|
|
2065
|
-
"sha256": "
|
|
2065
|
+
"sha256": "d408c3f7bee29bbdf5f537ec7ed4559b88fae5d8074a8d58e556dc9e65bff46d"
|
|
2066
2066
|
},
|
|
2067
2067
|
{
|
|
2068
2068
|
"path": "docs/adr/0315-a-plane-with-no-proxy-is-refused-not-given-a-no-op-ingress.md",
|
|
@@ -2079,10 +2079,15 @@
|
|
|
2079
2079
|
"mode": "0000644",
|
|
2080
2080
|
"sha256": "4dd5bbb9c14e4ba19568646ceea2d29b66c46b99555d6c236a25b7b40097756c"
|
|
2081
2081
|
},
|
|
2082
|
+
{
|
|
2083
|
+
"path": "docs/adr/0318-listing-a-file-for-task-affinity-is-not-owning-it.md",
|
|
2084
|
+
"mode": "0000644",
|
|
2085
|
+
"sha256": "d5364169236db8c8d1a7968051fba352bb368aca53b19e554f172e0c3dc90d5d"
|
|
2086
|
+
},
|
|
2082
2087
|
{
|
|
2083
2088
|
"path": "docs/adr/README.md",
|
|
2084
2089
|
"mode": "0000644",
|
|
2085
|
-
"sha256": "
|
|
2090
|
+
"sha256": "9cebfdfef7de4b2c0ede697b30f387121f2a4a0df695b21b205f16acea84bad9"
|
|
2086
2091
|
},
|
|
2087
2092
|
{
|
|
2088
2093
|
"path": "docs/api-reference.md",
|
|
@@ -2652,7 +2657,7 @@
|
|
|
2652
2657
|
{
|
|
2653
2658
|
"path": "docs/module-api-changelog.md",
|
|
2654
2659
|
"mode": "0000644",
|
|
2655
|
-
"sha256": "
|
|
2660
|
+
"sha256": "824b753887223eeba6c21424a6a1f0984235e33165799114ac4a40993bb9b080"
|
|
2656
2661
|
},
|
|
2657
2662
|
{
|
|
2658
2663
|
"path": "docs/modules-contract.md",
|
|
@@ -7847,12 +7852,12 @@
|
|
|
7847
7852
|
{
|
|
7848
7853
|
"path": "package-lock.json",
|
|
7849
7854
|
"mode": "0000644",
|
|
7850
|
-
"sha256": "
|
|
7855
|
+
"sha256": "9842c68958d709653d9006a15332aa271621aaa6e4fac8de8411896a6c103042"
|
|
7851
7856
|
},
|
|
7852
7857
|
{
|
|
7853
7858
|
"path": "package.json",
|
|
7854
7859
|
"mode": "0000644",
|
|
7855
|
-
"sha256": "
|
|
7860
|
+
"sha256": "31b797c1d71820a20e2744debb104990294e44cf943d24aeed1b77b35e2f1b2a"
|
|
7856
7861
|
},
|
|
7857
7862
|
{
|
|
7858
7863
|
"path": "public-docs/index.html",
|
|
@@ -9662,7 +9667,7 @@
|
|
|
9662
9667
|
{
|
|
9663
9668
|
"path": "src/bongos/module-scope-map.js",
|
|
9664
9669
|
"mode": "0000644",
|
|
9665
|
-
"sha256": "
|
|
9670
|
+
"sha256": "40f2196830c746ed440ba91b938bece8ba48fdf225285c6a6d5dccd109d47d3d"
|
|
9666
9671
|
},
|
|
9667
9672
|
{
|
|
9668
9673
|
"path": "src/bongos/module-submissions.js",
|
|
@@ -9852,7 +9857,7 @@
|
|
|
9852
9857
|
{
|
|
9853
9858
|
"path": "src/module-api.js",
|
|
9854
9859
|
"mode": "0000644",
|
|
9855
|
-
"sha256": "
|
|
9860
|
+
"sha256": "250d1791c44836efe9407a5a524a0171bf7ad0c9b337c0cfaa6dd4c491b76d7c"
|
|
9856
9861
|
},
|
|
9857
9862
|
{
|
|
9858
9863
|
"path": "src/module-loader/catalog.js",
|
|
@@ -12357,7 +12362,7 @@
|
|
|
12357
12362
|
{
|
|
12358
12363
|
"path": "tests/module-scope-map.mjs",
|
|
12359
12364
|
"mode": "0000644",
|
|
12360
|
-
"sha256": "
|
|
12365
|
+
"sha256": "0a6878a91c21b5e8c31f0c06518177969f907cb0a980733c2ec1a89f8621bcb8"
|
|
12361
12366
|
},
|
|
12362
12367
|
{
|
|
12363
12368
|
"path": "tests/module_api.mjs",
|
|
@@ -111,11 +111,16 @@ new derivation mechanism. Filed separately:
|
|
|
111
111
|
structural path test — directories under `scripts/` are territory and still
|
|
112
112
|
protect), narrowing the derived set from twelve to the nine predicted below.
|
|
113
113
|
Nothing was unprotected to achieve it and the path wall did not move.
|
|
114
|
-
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
- ~~**`builder-settings`, deliberately out of scope.**~~ **Resolved** by
|
|
115
|
+
[ADR 0318](0318-listing-a-file-for-task-affinity-is-not-owning-it.md)
|
|
116
|
+
([task 1004110](https://cloudbongos.com/builders#/task/1004110)), and by neither
|
|
117
|
+
of the two routes predicted here. Its overlap was `src/bongos/routes/me.js`
|
|
118
|
+
caught by the blanket `src/bongos/routes/` prefix — but that glob was a task
|
|
119
|
+
AFFINITY listing, not an ownership claim, on a file `MODULE_GLOBS` itself
|
|
120
|
+
called a core facade. Ownership moved to `kernel`, where the aggregator
|
|
121
|
+
belongs; the blanket glob was left alone (all eighteen route files are
|
|
122
|
+
protected by it and nothing else) and no strangler move was needed (the
|
|
123
|
+
aggregator resolves ten module ports). The derived set narrows nine to eight.
|
|
119
124
|
- **Nine protected paths are owned by no module at all** — `gen-repo-map.js`,
|
|
120
125
|
`gen-file-map.js`, `gen-session-index.js`, `install-git-hooks.js`, `cli-lib.js`,
|
|
121
126
|
`run-unit-tests.js`, `git-merge-regen.js`, `scripts/gds/hooks/` and
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# ADR 0318 — Listing a file for task affinity is not claiming to own it
|
|
2
|
+
|
|
3
|
+
- **Status:** accepted — **implemented** by [task 1004110](https://cloudbongos.com/builders#/task/1004110) (2026-09-20)
|
|
4
|
+
- **Date:** 2026-09-20
|
|
5
|
+
- **Task:** [task 1004110](https://cloudbongos.com/builders#/task/1004110), goal 1000095 — BONGOS-V2
|
|
6
|
+
- **Extends** [ADR 0314](0314-module-protection-is-per-path-not-per-module.md), which left this case open by name.
|
|
7
|
+
- **Supersedes nothing.**
|
|
8
|
+
|
|
9
|
+
## The question
|
|
10
|
+
|
|
11
|
+
After [ADR 0314](0314-module-protection-is-per-path-not-per-module.md), nine
|
|
12
|
+
modules derive `protected` — making a goal scoped to them Archon-only to create.
|
|
13
|
+
`builder-settings` was one of them, and it earned that on exactly one glob:
|
|
14
|
+
`src/bongos/routes/me.js`, caught by the blanket `src/bongos/routes/` prefix in
|
|
15
|
+
`PROTECTED_GLOBS`.
|
|
16
|
+
|
|
17
|
+
ADR 0314 named this and left it, because clearing it looked like it needed one of
|
|
18
|
+
two expensive things: finishing the strangler move of `me.js` into
|
|
19
|
+
`modules/builder-settings/`, or narrowing a blanket protected glob. Both were
|
|
20
|
+
scoped out. The task that picked this up was written the same way — those two
|
|
21
|
+
options, and *"the choice is the deliverable"*.
|
|
22
|
+
|
|
23
|
+
Both are wrong, and measuring them is what shows a third thing was actually broken.
|
|
24
|
+
|
|
25
|
+
## What the measurements said
|
|
26
|
+
|
|
27
|
+
**Narrowing the blanket prefix removes the only wall that exists.** All eighteen
|
|
28
|
+
files under `src/bongos/routes/` are protected by that prefix *and nothing else* —
|
|
29
|
+
no route file has a second protecting glob. Narrowing it unwalls `auth.js`,
|
|
30
|
+
`builders.js`, `backup.js` and `core-update.js` in the same stroke. This is now
|
|
31
|
+
pinned as a test rather than left as a caution, so the next reader does not have
|
|
32
|
+
to re-derive it.
|
|
33
|
+
|
|
34
|
+
**Moving `me.js` into `modules/builder-settings/` inverts the port architecture.**
|
|
35
|
+
`GET /me` is a deliberate aggregator: it resolves **ten** module ports —
|
|
36
|
+
`lifecycle`, `reward`, `onboarding`, `government`, `specialities`, `grade`,
|
|
37
|
+
`box.hasEverConnected`, `art.sharedKeyConfigured`, `art.getBuilderGeminiKeyRow`,
|
|
38
|
+
`discord.isLinked` — and `builder-settings` is one of the ten. Hosting it inside
|
|
39
|
+
one module would mean that module resolving nine foreign ports from the inside,
|
|
40
|
+
which is the `core -> module` edge [ADR 0083](0083-modular-architecture-strangler-build-plan.md)
|
|
41
|
+
rule 1 exists to forbid. A core facade is precisely what prevents that.
|
|
42
|
+
|
|
43
|
+
Three places already said so, in the code, before this task was written: the file
|
|
44
|
+
header (*"This file kept only the read aggregation"*), `module.json` (*"the
|
|
45
|
+
`GET /me` aggregator stays a thin core facade"*), and `MODULE_GLOBS` itself.
|
|
46
|
+
|
|
47
|
+
## The decision
|
|
48
|
+
|
|
49
|
+
**`MODULE_GLOBS` listed `src/bongos/routes/me.js` under `builder-settings` for
|
|
50
|
+
task affinity — and said so in the same comment — while `isProtected()` read it
|
|
51
|
+
as an ownership claim.** Affinity is a convenience for authoring; ownership is a
|
|
52
|
+
statement about territory. One listing, read as the other, made a whole module
|
|
53
|
+
Archon-only on the strength of a file its own comment called core.
|
|
54
|
+
|
|
55
|
+
So: **ownership follows what the file is.** `src/bongos/routes/me.js` moves to
|
|
56
|
+
`kernel`, which already owns `src/bongos/routes/security.js` and is the home of
|
|
57
|
+
the core route surface. `builder-settings` keeps its own directory and its
|
|
58
|
+
pref-mirroring CLI, and stops being protected.
|
|
59
|
+
|
|
60
|
+
This is [ADR 0314](0314-module-protection-is-per-path-not-per-module.md) one step
|
|
61
|
+
on. That decision said *owning the command is not owning the authority*, for a CLI
|
|
62
|
+
entrypoint that merely lives near a module. The same error wearing different
|
|
63
|
+
clothes: **being listed next to a module is not owning it either.** 0314 solved
|
|
64
|
+
its case structurally, with a path test (`isCommandPath`). This one is solved by
|
|
65
|
+
correcting the data, because there is exactly one such listing — see below.
|
|
66
|
+
|
|
67
|
+
## Why not a general affinity tier
|
|
68
|
+
|
|
69
|
+
The obvious generalisation is to mark affinity entries in `MODULE_GLOBS` so
|
|
70
|
+
`isProtected()` skips them, mirroring `isCommandPath`. Rejected, because the
|
|
71
|
+
audit does not support it. Of the nine protected modules, three others
|
|
72
|
+
(`kernel`, `devsecops`, `methodology`) are protected only by paths outside their
|
|
73
|
+
own directory — but in every one of those the foreign path genuinely *is* the
|
|
74
|
+
domain: `kernel` **is** `src/bongos/auth.js`, `devsecops` **is** `.github/`,
|
|
75
|
+
`methodology` **is** `CLAUDE.md` and `docs/`.
|
|
76
|
+
|
|
77
|
+
`builder-settings` was the only module protected by a path its own source
|
|
78
|
+
described as belonging elsewhere. A new tier in `MODULE_GLOBS` would be a new
|
|
79
|
+
concept, and a new place for a hole to hide, built for a population of one.
|
|
80
|
+
|
|
81
|
+
## Consequences
|
|
82
|
+
|
|
83
|
+
- `protectedModules()` goes **9 -> 8**. `builder-settings` leaves the set.
|
|
84
|
+
- **The wall that matters does not move.** This decides who may CREATE a goal
|
|
85
|
+
whose territory includes `/me`. It does not decide who may WRITE it: a
|
|
86
|
+
sub-Metic builder touching `src/bongos/routes/me.js` is still refused by
|
|
87
|
+
`checkPermissionPaths`, which the tests assert directly rather than assume.
|
|
88
|
+
- A `builder-settings`-scoped goal can no longer author a task touching `/me`;
|
|
89
|
+
that needs `kernel` scope. Correct, rather than a loss — the aggregator spans
|
|
90
|
+
ten modules and belongs to none of them.
|
|
91
|
+
- `src/bongos/routes/` now has **two** of eighteen files owned, up from two with
|
|
92
|
+
one of them wrong. The other sixteen remain authorable by no goal;
|
|
93
|
+
[task 1004116](https://cloudbongos.com/builders#/task/1004116) holds that
|
|
94
|
+
decision, and this change removes the builder-settings overlap that made its
|
|
95
|
+
option (a) awkward.
|
|
96
|
+
|
|
97
|
+
## Not built here
|
|
98
|
+
|
|
99
|
+
- **The other sixteen route files.** Still unowned, still
|
|
100
|
+
[task 1004116](https://cloudbongos.com/builders#/task/1004116). The blanket-prefix
|
|
101
|
+
tripwire added here is the measurement that task needs, landed early.
|
package/docs/adr/README.md
CHANGED
|
@@ -409,3 +409,4 @@ This keeps the decision history honest and traceable.
|
|
|
409
409
|
| 0316 | [**The ignite floor is a scheduling dial, not the trusted tier** ([task 1004084](https://cloudbongos.com/builders#/task/1004084), goal 1000095 — BONGOS-V2). BV2 decision 3 makes the ignite floor — who may turn a quick idea straight into claimable work — a per-instance setting, and decision 16 adds the descent decay rate beside it, the two to share one home. Both the spec and the seeded task described the mechanism the same way: the floor IS the `METIC_PLUS` constant in `modules/ideas/routing.js`, so this is "a setting, not a refactor". Tracing the code before building refuted that: `METIC_PLUS` is spelled once but READ for four semantically different gates — the two `decideLanding` bug branches (land at `ready` or at `backlog`), `mayProposeCriterion` (who may file a `kind='criterion-proposal'`, a planning-scope write), and the protected-scope wall in `authorizeRoutedLanding` (who may route work into the permission / ship-grade / deploy / migration core). Only the first two are the ignite floor, so making the CONSTANT configurable would mean an instance setting its floor to `xenos` — exactly the "project of one wants every thought claimable" case the owner asked for — ALSO gaining the right to file criterion-proposals and to breach the protected core. The codebase already said so in three places nobody had read into the spec: the constant's own header ("It grants nothing — the server-side rank floor and the goal's scope wall are separate, load-bearing gates"), the protected-scope branch ("the two are layers, not alternatives"), and `tests/filing_abuse_matrix.mjs`, which models the widening as an ATTACK and pins the literal. Decision: a separate dial. `modules/ideas/config.js` + `config/ideas.neutral.json` carry both knobs on the `modules/government/config.js` precedence (env -> instance root `config/ideas.json` -> core root neutral; the ADR 0268 dual-root split matters, since one root reads the host pack out of `node_modules` where `npm ci` erases it). `igniteFloor` defaults to `metic`, resolving to exactly the `METIC_PLUS` set, and is threaded into `decideLanding` as a ready-made `trustedRanks` **Set** by the callers — `routing.js` is TOLD the floor and never reads config, staying pure and DB-free — while `METIC_PLUS` stays a FIXED constant for the other two gates, neither of which takes a floor argument (pinned structurally). `descentDecayRate` defaults to 0.5 and ships with no production caller, because R11 is its intended reader and depends on the unbuilt R08 — the same deliberate slice boundary `modules/government/config.js` documents for itself, and what decision 16 asked for. Everything fails to VANILLA rather than dead: a malformed pack, an unknown rank or an out-of-band rate falls back, and an unrecognised floor yields the DEFAULT set, never an empty one (failing closed would stop every ignition on a typo). Rejected: configuring `METIC_PLUS` as written (a security widening in a config file's clothes); splitting it into four named constants (the other three want the same fixed tier, and naming them invites configuring them); having `routing.js` read the config itself (makes the landing matrix impure and untestable at a different floor). Spec decision 3's "spelled in one place" describes the intent, not the mechanism; the spec was not edited and the task description was amended in flight.](0316-the-ignite-floor-is-not-the-trusted-tier.md) | ideas / permissions |
|
|
410
410
|
| 0315 | [**A plane with no proxy is refused, not given a no-op ingress** ([task 1004017](https://cloudbongos.com/builders#/task/1004017), goal 1000106 — BONGOS-V2). The piece [ADR 0306](0306-instance-supervision-is-a-seam-with-systemd-as-the-default.md) explicitly deferred: the self-host image ships no proxy, so provisioning step 6's `<slug>.caddy` snippet write and `systemctl reload caddy` cannot work there and a child instance gets no front door. **Two failure shapes, only one visible**: a DOMAINED child already half-refused, because `caddy adapt` fails with no binary and [ADR 0303](0303-provisioning-verifies-the-base-caddyfile-and-never-edits-it.md)'s own catch turns that into a gap — but the message told the operator to add a directive to `/etc/caddy/Caddyfile`, a file the container does not have and that ADR 0303 §1 forbids provisioning from creating; a DOMAINLESS child skipped that preflight entirely (it is gated on `inst.domain`), reached step 6 and died on the `sudo` write, an uncaught throw that bypasses the `{ok:false}` pending-retry path and hard-errors the instance on attempt one — a crash recorded where a refusal was the honest answer. Decision: `ingressPlaneGap(exec)` probes the plane with `caddy version` — one cheap unprivileged read, the same posture as `checkCaddyWiring` — and both standup legs refuse with `Ingress preflight failed: …`, deliberately NOT domain-gated, because the domainless child is the one 0303 could not see. **Only a missing binary proves there is no proxy**: under remote exec `boxExec` is `ssh <host> bash -s`, so an unreachable co-hosting box throws here too, and diagnosing a network blip as 'this plane has no proxy, provision elsewhere' would be a confident wrong answer that burns the intent's retry budget — exit 127 is read as proof, anything else answers 'could not confirm a reverse proxy on this plane' with the real cause leading. The three refusals are ordered **capacity → ingress → wiring**, cheapest and most fundamental first and all before any mutation, which moves 0303's preflight behind the ADR 0285 capacity measurement; both still refuse before anything changes, so only the precedence between two refusals moves. **No ingress seam and no no-op backend**, despite the obvious symmetry with 0306: a `none` implementation whose `writeSite`/`reload` are no-ops lets the standup SUCCEED while leaving the child unreachable — neither half of the task's done-when, and exactly the silently-broken instance it exists to prevent. Does not decide: what a child's front door IS on a container plane (a published port, a bundled proxy, an explicit localhost-only shape) — a future ADR that adds one replaces this refusal rather than extending it. Teardown is left alone on purpose: it already reloads `allowFail`, and refusing there would strand a row instead of cleaning it.](0315-a-plane-with-no-proxy-is-refused-not-given-a-no-op-ingress.md) | provisioning / ingress |
|
|
411
411
|
| 0317 | [**A plane that cannot give an instance its own account is refused, not given a shared uid** ([task 1004018](https://cloudbongos.com/builders#/task/1004018), goal 1000106 — BONGOS-V2). The last of the three container gaps [ADR 0306](0306-instance-supervision-is-a-seam-with-systemd-as-the-default.md) deferred, and the sibling of [ADR 0315](0315-a-plane-with-no-proxy-is-refused-not-given-a-no-op-ingress.md). [ADR 0281](0281-an-instance-identity-is-its-own-unix-account-and-pg-role.md) makes an instance's identity its own unix account, and the unit's `User=`/`Group=` is what stops one co-tenant reading a sibling's `/proc/<pid>/environ` — where every instance's `EnvironmentFile` carries its GitHub App private key and OAuth client secret (audit B3). `useradd` needs uid 0; the self-host image is `node:22-bookworm-slim` running `USER node` with **no sudo**, so there the account cannot be created at all. **Two failure shapes, neither honest**: today step 1c's `boxExec(ensureInstanceUserCmd(...))` is not `allowFail`, so it throws AFTER every preflight passed, AFTER the status flip to `provisioning`, and — for a standalone — after a repo pull and an `npm ci`, recording a crash with the box half-changed; and tomorrow it goes quiet, because a compose self-host only stops short of step 1c by ADR 0315's coincidence, and 0315 says in its own consequences that a future front door **replaces** that refusal. Decision: `accountPlaneGap(exec, { privileged })` in `provision-repo.js` probes with `${sudo}sh -c 'test "$(id -u)" = 0'` and `provisionInstance` refuses with `Account preflight failed: …` before any mutation. **The probe runs the SAME escalation the mutation will** (`PROVISION_SUDO=1` renders the same `sudo ` prefix `ensureInstanceUserCmd` uses), so a pass is evidence rather than inference. **`sh -c`, not a bare `test`, is load-bearing**: under sudo the outer unprivileged shell would expand `$(id -u)` first and answer about the wrong user, passing on exactly the plane this refuses — and for the same reason there is no pipeline, whose exit status is its last stage's. **Only a shell that ran and answered proves it**: `boxExec` is `ssh <host> bash -s` under remote exec, so exit 1 (`test` said no) and 127 (no sudo binary) are read as proof while ssh's own 255 answers "could not confirm" with the real cause leading — ADR 0315's rule applied to a different question. Ordered **third**, after capacity and ingress and before 0303's wiring check: the first three ask about the PLANE, 0303 asks about this instance's DOMAIN, and 0315's order is extended rather than rearranged. **Rejected: accepting it** — documenting a self-host container as single-tenant with a shared uid is a silent downgrade of the vector 0281 closed, observable nowhere on the running box, and "single-tenant" is not what a container hosting several child projects is; **an identity seam with a container implementation**, because nothing exists to put behind it and a seam whose second backend does nothing is 0315's no-op wearing a seam's clothes; **probing `useradd --help` / `command -v useradd`**, which succeed unprivileged on the very image this refuses; **`${sudo}id -u` compared against `0`**, which decides on captured stdout where every sibling preflight and every test fake treats exit status as the contract; and **refusing inside step 1c**, which keeps the refusal after the status flip. Not covered, deliberately: root-but-no-`useradd` (an Alpine-style plane fails loudly at step 1c as today), and teardown (0315's reason — a refusal there strands a row instead of cleaning it). No `PROVISION_ALLOW_SHARED_UID`: a runner either reaches root on this plane or it does not.](0317-a-plane-that-cannot-give-an-instance-its-own-account-is-refused.md) | provisioning / identity |
|
|
412
|
+
| 0318 | [**Listing a file for task affinity is not claiming to own it** ([task 1004110](https://cloudbongos.com/builders#/task/1004110), goal 1000095 — BONGOS-V2). The case [ADR 0314](0314-module-protection-is-per-path-not-per-module.md) named and left open, closed by neither of the two routes it predicted. `builder-settings` derived `protected` — Archon-only to scope a goal to — on exactly one glob, `src/bongos/routes/me.js`, caught by the blanket `src/bongos/routes/` prefix. **That glob was never an ownership claim:** `MODULE_GLOBS` listed it for task AFFINITY and said in the same comment that the `GET /me` aggregator stays a thin core facade, and `isProtected()` cannot tell the two apart. **Both predicted routes were measured and rejected.** Narrowing the blanket prefix: all eighteen files under `src/bongos/routes/` are protected by that prefix AND NOTHING ELSE, so narrowing unwalls `auth.js`, `builders.js`, `backup.js` and `core-update.js` together — now pinned as a tripwire test rather than left as a caution. Moving `me.js` into `modules/builder-settings/`: the aggregator resolves TEN module ports (`lifecycle`, `reward`, `onboarding`, `government`, `specialities`, `grade`, `box`, `art` ×2, `discord`) and `builder-settings` is one of them, so hosting it inside one module means resolving nine foreign ports from the inside — the `core -> module` edge [ADR 0083](0083-modular-architecture-strangler-build-plan.md) rule 1 forbids. Decision: **ownership follows what the file is** — `me.js` moves to `kernel`, which already owns `src/bongos/routes/security.js`. This is 0314 one step on: owning the command is not owning the authority, and **being listed next to a module is not owning it either**. **Rejected: a general affinity tier** in `MODULE_GLOBS` mirroring `isCommandPath` — the audit does not support it. Three other modules (`kernel`, `devsecops`, `methodology`) are also protected only by paths outside their own directory, but in each the foreign path genuinely IS the domain; `builder-settings` was the only module protected by a path its own source called core, so a new tier would be a new place for a hole to hide, built for a population of one. `protectedModules()` goes 9 -> 8. **The wall that matters does not move:** a sub-Metic builder touching `/me` is still refused by `checkPermissionPaths`, asserted directly rather than assumed. The sixteen still-unowned route files remain [task 1004116](https://cloudbongos.com/builders#/task/1004116), whose option (a) this de-risks.](0318-listing-a-file-for-task-affinity-is-not-owning-it.md) | permissions / module scope |
|
|
@@ -2308,5 +2308,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2308
2308
|
landed since 1.19.915 with no explicit bump. run 35525694524. (task 1002620)
|
|
2309
2309
|
1.19.917 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2310
2310
|
landed since 1.19.916 with no explicit bump. run 35526196428. (task 1002620)
|
|
2311
|
+
1.19.918 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2312
|
+
landed since 1.19.917 with no explicit bump. run 35529342372. (task 1002620)
|
|
2311
2313
|
---------------------------------------------------------------------------
|
|
2312
2314
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.918",
|
|
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.918",
|
|
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.918",
|
|
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",
|
|
@@ -62,6 +62,14 @@ const MODULE_GLOBS = {
|
|
|
62
62
|
// exploit-detail engagement-doc store (/security/docs). Stays kernel when the
|
|
63
63
|
// red-team FEATURE carved to modules/security/ (BV1.R79 / 02-module-map §5).
|
|
64
64
|
'src/bongos/routes/security.js',
|
|
65
|
+
// The GET /me AGGREGATOR (task 1004110, ADR 0318). A core facade, not a
|
|
66
|
+
// builder-settings file: it resolves TEN module ports (lifecycle, reward,
|
|
67
|
+
// onboarding, government, specialities, grade, box, art, discord) and
|
|
68
|
+
// builder-settings is merely one of them. It was listed under that module
|
|
69
|
+
// for task AFFINITY, and the affinity listing alone made the whole module
|
|
70
|
+
// protected - ADR 0314 in a second costume. Ownership follows what the
|
|
71
|
+
// file IS, and what it is is the core read facade over ten modules.
|
|
72
|
+
'src/bongos/routes/me.js',
|
|
65
73
|
'server.js', 'config/',
|
|
66
74
|
|
|
67
75
|
// ADOPTED (task 1004111): a protected path owned by no module is authorable
|
|
@@ -140,11 +148,13 @@ const MODULE_GLOBS = {
|
|
|
140
148
|
// --- builder prefs (wandering/skill/sound/render) + builder-needs ---
|
|
141
149
|
// Carved into modules/builder-settings/ (BV1.R80 / ADR 0093 §1): the five pref
|
|
142
150
|
// libs + builder-needs + their db slice + the `/me/*-prefs` routes + the
|
|
143
|
-
// `builder-settings` kernel PORT. The `GET /me` aggregator
|
|
144
|
-
// facade
|
|
145
|
-
//
|
|
151
|
+
// `builder-settings` kernel PORT. The `GET /me` aggregator is NOT here: it
|
|
152
|
+
// stays a thin core facade resolving that port, and since task 1004110
|
|
153
|
+
// (ADR 0318) it is owned by KERNEL. Listing it here for task AFFINITY was
|
|
154
|
+
// the single glob that made this whole module protected, on a file the
|
|
155
|
+
// comment itself said was core. The pref-mirroring CLIs travel with the
|
|
156
|
+
// domain but stay under scripts/.
|
|
146
157
|
'builder-settings': [
|
|
147
|
-
'src/bongos/routes/me.js',
|
|
148
158
|
'scripts/gds/fetch-sound-prefs.js',
|
|
149
159
|
'modules/builder-settings/',
|
|
150
160
|
],
|
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.918'; // 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');
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { test } from 'node:test';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
|
+
import fs from 'node:fs';
|
|
5
6
|
import path from 'node:path';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
7
8
|
|
|
@@ -78,12 +79,24 @@ test('`protected` is DERIVED from the imported PROTECTED_GLOBS, never hand-set',
|
|
|
78
79
|
// three is still refused by the path wall (asserted below). What changed is who
|
|
79
80
|
// may CREATE a goal whose territory happens to include them.
|
|
80
81
|
//
|
|
81
|
-
// `builder-settings`
|
|
82
|
-
//
|
|
83
|
-
//
|
|
82
|
+
// NARROWED NINE -> EIGHT BY ADR 0318 (task 1004110). `builder-settings` left the
|
|
83
|
+
// set. It derived protected on exactly one glob, src/bongos/routes/me.js, caught
|
|
84
|
+
// by the blanket src/bongos/routes/ prefix. That glob was never an ownership
|
|
85
|
+
// claim: MODULE_GLOBS listed it for task AFFINITY and said in the same comment
|
|
86
|
+
// that the GET /me aggregator stays a thin CORE facade. It resolves ten module
|
|
87
|
+
// ports and builder-settings is one of them, so the file is kernel territory and
|
|
88
|
+
// is now owned there. Same shape as ADR 0314 one step on: owning the command is
|
|
89
|
+
// not owning the authority, and neither is being listed next to it.
|
|
90
|
+
//
|
|
91
|
+
// Neither of the two routes the task proposed was taken, and the measurements are
|
|
92
|
+
// why. Narrowing the blanket prefix was rejected because all 18 files under
|
|
93
|
+
// src/bongos/routes/ are protected by that prefix AND NOTHING ELSE, so narrowing
|
|
94
|
+
// removes the only wall from auth.js, builders.js, backup.js and core-update.js.
|
|
95
|
+
// Moving me.js into modules/builder-settings/ was rejected because a ten-port
|
|
96
|
+
// aggregator inside one module would have to resolve nine foreign ports from
|
|
97
|
+
// inside a module, which is the core->module edge ADR 0083 rule 1 forbids.
|
|
84
98
|
const PROTECTED_MODULES = [
|
|
85
99
|
'agents',
|
|
86
|
-
'builder-settings',
|
|
87
100
|
'dev-box',
|
|
88
101
|
'devsecops',
|
|
89
102
|
'government',
|
|
@@ -632,9 +645,16 @@ test('adopting the unowned paths did NOT change which modules are protected', ()
|
|
|
632
645
|
for (const k of ['kernel', 'lifecycle', 'dev-box', 'devsecops', 'methodology']) {
|
|
633
646
|
assert.equal(scope.scopeForModule(k).protected, true, `${k} was already protected and must stay so`);
|
|
634
647
|
}
|
|
635
|
-
// and the
|
|
636
|
-
//
|
|
637
|
-
|
|
648
|
+
// and the adoption changed no COUNT either. Pinned against PROTECTED_MODULES
|
|
649
|
+
// rather than a literal, so the by-name pin above stays the single place the
|
|
650
|
+
// set is spelled out: a deliberate departure (ADR 0318 removing
|
|
651
|
+
// builder-settings) edits one list, while an ACCIDENTAL widening still reds
|
|
652
|
+
// both this and the by-name test. A literal here duplicated the roster and
|
|
653
|
+
// made an intended change look like two unrelated failures.
|
|
654
|
+
assert.equal(
|
|
655
|
+
scope.protectedModules().length, PROTECTED_MODULES.length,
|
|
656
|
+
'adoption must not change the size of the protected set',
|
|
657
|
+
);
|
|
638
658
|
});
|
|
639
659
|
|
|
640
660
|
test('the probe trap: matchOne takes an ARRAY, and a string reports everything unowned', () => {
|
|
@@ -651,3 +671,76 @@ test('the probe trap: matchOne takes an ARRAY, and a string reports everything u
|
|
|
651
671
|
'a STRING second argument must not be mistaken for a working query',
|
|
652
672
|
);
|
|
653
673
|
});
|
|
674
|
+
|
|
675
|
+
// --- ADR 0318: the GET /me aggregator is kernel territory (task 1004110) ---
|
|
676
|
+
|
|
677
|
+
test('the GET /me aggregator is owned by kernel, not by builder-settings', () => {
|
|
678
|
+
// The affinity listing that made a whole module Archon-only. MODULE_GLOBS put
|
|
679
|
+
// src/bongos/routes/me.js under builder-settings and said in the same comment
|
|
680
|
+
// that the aggregator stays a thin CORE facade - so the glob was task affinity,
|
|
681
|
+
// never an ownership claim, and isProtected() could not tell the two apart.
|
|
682
|
+
const ME = 'src/bongos/routes/me.js';
|
|
683
|
+
assert.ok(
|
|
684
|
+
matchOne(ME, scope.scopeForModule('kernel').globs),
|
|
685
|
+
'kernel must own the /me aggregator - it is the core read facade over ten module ports',
|
|
686
|
+
);
|
|
687
|
+
assert.equal(
|
|
688
|
+
matchOne(ME, scope.scopeForModule('builder-settings').globs), null,
|
|
689
|
+
'builder-settings must NOT own /me: it is one of the ten ports the facade resolves, not its home',
|
|
690
|
+
);
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
test('builder-settings is not protected, and its own territory is what says so', () => {
|
|
694
|
+
assert.equal(
|
|
695
|
+
scope.scopeForModule('builder-settings').protected, false,
|
|
696
|
+
'builder-settings derived protected on the me.js affinity listing alone - removing it is the whole point of ADR 0318',
|
|
697
|
+
);
|
|
698
|
+
// Prove the module did not simply lose its territory on the way: it still owns
|
|
699
|
+
// its own directory, and that directory is genuinely not protected. Without
|
|
700
|
+
// this, blanking the globs entirely would pass the assertion above.
|
|
701
|
+
assert.ok(
|
|
702
|
+
matchOne('modules/builder-settings/interaction-prefs.js', scope.scopeForModule('builder-settings').globs),
|
|
703
|
+
'builder-settings must still own its own module directory',
|
|
704
|
+
);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test('unprotecting builder-settings did NOT open the path wall on /me', () => {
|
|
708
|
+
// The half that would make this a false success, mirroring the ADR 0314 test
|
|
709
|
+
// above. The module flag decides who may CREATE a goal over the territory; the
|
|
710
|
+
// per-file wall decides who may WRITE it. Only the first was meant to move.
|
|
711
|
+
const ME = 'src/bongos/routes/me.js';
|
|
712
|
+
assert.ok(
|
|
713
|
+
touchesOverlap([ME], PROTECTED_GLOBS),
|
|
714
|
+
'fixture assumption: /me is still a protected surface via the src/bongos/routes/ prefix',
|
|
715
|
+
);
|
|
716
|
+
assert.equal(gsc.scopeSubset(['kernel'], [ME]).ok, true, 'fixture assumption: kernel now owns /me');
|
|
717
|
+
const r = gsc.check(['kernel'], [ME], 'thetes');
|
|
718
|
+
assert.equal(r.ok, false, 'a sub-Metic builder must still be refused on /me');
|
|
719
|
+
assert.equal(r.code, 'protected_path', '/me must be refused BY THE PATH WALL, not by scope');
|
|
720
|
+
assert.equal(gsc.check(['kernel'], [ME], 'metic').ok, true, 'a Metic must still be able to author on /me');
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
test('every file under src/bongos/routes/ is protected by the blanket prefix AND NOTHING ELSE', () => {
|
|
724
|
+
// The measurement that killed the other option on task 1004110, pinned so the
|
|
725
|
+
// next reader does not have to re-derive it. Narrowing the blanket
|
|
726
|
+
// src/bongos/routes/ prefix looks like a small change and is not: it is the
|
|
727
|
+
// ONLY protection every route file has, auth.js and core-update.js included.
|
|
728
|
+
//
|
|
729
|
+
// If this reds because a route gained its own PROTECTED_GLOBS entry, that is
|
|
730
|
+
// fine and the exemption should be stated deliberately. If it reds because the
|
|
731
|
+
// blanket entry went away, read task 1004116 before touching anything.
|
|
732
|
+
const BLANKET = 'src/bongos/routes/';
|
|
733
|
+
assert.ok(PROTECTED_GLOBS.includes(BLANKET), 'the blanket route prefix must still be a protected glob');
|
|
734
|
+
const others = PROTECTED_GLOBS.filter((g) => g !== BLANKET);
|
|
735
|
+
const dir = path.join(ROOT, 'src', 'bongos', 'routes');
|
|
736
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.js') || f.endsWith('.md'));
|
|
737
|
+
assert.ok(files.length >= 18, 'expected the route directory to still be populated, saw ' + files.length);
|
|
738
|
+
for (const f of files) {
|
|
739
|
+
const rel = BLANKET + f;
|
|
740
|
+
assert.ok(touchesOverlap([rel], PROTECTED_GLOBS), rel + ' must be protected');
|
|
741
|
+
assert.deepEqual(
|
|
742
|
+
others.filter((g) => matchOne(rel, [g])), [],
|
|
743
|
+
rel + ' is protected by the blanket prefix alone - narrowing the prefix would leave it unwalled',
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
});
|