@bongos/core 1.19.632 → 1.19.633
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 -18
- package/docs/adr/0273-hall-nav-can-gate-on-a-permission.md +98 -0
- package/docs/adr/README.md +1 -0
- package/docs/copy-inventory.md +114 -114
- package/docs/copy-registry.json +127 -127
- package/docs/module-api-changelog.md +2 -0
- package/modules/hall-ui/public/hall-render.js +5 -0
- package/modules/hall-ui/public/shell.js +58 -17
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/bongos/routes/me.js +56 -0
- package/src/module-api.js +1 -1
- package/tests/hall_nav.mjs +158 -9
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.633",
|
|
6
|
+
"core_contract": "1.19.633",
|
|
7
|
+
"source_commit": "8a560f46851725a2db7c987576b636af9054138e",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-09T23:
|
|
9
|
+
"built_at": "2026-09-09T23:38:22.332Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 468,
|
|
13
13
|
"agent_docs_stubbed": 24,
|
|
14
14
|
"functional_verbatim": 2099,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2591,
|
|
20
|
+
"tree_sha256": "a7e9c16628645ccc819cbdbf1f77115bf6b14faaa2e04f9c019680d0ab321d1a",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/backlog-review/SKILL.md",
|
|
@@ -1884,10 +1884,15 @@
|
|
|
1884
1884
|
"mode": "0000644",
|
|
1885
1885
|
"sha256": "64a49e7f90224dd348da3ac8dce9b75f8585e2c59336f71c3fceb496cb351005"
|
|
1886
1886
|
},
|
|
1887
|
+
{
|
|
1888
|
+
"path": "docs/adr/0273-hall-nav-can-gate-on-a-permission.md",
|
|
1889
|
+
"mode": "0000644",
|
|
1890
|
+
"sha256": "2fd3191d95758b5be0f84dd050cfa0244707c0c86646eaa9dd843216e754ff07"
|
|
1891
|
+
},
|
|
1887
1892
|
{
|
|
1888
1893
|
"path": "docs/adr/README.md",
|
|
1889
1894
|
"mode": "0000644",
|
|
1890
|
-
"sha256": "
|
|
1895
|
+
"sha256": "eeb226dcbb35f522095d294952379ea8647df9f8f5048eb1754eb112e9b687af"
|
|
1891
1896
|
},
|
|
1892
1897
|
{
|
|
1893
1898
|
"path": "docs/api-reference.md",
|
|
@@ -1922,12 +1927,12 @@
|
|
|
1922
1927
|
{
|
|
1923
1928
|
"path": "docs/copy-inventory.md",
|
|
1924
1929
|
"mode": "0000644",
|
|
1925
|
-
"sha256": "
|
|
1930
|
+
"sha256": "cc0e6bc28171104675ec8c4cfba69eaad07475b973cd36b3634b14dce94c7a5e"
|
|
1926
1931
|
},
|
|
1927
1932
|
{
|
|
1928
1933
|
"path": "docs/copy-registry.json",
|
|
1929
1934
|
"mode": "0000644",
|
|
1930
|
-
"sha256": "
|
|
1935
|
+
"sha256": "28b19e15641338500a9251ce50bfa8e63ec8eac4fd3ea627c29f1c00c0c9a39c"
|
|
1931
1936
|
},
|
|
1932
1937
|
{
|
|
1933
1938
|
"path": "docs/design/apex-pass-2-direction.md",
|
|
@@ -2772,7 +2777,7 @@
|
|
|
2772
2777
|
{
|
|
2773
2778
|
"path": "docs/module-api-changelog.md",
|
|
2774
2779
|
"mode": "0000644",
|
|
2775
|
-
"sha256": "
|
|
2780
|
+
"sha256": "9be51df76afca08c558e77044a4206b75cc51f30cddeb72a248030bd878c6541"
|
|
2776
2781
|
},
|
|
2777
2782
|
{
|
|
2778
2783
|
"path": "docs/modules-contract.md",
|
|
@@ -4997,7 +5002,7 @@
|
|
|
4997
5002
|
{
|
|
4998
5003
|
"path": "modules/hall-ui/public/hall-render.js",
|
|
4999
5004
|
"mode": "0000644",
|
|
5000
|
-
"sha256": "
|
|
5005
|
+
"sha256": "a45f71b70008e54672f84b5f8f084b492c037f765599af38e2bf6035e336dc94"
|
|
5001
5006
|
},
|
|
5002
5007
|
{
|
|
5003
5008
|
"path": "modules/hall-ui/public/hall-widgets.js",
|
|
@@ -5352,7 +5357,7 @@
|
|
|
5352
5357
|
{
|
|
5353
5358
|
"path": "modules/hall-ui/public/shell.js",
|
|
5354
5359
|
"mode": "0000644",
|
|
5355
|
-
"sha256": "
|
|
5360
|
+
"sha256": "2ef1adc9012ff52126d1fe913de7c4cfbd82cb2be2d8c5f0a21c052fcc2ea541"
|
|
5356
5361
|
},
|
|
5357
5362
|
{
|
|
5358
5363
|
"path": "modules/hall-ui/public/ship.wav",
|
|
@@ -7702,12 +7707,12 @@
|
|
|
7702
7707
|
{
|
|
7703
7708
|
"path": "package-lock.json",
|
|
7704
7709
|
"mode": "0000644",
|
|
7705
|
-
"sha256": "
|
|
7710
|
+
"sha256": "c0fc65fd1a4de740e7e68cbba9bc6ea3a3fa951ce373d3f79a4f829837ee0e78"
|
|
7706
7711
|
},
|
|
7707
7712
|
{
|
|
7708
7713
|
"path": "package.json",
|
|
7709
7714
|
"mode": "0000644",
|
|
7710
|
-
"sha256": "
|
|
7715
|
+
"sha256": "49f18e7b79579bd167d9a705afb40cbb99f67bf71b8118ccbbddfb306e15f740"
|
|
7711
7716
|
},
|
|
7712
7717
|
{
|
|
7713
7718
|
"path": "public-docs/index.html",
|
|
@@ -9372,7 +9377,7 @@
|
|
|
9372
9377
|
{
|
|
9373
9378
|
"path": "src/bongos/routes/me.js",
|
|
9374
9379
|
"mode": "0000644",
|
|
9375
|
-
"sha256": "
|
|
9380
|
+
"sha256": "c730714c3ac3a8b0f8294bca3d15939bf3c37dc2119f7d2faaf194087022634b"
|
|
9376
9381
|
},
|
|
9377
9382
|
{
|
|
9378
9383
|
"path": "src/bongos/routes/modules.js",
|
|
@@ -9447,7 +9452,7 @@
|
|
|
9447
9452
|
{
|
|
9448
9453
|
"path": "src/module-api.js",
|
|
9449
9454
|
"mode": "0000644",
|
|
9450
|
-
"sha256": "
|
|
9455
|
+
"sha256": "833f00528d9bc449a51caa1a3ac924b866d1a054f89faf45bebf49d0b62be156"
|
|
9451
9456
|
},
|
|
9452
9457
|
{
|
|
9453
9458
|
"path": "src/module-loader/catalog.js",
|
|
@@ -11007,7 +11012,7 @@
|
|
|
11007
11012
|
{
|
|
11008
11013
|
"path": "tests/hall_nav.mjs",
|
|
11009
11014
|
"mode": "0000644",
|
|
11010
|
-
"sha256": "
|
|
11015
|
+
"sha256": "baaecfac630b4154ccfea5ecbeae07eaa6ae084a01583875d480a42fe7bbf9b9"
|
|
11011
11016
|
},
|
|
11012
11017
|
{
|
|
11013
11018
|
"path": "tests/hall_oversight_world.mjs",
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# 0273 — The hall nav can gate on a permission, and the Board Room leaves Government
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-09-09
|
|
5
|
+
- **Tasks:** [#1003744](https://cloudbongos.com/builders#/task/1003744) (this record and the work).
|
|
6
|
+
- **Goal:** [#1000111](https://cloudbongos.com/builders#/goal/1000111) — Working area 7, Government; criterion `wa7-government`.
|
|
7
|
+
- **Decided by:** the owner (Masterqua / `IAMMASTERQUA`, Archon, member of area 7), 2026-09-09 — the placement question in §3.
|
|
8
|
+
- **Builds on:** [ADR 0266](<redacted>.md) (the carve that surfaced this gap), [ADR 0175](<redacted>.md) §6 (who governs is configuration), [ADR 0151](<redacted>.md) (permissions as the atom), [ADR 0157](<redacted>.md) (`page.view.government` floors at metic), [ADR 0016](<redacted>.md) (the server enforces; the client decorates).
|
|
9
|
+
|
|
10
|
+
## 1. Context — a sidebar that could only speak rank
|
|
11
|
+
|
|
12
|
+
`modules/hall-ui/public/shell.js` `gateAllows()` resolved six gate keys, and
|
|
13
|
+
every one of them was a **rank**: `always`, `signed`, `member`, `work`,
|
|
14
|
+
`government` (a metic-or-archon alias), `archon`. There was no way to ask *does
|
|
15
|
+
this viewer hold permission X*.
|
|
16
|
+
|
|
17
|
+
That was fine while every gated page fronted a rank floor. ADR 0266 ended it.
|
|
18
|
+
The Board Room's page gate is the **atom** `board.vote.cast`
|
|
19
|
+
(`auth.js requireBoardVotePage`), which floors at **xenos** on purpose —
|
|
20
|
+
`modules/government/board.js` records the floor is low *so that widening the
|
|
21
|
+
board works*. Its nav item could only borrow the nearest rank the shell could
|
|
22
|
+
express, so it carried `gate: 'government'` (metic+), and ADR 0266's own carve
|
|
23
|
+
left a comment saying as much and filing the gap rather than guessing at it.
|
|
24
|
+
|
|
25
|
+
The cost was latent, not theoretical. Today's constitution seats `rank:archon`,
|
|
26
|
+
so no sub-Metic member exists. The moment an instance widens membership below
|
|
27
|
+
Metic — the exact act ADR 0175 §6 exists to make a configuration change — those
|
|
28
|
+
members can reach the room by URL and by the `board_votes` need's deep link, and
|
|
29
|
+
have **no sidebar entry at all**.
|
|
30
|
+
|
|
31
|
+
## 2. Decision — a `perm:<atom>` gate, fed by a server-resolved allowlist
|
|
32
|
+
|
|
33
|
+
A nav item may now declare `gate: 'perm:<atom>'`. Three pieces:
|
|
34
|
+
|
|
35
|
+
- **`/me` resolves the answer.** `NAV_PERMISSION_ATOMS` in
|
|
36
|
+
[`src/bongos/routes/me.js`](../../src/bongos/routes/me.js) is a frozen list of
|
|
37
|
+
the atoms a nav item is allowed to gate on; the route resolves each through the
|
|
38
|
+
`government` port's `resolveBuilderPermissions` and returns
|
|
39
|
+
`permissions: { '<atom>': boolean }`.
|
|
40
|
+
- **The shell reads it, and never re-derives it.** `applyAccess` stores the map
|
|
41
|
+
on `access`; `gateAllows('perm:X')` answers `permissions[X] === true`.
|
|
42
|
+
- **The item quotes its page.** The Board Room item declares
|
|
43
|
+
`perm:board.vote.cast` — the same key `requireBoardVotePage` checks — so the
|
|
44
|
+
nav gate and the page gate cannot drift.
|
|
45
|
+
|
|
46
|
+
Four rules hold it in place.
|
|
47
|
+
|
|
48
|
+
- **It stays cosmetic (ADR 0016).** This decides who is *offered* a link. The
|
|
49
|
+
page gate and each data route's `requirePermission` are the wall, and run
|
|
50
|
+
regardless.
|
|
51
|
+
- **An allowlist, not the whole set.** The builder's full resolved permission set
|
|
52
|
+
is the shape of the instance's governance, and a cosmetic sidebar has no
|
|
53
|
+
business shipping that on every page load. Adding an atom to
|
|
54
|
+
`NAV_PERMISSION_ATOMS` is the deliberate act that lets a nav item gate on it.
|
|
55
|
+
- **Unreadable is not visible.** `permissions` is `null` when authority could not
|
|
56
|
+
be established (government off, port missing, resolver threw). That is *cannot
|
|
57
|
+
say*, not *holds nothing*, and it hides the link — `gateAllows()`'s
|
|
58
|
+
`default: false` and the social mode's OFF-is-the-fallback rule, applied.
|
|
59
|
+
- **The client does not re-derive.** The shell reads the resolved boolean, never
|
|
60
|
+
rank plus a guess — the social-mode precedent (task 1003178): a page that
|
|
61
|
+
decided for itself could disagree with the rest of the hall.
|
|
62
|
+
|
|
63
|
+
## 3. The placement call — the Board Room joins the project group
|
|
64
|
+
|
|
65
|
+
Gating the item honestly while leaving it in the **Government** group would have
|
|
66
|
+
put a wider item under a narrower head: a newcomer board member would see a
|
|
67
|
+
sidebar group labelled "Government" holding exactly one link — more authority
|
|
68
|
+
than they hold, and an IA claim nobody made. ADR 0266 §Scope reserved that
|
|
69
|
+
question, and task 1003744 was written to surface it rather than settle it.
|
|
70
|
+
|
|
71
|
+
The owner's answer (2026-09-09) was to **move the item**, not widen the group:
|
|
72
|
+
the Board Room now sits in the unlabeled project group, after Tasks, beside
|
|
73
|
+
Home / Roadmap / Goals. A sitting is work the project does, not a monitoring
|
|
74
|
+
surface. The Government group's audience, head and floor are unchanged, and it
|
|
75
|
+
is rank surface throughout again.
|
|
76
|
+
|
|
77
|
+
Two options were declined: widening the Government head so it shows whenever any
|
|
78
|
+
item inside is visible (the one-item "Government" group above), and giving the
|
|
79
|
+
Board Room its own single-item group.
|
|
80
|
+
|
|
81
|
+
## 4. Consequences
|
|
82
|
+
|
|
83
|
+
- The hall's gate vocabulary is no longer rank-only. A page gated on an atom can
|
|
84
|
+
have an honest nav entry; the next such page adds its atom to the allowlist and
|
|
85
|
+
declares it, with no second pattern to invent.
|
|
86
|
+
- `tests/hall_nav.mjs` gained the missing half of its group invariant. It checked
|
|
87
|
+
only that a head is never *wider* than its items; an item wider than its own
|
|
88
|
+
**head** — an orphan link under a hidden label, which is precisely the
|
|
89
|
+
arrangement declined in §3 — had no check. It does now, and `GATE_WIDTH` learned
|
|
90
|
+
`government`, which had been `undefined` and made every comparison against that
|
|
91
|
+
group pass vacuously.
|
|
92
|
+
- The harness `/me` fixture carries `permissions`. Without it the item would be
|
|
93
|
+
permanently invisible in `scripts/hall-preview`, which is the
|
|
94
|
+
"a page that only ever renders its empty state cannot be redesigned by looking"
|
|
95
|
+
trap `modules/hall-ui/CLAUDE.md` names.
|
|
96
|
+
- **Not done here:** no other nav item was re-gated. `people` still reads
|
|
97
|
+
`government` for `project.curate`, and moving it onto its atom is a separate,
|
|
98
|
+
reviewable change.
|
package/docs/adr/README.md
CHANGED
|
@@ -363,4 +363,5 @@ This keeps the decision history honest and traceable.
|
|
|
363
363
|
| 0269 | [**The CLI session store is host-keyed at a fixed anchor; the per-brand file stays the active pointer** ([task 1003741](https://cloudbongos.com/builders#/task/1003741) · goal 1000090 — *Working area 4, Bongos Core distribution*). The CLI resolved its session path from the BRANDING PACK, read out of whatever checkout the process stood in — one slot, that moved. Standalone (the public CLI's whole situation) no checkout means no brand, so every instance shared `~/.config/cloudbongos/gds-session.json` and signing into a second DESTROYED the first; in-repo it landed in `~/.config/<slug>/` where the standalone CLI could never find it (`npx … api GET /me` from a bare dir returned the cloudbongos builder while a valid hermeslines session sat on the same machine). Not theoretical — the owner's config dir carries a hand-made `gds-session.<redacted>.bak.json` and four more of the same shape. **Decision: one file per instance keyed by HOST at a FIXED anchor (`~/.config/<FALLBACK_DIR>/instances/<host>.json`), with the per-brand `gds-session.json` left exactly where it is as the ACTIVE pointer.** Four load-bearing parts. (1) The anchor must never read `configHome()`/`configDirName()`/the pack — a test asserts the function body names none, since routing it back through the brand silently reinstates the split. (2) The active pointer does NOT move: every existing reader (in-repo skills, the card hook, the dev box, `readSessionToken`) looks there, and the store is purely additive. (3) The OUTGOING session is archived BEFORE the incoming one lands — a session written before the store existed is not in it, so without this the first login after upgrading still loses it, the bug surviving its own fix. (4) A stored token is VERIFIED against `/api/gds/me` before being reinstated; unverified falls through to the real device flow, `--force` signs in as somebody else. `login` also names the other instances and how to switch, since sessions are now kept rather than overwritten. Files 0600 in a 0700 dir; an `api_base` naming no host is not stored rather than stored under a guess, so nothing can write outside the store. The dir is `instances/` NOT `sessions/` — `sessions` is a live module key and [ADR 0083](<redacted>.md) §Decision #4 forbids kernel machinery naming one; the fitness gate caught it. Rejected: moving the active pointer (breaks every reader for no visible gain); one file holding a map (changes a format hooks and the dev box parse today); keying under `configHome()` (the bug); restoring without verifying (hands over a dead session that looks live); a new `bongos switch` verb (login is the command already reached for, and the public CLI's surface is deliberately small).](<redacted>.md) | cli / auth / distribution |
|
|
364
364
|
| 0270 | [**The rank floor derives from facts, not from a prediction** ([task 1003663](https://cloudbongos.com/builders#/task/1003663) · goal 1000111 — *Working area 7, Government*). [ADR 0084](<redacted>.md) derives every task’s claim-time `requires_rank` floor from `touches[]` + `security_sensitive`; [ADR 0049](<redacted>.md) then retired PREDICTIVE `touches[]`, which is now backfilled from the real committed diff at SHIP — after the claim the floor exists to gate. So on every create vector that omits it the matcher saw an empty array, and because the floor is computed ONCE at creation and never recomputed, it returned `xenos` forever. Nothing threw: an empty `touches[]` is a legal state. **Measured before the fix: 88 tasks declared `needs_migration` and 68 of them (77%) sat at `xenos`** — including [task 1003372](https://cloudbongos.com/builders#/task/1003372), a READY task to hash the `builder_sessions` token store at rest, claimable by any newcomer; goal 1000111 alone held 20 such rows. Two mechanisms hid it: the authoring routes DISAGREE (`POST /goals/:id/tasks` REQUIRES `touches[]` as its scope-wall blast radius, so the floor derives there, while its sibling `POST /tasks` — the route `/goal-create` actually files seed tasks through — treats it as optional), and `goal-create/SKILL.md` told authors in ONE line to *omit `touches[]`* and *never hand-set `requires_rank`*: omit the only input, never set the output. **Decision:** the floor derives from facts a task already carries at create time, never from a prediction — `deriveRequiredRank` gains `opts` with two touches-free inputs, `needs_migration` (the direct successor to the `migrations/` entry `POST /tasks` used to fold into `touches[]`) and `scopeProtected` (the owning goal’s `scope_modules` wall, ALWAYS available since `goal_id` is required, read through the same `scopeIncludesProtected` primitive the pre-push hook, grader and main-audit use, so it cannot drift). Derived at the LOWEST create layer in `createTask` after `goalIdEffective` resolves — the ADR 0096 argument — so every vector inherits it and patching the two routes separately (which is how they came to disagree) is not repeated. `updateTaskRequiresRank` re-derives with the same four inputs, else an Archon setting a migration task to `xenos` would silently undo the create-time floor; raise-only throughout; `archon` still never auto-assigned; a strict `=== true` so a stray truthy value cannot wall off the open queue. One-time backfill `core_236` raises still-CLAIMABLE rows only (the floor is a claim-time gate; rewriting shipped rows changes no live decision) and never lowers. **Accepted cost:** a protected goal wall is blunt — any protected module in the wall floors the whole goal, so a `hall-ui` task in a mixed goal floors at Metic too; same semantics the ideas vector has enforced since task 1003066, fails safe, and the remedy is to narrow the wall. Zero claimable `newcomer_friendly` tasks exist today, and no carve-out was added for them on purpose. Rejected: re-requiring predictive `touches[]` (re-litigates ADR 0049); recomputing at ship (too late by construction); deriving from `module_key` (null on most tasks, including the reporter).](<redacted>.md) | permissions / task lifecycle |
|
|
365
365
|
| 0271 | [**The ideator surface is two halves: Bongos owns the prose, the harness owns the chrome** ([task 1003506](https://cloudbongos.com/builders#/task/1003506) · goal 1000095 — *Working area 6, Governor / Builder / Artist / Ideator experience*, criterion `wa6-kernel-and-packs`; owner idea 1000732, 2026-08-16). The ask — *no need for an ideator to see all the text and technical work, or the inner Bongos logic* — is two problems in one sentence. **The PROSE half ships here**, through three seams that already existed: a `machinery` field on `interaction-prefs` (per-PERSON, default `show`, riding the live Conductor injection rail — advisory 8, which means task [#1002514](https://cloudbongos.com/builders#/task/1002514)’s claim that `contractFor()` has zero consumers is STALE); the `ideator` directive in `discipline-modes.json` (per-ROLE, the file whose own docs say *to give a discipline its own experience later, add an entry here*); and a new surface section in `/ideate`, which the directive names as the only home of the experience. Default `show` means no existing builder changes. **The CHROME half is the harness’s and is deferred to a spike, not declared impossible** — this record was nearly written on the wrong assumption that config cannot reach it. Two real levers exist: **A**, shipped client defaults (`/focus` renders *only your last prompt, a one-line tool-call summary, and the final response*; `alwaysThinkingEnabled:false` stops thinking being generated; `showTurnDuration`/`spinnerTipsEnabled`/`statusLine` trim chrome) — but `/focus` is fullscreen-only (research preview), `viewMode`’s committability is UNVERIFIED, and thinking suppression is per-model; and **B**, an app-owned surface over `claude -p --output-format stream-json`, whose documented streaming-UI pattern (working indicator on a `tool_use` block, render on `result`) IS the ask, is model-independent, and is the recorded bias. **Bypass is REJECTED** despite the idea proposing it, on three grounds ending in a technical one: the least technical role is the worst audience for removing confirmations; the prompt was never the gate ([ADR 0016](<redacted>.md) — rank is checked server-side, and sub-Metic protected-path globs already narrow an ideator); and a project **cannot** ship it — `defaultMode: bypassPermissions` in `.claude/settings.json` silently does not take effect and starts the session in Manual. Rejected also: a hall ROOM on the Studio’s precedent (goal 1000074’s *a place, not a form*) — right for an artist, wrong here, because the ideator’s complaint is about the Claude session’s surface, which the hall does not render; and waiting on per-session declared role, whose dependency is softer than filed since `discipline` + the claim-time fork already carry the craft axis. When declaration lands the contract MOVES into the Ideator pack with nothing undone.](<redacted>.md) | roles / ideator experience / session surface |
|
|
366
|
+
| 0273 | [**The hall nav can gate on a permission, and the Board Room leaves Government** ([task 1003744](https://cloudbongos.com/builders#/task/1003744) · goal 1000111 — *Working area 7, Government*, criterion `wa7-government`; owner decision 2026-09-09). `shell.js` `gateAllows()` resolved six gate keys and every one was a RANK, so a nav item whose page is gated on an ATOM had to borrow the nearest rank floor and hope the two agreed. [ADR 0266](<redacted>.md) ended that: the Board Room page asks `board.vote.cast`, which floors at XENOS deliberately (`board.js` — the floor is low *so that widening the board works*), while its nav item read the Government group's metic floor; the carve filed the gap rather than guessing at it. Latent today because the constitution seats `rank:archon`, live the moment an instance widens membership below Metic — the exact act [ADR 0175](<redacted>.md) §6 exists to make a CONFIG change — at which point those members reach the room by URL and by the `board_votes` deep link with no sidebar entry. **Decision: a `perm:<atom>` gate key, answered by the SERVER.** `NAV_PERMISSION_ATOMS` in `routes/me.js` is a frozen allowlist of the atoms a nav item may gate on; `/me` resolves each through the `government` port and returns `permissions: { atom: boolean }`; `gateAllows` reads that map and never re-derives it (the social-mode precedent — a page that decided for itself could disagree with the rest of the hall); the Board Room item declares the SAME key `requireBoardVotePage` checks, so the two cannot drift. An ALLOWLIST rather than the full resolved set, because the full set is the shape of the instance's governance and a cosmetic sidebar has no business shipping it on every page load. `permissions: null` (government off, port missing, resolver threw) means *cannot say*, not *holds nothing*, and HIDES the link — `default: false` and OFF-is-the-fallback, applied. Cosmetic throughout ([ADR 0016](<redacted>.md)): the page gate and each route's `requirePermission` are the wall. **The placement was the owner's call, and the reason this task existed:** gating honestly while leaving the item in the Government group would put a WIDER item under a narrower head, so a newcomer board member would see a group labelled "Government" holding one link — more authority than they hold. Declined; the item MOVES to the unlabeled project group after Tasks, because a sitting is work the project does, not a monitoring surface. Government's audience, head and floor are unchanged and it is rank surface throughout again. Also rejected: widening the Government head to show whenever any item inside is visible, and a single-item "Board" group. `tests/hall_nav.mjs` gained the MISSING HALF of its group invariant — it checked only that a head is never wider than its items, never that an item is wider than its own head (an orphan link under a hidden label, exactly the declined arrangement) — and `GATE_WIDTH` learned `government`, which had been `undefined` so every comparison against that group passed vacuously. The harness `/me` fixture carries `permissions`, or the item would be permanently invisible in `scripts/hall-preview`. **Not done:** no other item re-gated — `people` still reads `government` for `project.curate`.](<redacted>.md) | hall nav / permissions / government |
|
|
366
367
|
| 0272 | [**A craft is an identity, not a toolchain: `artist` becomes core** ([task 1003719](https://cloudbongos.com/builders#/task/1003719) · goal 1000111 — *Government*). Reported from Discord on 2026-08-15 as "cant save preferences of role types in account page"; reproduced live 2026-09-09: `GET /me` returns `preferred_disciplines: ["artist","ideator"]` and `PATCH /me/disciplines` with that SAME array returns `422 invalid_discipline {bad:["artist"]}` — the API handing a builder a value and refusing to accept it back. **Cause:** `updateBuilderDisciplines` validates against `enabledDisciplines()`, and R59 (task 1198) put `artist` in the module half on the reasoning that the craft existed to make the game pixel art, so it rode `art-pipeline`. **That gate had stopped existing** — art-pipeline was never converted to a discovered module, there is no `modules/art-pipeline/module.json`, and so NOTHING declared `contributes.disciplines: ["artist"]`. Measured on a FULL repo checkout with the pipeline present on disk: `["engineer","ideator","ui"]`. The craft was offered by no instance in any configuration, and is unreachable by construction on a published core anyway, since [ADR 0098](<redacted>.md) excludes `modules/art-pipeline/` for a hard copyright reason (ripped reference maps). Meanwhile it stayed first-class everywhere else: the `builders.preferred_disciplines` CHECK (086/176), `tasks.discipline`, and the help-request craft roster all accept it — so a TASK could be an artist task and a PERSON could not be an artist. **The tests were green because they skipped:** the artist assertion sat behind `if (!EFFECTIVE["art-pipeline"]) return;`, always false, so the only real leg never ran. **Decision:** `artist` joins `CORE_DISCIPLINES` → `["engineer","artist","ideator"]` (migration 176 enum order); modules still contribute their own (ui-design → ui). A craft names WHO A BUILDER IS, not which toolchain ships — an instance can have artists without a pixel-art pipeline, and binding the identity to one optional module was the category error. The new invariant, pinned with no skip guard over the widest module set: anything the DB CHECK will store, some instance shape must offer. Rejected: enabling art-pipeline (impossible — absent from the package, and enabling an absent module throws at boot); writing the missing manifest (works only in a full checkout); an instance-declared discipline list (leaves the core CHECK describing a craft core cannot offer, and does not fix the vanilla case); stripping unofferable crafts from stored rows (deletes a builder stated identity to make a validator mistake look consistent). No migration — the rows were always CHECK-valid; only the validator disagreed.](<redacted>.md) | crafts / instance composition |
|