@bongos/core 1.19.251 → 1.19.253
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 +45 -30
- package/clients/bongos-client/index.d.ts +1 -1
- package/docs/adr/0179-board-membership-predicate-grammar.md +113 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +3 -0
- package/docs/api-reference.md +1 -1
- package/docs/copy-inventory.md +46 -44
- package/docs/copy-registry.json +64 -46
- package/modules/government/board-membership.js +20 -20
- package/modules/government/board.js +63 -4
- package/modules/government/config.js +14 -8
- package/modules/government/db.js +18 -7
- package/modules/government/membership-predicate.js +115 -0
- package/modules/government/routes/government.js +19 -0
- package/modules/hall-ui/public/government.js +88 -9
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/run-unit-tests.js +5 -0
- package/src/module-api.js +5 -1
- package/tests/government_board_amendment.mjs +87 -1
- package/tests/government_board_membership.mjs +101 -10
- package/tests/government_board_membership_db.mjs +150 -0
- package/tests/government_board_vote.mjs +5 -3
- package/tests/government_charter_library.mjs +107 -1
- package/tests/government_constitution_view.mjs +53 -0
- package/tests/rank_tier_single_source.mjs +9 -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.253",
|
|
6
|
+
"core_contract": "1.19.253",
|
|
7
|
+
"source_commit": "7e98077a8a33b1ef861ab40823c1db64acffc71d",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-19T01:
|
|
9
|
+
"built_at": "2026-08-19T01:42:12.781Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 315,
|
|
13
13
|
"agent_docs_stubbed": 22,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1345,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1682,
|
|
20
|
+
"tree_sha256": "942f711051e57b17995dbd18b2ee81dee4466440acb5c3559659f72b4e0291ba",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -367,7 +367,7 @@
|
|
|
367
367
|
{
|
|
368
368
|
"path": "clients/bongos-client/index.d.ts",
|
|
369
369
|
"mode": "0000644",
|
|
370
|
-
"sha256": "
|
|
370
|
+
"sha256": "1c790a9dca384a70cba29ba814cff546cdd69eb784f512fb88e06c336b3f75a9"
|
|
371
371
|
},
|
|
372
372
|
{
|
|
373
373
|
"path": "clients/bongos-client/index.mjs",
|
|
@@ -1374,20 +1374,25 @@
|
|
|
1374
1374
|
"mode": "0000644",
|
|
1375
1375
|
"sha256": "36f95e2bd77c310e1be3632e9b9d84bf014d4d51ae73b450365ed353fefa8923"
|
|
1376
1376
|
},
|
|
1377
|
+
{
|
|
1378
|
+
"path": "docs/adr/0179-board-membership-predicate-grammar.md",
|
|
1379
|
+
"mode": "0000644",
|
|
1380
|
+
"sha256": "782900e7a2e49c14d7796d94e2b63493659e3c6c3ad8eb8bd721fa589307612b"
|
|
1381
|
+
},
|
|
1377
1382
|
{
|
|
1378
1383
|
"path": "docs/adr/README.md",
|
|
1379
1384
|
"mode": "0000644",
|
|
1380
|
-
"sha256": "
|
|
1385
|
+
"sha256": "bf9dddba0e72ab43ee6124fbba2a0479f1cb7dfc35ea4005f3382aae98e0066f"
|
|
1381
1386
|
},
|
|
1382
1387
|
{
|
|
1383
1388
|
"path": "docs/api-reference.md",
|
|
1384
1389
|
"mode": "0000644",
|
|
1385
|
-
"sha256": "
|
|
1390
|
+
"sha256": "3eb89e224eea00629fb69752c7606e9ffed34171ab16fcb82120e030e5db8476"
|
|
1386
1391
|
},
|
|
1387
1392
|
{
|
|
1388
1393
|
"path": "docs/api/openapi.json",
|
|
1389
1394
|
"mode": "0000644",
|
|
1390
|
-
"sha256": "
|
|
1395
|
+
"sha256": "2fc5bd12b302ff6ef0bc4e0012282bcef8616942c23e9c5f0fbe7c9e6a71d6a9"
|
|
1391
1396
|
},
|
|
1392
1397
|
{
|
|
1393
1398
|
"path": "docs/architecture.md",
|
|
@@ -1412,12 +1417,12 @@
|
|
|
1412
1417
|
{
|
|
1413
1418
|
"path": "docs/copy-inventory.md",
|
|
1414
1419
|
"mode": "0000644",
|
|
1415
|
-
"sha256": "
|
|
1420
|
+
"sha256": "46a8ae1438d900645252af1e71da65863b247920899bf9fdb941a7f5f2b09b60"
|
|
1416
1421
|
},
|
|
1417
1422
|
{
|
|
1418
1423
|
"path": "docs/copy-registry.json",
|
|
1419
1424
|
"mode": "0000644",
|
|
1420
|
-
"sha256": "
|
|
1425
|
+
"sha256": "7d1918a10be54cf4fedf58152c50950b89e548a2c5bc8d04a553523b5263058b"
|
|
1421
1426
|
},
|
|
1422
1427
|
{
|
|
1423
1428
|
"path": "docs/design-contract.md",
|
|
@@ -2977,12 +2982,12 @@
|
|
|
2977
2982
|
{
|
|
2978
2983
|
"path": "modules/government/board-membership.js",
|
|
2979
2984
|
"mode": "0000644",
|
|
2980
|
-
"sha256": "
|
|
2985
|
+
"sha256": "26f212bfb64239e98418618ec3304664180b06f3740b8c4f682f29d3bbf2f3e9"
|
|
2981
2986
|
},
|
|
2982
2987
|
{
|
|
2983
2988
|
"path": "modules/government/board.js",
|
|
2984
2989
|
"mode": "0000644",
|
|
2985
|
-
"sha256": "
|
|
2990
|
+
"sha256": "e6e01e38328eb6d234c76d7f5aa9e96589fd2c9ef89c105216495303ac1e3fdd"
|
|
2986
2991
|
},
|
|
2987
2992
|
{
|
|
2988
2993
|
"path": "modules/government/catalog.js",
|
|
@@ -2992,12 +2997,17 @@
|
|
|
2992
2997
|
{
|
|
2993
2998
|
"path": "modules/government/config.js",
|
|
2994
2999
|
"mode": "0000644",
|
|
2995
|
-
"sha256": "
|
|
3000
|
+
"sha256": "62fc47cb6775b1af86bf7f22773180b390d584230b87af6bb1ec20a841be57e1"
|
|
2996
3001
|
},
|
|
2997
3002
|
{
|
|
2998
3003
|
"path": "modules/government/db.js",
|
|
2999
3004
|
"mode": "0000644",
|
|
3000
|
-
"sha256": "
|
|
3005
|
+
"sha256": "6d85422869793382fc372a43b6a2b745bcb0e8a62c4fec06b780e812ae5066b9"
|
|
3006
|
+
},
|
|
3007
|
+
{
|
|
3008
|
+
"path": "modules/government/membership-predicate.js",
|
|
3009
|
+
"mode": "0000644",
|
|
3010
|
+
"sha256": "57a1054cb3ae65e8b6d9af6657601ea67a20dd658daf442e0fca729165a55461"
|
|
3001
3011
|
},
|
|
3002
3012
|
{
|
|
3003
3013
|
"path": "modules/government/migrations/governance_001_tables.sql",
|
|
@@ -3107,7 +3117,7 @@
|
|
|
3107
3117
|
{
|
|
3108
3118
|
"path": "modules/government/routes/government.js",
|
|
3109
3119
|
"mode": "0000644",
|
|
3110
|
-
"sha256": "
|
|
3120
|
+
"sha256": "33f0cf42c404e087a6f3817392299dd2a478f41f748932e108747059b756e703"
|
|
3111
3121
|
},
|
|
3112
3122
|
{
|
|
3113
3123
|
"path": "modules/government/routes/rank-sync.js",
|
|
@@ -3372,7 +3382,7 @@
|
|
|
3372
3382
|
{
|
|
3373
3383
|
"path": "modules/hall-ui/public/government.js",
|
|
3374
3384
|
"mode": "0000644",
|
|
3375
|
-
"sha256": "
|
|
3385
|
+
"sha256": "7f8a1ab330391d564e88420fc3577e861869269e71eccaec4d1924f9544b5889"
|
|
3376
3386
|
},
|
|
3377
3387
|
{
|
|
3378
3388
|
"path": "modules/hall-ui/public/hall-kit.js",
|
|
@@ -4422,12 +4432,12 @@
|
|
|
4422
4432
|
{
|
|
4423
4433
|
"path": "package-lock.json",
|
|
4424
4434
|
"mode": "0000644",
|
|
4425
|
-
"sha256": "
|
|
4435
|
+
"sha256": "ade08bd4afada8f234fdb5bd155ad37628bc01040f76de7a378356d338933721"
|
|
4426
4436
|
},
|
|
4427
4437
|
{
|
|
4428
4438
|
"path": "package.json",
|
|
4429
4439
|
"mode": "0000644",
|
|
4430
|
-
"sha256": "
|
|
4440
|
+
"sha256": "2278bb9510a79406d9ee34e9662df8a81273343075d382d7241b0f360650d199"
|
|
4431
4441
|
},
|
|
4432
4442
|
{
|
|
4433
4443
|
"path": "public-docs/index.html",
|
|
@@ -5417,7 +5427,7 @@
|
|
|
5417
5427
|
{
|
|
5418
5428
|
"path": "scripts/gds/run-unit-tests.js",
|
|
5419
5429
|
"mode": "0000644",
|
|
5420
|
-
"sha256": "
|
|
5430
|
+
"sha256": "96e8495ffded28083ba458e8d527c5ba25793a20d5f4c0fee5160cff778f4def"
|
|
5421
5431
|
},
|
|
5422
5432
|
{
|
|
5423
5433
|
"path": "scripts/gds/sandbox-stage.js",
|
|
@@ -6047,7 +6057,7 @@
|
|
|
6047
6057
|
{
|
|
6048
6058
|
"path": "src/module-api.js",
|
|
6049
6059
|
"mode": "0000644",
|
|
6050
|
-
"sha256": "
|
|
6060
|
+
"sha256": "a12419c00b425a6b98a646cb98e5d20584863185779453e88c52c7036ac1ff62"
|
|
6051
6061
|
},
|
|
6052
6062
|
{
|
|
6053
6063
|
"path": "src/module-loader/catalog.js",
|
|
@@ -6992,7 +7002,7 @@
|
|
|
6992
7002
|
{
|
|
6993
7003
|
"path": "tests/government_board_amendment.mjs",
|
|
6994
7004
|
"mode": "0000644",
|
|
6995
|
-
"sha256": "
|
|
7005
|
+
"sha256": "54b0d380414a077b072080760a24cfc813d22249f8786b96003ab2e010081b4d"
|
|
6996
7006
|
},
|
|
6997
7007
|
{
|
|
6998
7008
|
"path": "tests/government_board_close.mjs",
|
|
@@ -7007,7 +7017,12 @@
|
|
|
7007
7017
|
{
|
|
7008
7018
|
"path": "tests/government_board_membership.mjs",
|
|
7009
7019
|
"mode": "0000644",
|
|
7010
|
-
"sha256": "
|
|
7020
|
+
"sha256": "aa36727bb3fe750783f0adb82235fa7b2e5b1013c01b773a12d2dff6f0c5ce78"
|
|
7021
|
+
},
|
|
7022
|
+
{
|
|
7023
|
+
"path": "tests/government_board_membership_db.mjs",
|
|
7024
|
+
"mode": "0000644",
|
|
7025
|
+
"sha256": "7da08f5ad67ca1d42d0ef2afb641d1a5f1d5e0c6e0267e8b5f3ad0d275603a36"
|
|
7011
7026
|
},
|
|
7012
7027
|
{
|
|
7013
7028
|
"path": "tests/government_board_view.mjs",
|
|
@@ -7017,7 +7032,7 @@
|
|
|
7017
7032
|
{
|
|
7018
7033
|
"path": "tests/government_board_vote.mjs",
|
|
7019
7034
|
"mode": "0000644",
|
|
7020
|
-
"sha256": "
|
|
7035
|
+
"sha256": "ed31b8c643d2b9279f4d7e70a1f2ab67d8fbe620c1f61ce8496ec858247d7d26"
|
|
7021
7036
|
},
|
|
7022
7037
|
{
|
|
7023
7038
|
"path": "tests/government_board_vote_ui.mjs",
|
|
@@ -7037,7 +7052,7 @@
|
|
|
7037
7052
|
{
|
|
7038
7053
|
"path": "tests/government_charter_library.mjs",
|
|
7039
7054
|
"mode": "0000644",
|
|
7040
|
-
"sha256": "
|
|
7055
|
+
"sha256": "947af198213dea4f041c7391c1deb28e1db62347d2cbdf18d8be6a4cc252f805"
|
|
7041
7056
|
},
|
|
7042
7057
|
{
|
|
7043
7058
|
"path": "tests/government_claim_eligibility.mjs",
|
|
@@ -7052,7 +7067,7 @@
|
|
|
7052
7067
|
{
|
|
7053
7068
|
"path": "tests/government_constitution_view.mjs",
|
|
7054
7069
|
"mode": "0000644",
|
|
7055
|
-
"sha256": "
|
|
7070
|
+
"sha256": "1fb474588046f937319bde1121f66428300cc0f63e36766ab4543c684a5a09bc"
|
|
7056
7071
|
},
|
|
7057
7072
|
{
|
|
7058
7073
|
"path": "tests/government_fold.mjs",
|
|
@@ -7847,7 +7862,7 @@
|
|
|
7847
7862
|
{
|
|
7848
7863
|
"path": "tests/rank_tier_single_source.mjs",
|
|
7849
7864
|
"mode": "0000644",
|
|
7850
|
-
"sha256": "
|
|
7865
|
+
"sha256": "92ab673d4e865c98c9d70ce326b99bee56aab70a3852f2d205af209de8d196ed"
|
|
7851
7866
|
},
|
|
7852
7867
|
{
|
|
7853
7868
|
"path": "tests/rate_limit_public.mjs",
|
|
@@ -237,7 +237,7 @@ export interface PostGoalsRequest { version_id: string; title: string; descripti
|
|
|
237
237
|
export interface PostGoalsResponse { ok: boolean; goal: unknown; succeeds_goal_id: unknown }
|
|
238
238
|
export interface PostGovernmentAssignmentsRequest { builder_id: number; rank_key: string }
|
|
239
239
|
export interface PostGovernmentBoardItemsItemIdVotesRequest { direction: string; reason_md?: string; section_key?: string }
|
|
240
|
-
export interface PostGovernmentBoardItemsRequest { subject_type: string; proposal: Record<string, unknown>; rationale_md?: string }
|
|
240
|
+
export interface PostGovernmentBoardItemsRequest { subject_type: string; proposal: Record<string, unknown>; rationale_md?: string; acknowledge_self_removal?: boolean }
|
|
241
241
|
export interface PostGovernmentBoardItemsResponse { ok: boolean; amendment: unknown; item: unknown }
|
|
242
242
|
export interface PostGovernmentRanksRankKeyResetResponse { ok: boolean; rank_key: unknown; seeded_rank: unknown; permission_count: unknown }
|
|
243
243
|
export interface PostGovernmentRanksRequest { rank_key: string; label: string; description?: string }
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# 0179 — `rank:metic+` means metic and above; a bare `rank:metic` still means exactly metic
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-08-19
|
|
5
|
+
- **Tasks:** [#1003094](https://cloudbongos.com/builders#/task/1003094) (goal 1000069, the Board Room). Blocks R25's sign-off ([#1003038](https://cloudbongos.com/builders#/task/1003038)); unblocks the charter-library fix ([#1003095](https://cloudbongos.com/builders#/task/1003095))
|
|
6
|
+
- **Amends:** [ADR 0175](<redacted>.md) §7 — the constitution's `board.membership` predicate
|
|
7
|
+
- **Related:** ADR 0016 (authority server-side, per request, uncached), 0034 (rank inheritance), 0174 (ranks are ranks), 0083 (a module may not import a sibling)
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
ADR 0175 gave the constitution a membership predicate with exactly one form, `rank:<key>`, resolved by
|
|
12
|
+
`WHERE rank_key = $1`. An **exact** match. So:
|
|
13
|
+
|
|
14
|
+
- `rank:metic` seated metics **only** — Archons excluded;
|
|
15
|
+
- `rank:xenos` seated **newcomers only**, not "everyone";
|
|
16
|
+
- **"Metic and above" was inexpressible.**
|
|
17
|
+
|
|
18
|
+
Everywhere else in this system a rank is a **ladder** and authority is cumulative — ADR 0034's
|
|
19
|
+
inheritance, the floor-derived permission seed, `requireRank('metic','archon')`. Board membership was
|
|
20
|
+
the single place `rank:X` meant *exactly* X, and nothing said so.
|
|
21
|
+
|
|
22
|
+
Rehearsing R25 end to end on a real Postgres found what that costs. An Archon proposed and ratified
|
|
23
|
+
`{membership: 'rank:metic', pass_rule: 'consent', …}` under the day-one monarchy. The apply worked
|
|
24
|
+
perfectly; the live constitution flipped. Then they tried to vote on the next Full Idea:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
POST /government/board/items/7/votes → 403 { code: 'not_board_member' }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**The owner flipped the switch and fell off their own board.** The escape hatch that saved the sandbox
|
|
31
|
+
is itself the diagnosis: an Archon can still *propose* (`board.item.open` floors at metic) but not
|
|
32
|
+
*vote*, so the board would have deadlocked at one member with no one able to undo it.
|
|
33
|
+
|
|
34
|
+
The charter library made the same mistake one layer up — it told owners Direct Democracy was
|
|
35
|
+
selectable today by typing `membership rank:xenos (everyone)`, which seats newcomers only. Its own
|
|
36
|
+
recommended charter, "a consent circle of all trusted members", was not expressible at all.
|
|
37
|
+
|
|
38
|
+
## Decision
|
|
39
|
+
|
|
40
|
+
### 1. Explicit grammar — a bare key still means exactly that key
|
|
41
|
+
|
|
42
|
+
The owner's decision (2026-08-18), recorded on the task. Three forms:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
rank:metic exactly metic (v1 form, meaning UNCHANGED)
|
|
46
|
+
rank:metic+ metic and every rank above it (ladder)
|
|
47
|
+
rank:metic,archon exactly these ranks (union)
|
|
48
|
+
rank:xenos+,ops the ladder from xenos, plus a custom rank
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Rejected — Option 1, redefining the bare form as "and above."** It was the smaller diff and it
|
|
52
|
+
matched every other authority read in the system, which is exactly what made it dangerous: it would
|
|
53
|
+
have silently changed the meaning of every constitution already ratified, including the shipped
|
|
54
|
+
monarchy default, the moment an instance upgraded. **Nothing in a government should change meaning
|
|
55
|
+
without a vote.** Because the bare form is untouched, this whole change is meaning-preserving.
|
|
56
|
+
|
|
57
|
+
**Rejected — Option 3, keep exact-match and fix only the prose.** Cheapest, and it leaves the ladder
|
|
58
|
+
inconsistency and the foot-gun in place. The library would have had to describe a limitation instead
|
|
59
|
+
of stating a rule.
|
|
60
|
+
|
|
61
|
+
### 2. `+` is defined only on the standard ladder
|
|
62
|
+
|
|
63
|
+
`RANK_ORDER` is the four-rung standard ladder; a project's **custom** ranks are DB rows with no
|
|
64
|
+
position on it. "Metic and above" is a real question; "and above" about a rank that sits on no ladder
|
|
65
|
+
is not. So `rank:some-custom+` is a **parse refusal**, not a guess — and a refusal resolves to
|
|
66
|
+
**nobody**, per the module's existing fail-closed rule. A bare custom key is unaffected.
|
|
67
|
+
|
|
68
|
+
### 3. The grammar is written once
|
|
69
|
+
|
|
70
|
+
It had been written **twice**: a shape regex in R05's config sanitizer and a parse regex in R06's
|
|
71
|
+
resolver, held together by a property test asserting "config keeps it ⇔ the resolver parses it". That
|
|
72
|
+
is sound while the grammar is one literal form. It stops being sound the moment a rule needs a
|
|
73
|
+
*lookup* — and `+` is a lookup. **Two hand-written copies of a rule containing a lookup is the same
|
|
74
|
+
class of drift that produced this bug**, so both now call one pure module,
|
|
75
|
+
`modules/government/membership-predicate.js` (it requires only `catalog`, so the sanitizer can use it
|
|
76
|
+
during boot without dragging in the DB layer).
|
|
77
|
+
|
|
78
|
+
### 4. The disenfranchisement guard
|
|
79
|
+
|
|
80
|
+
A change to the government that removes the person making it is **the one shape that cannot
|
|
81
|
+
self-correct**: once it lands they can no longer vote, including to undo it.
|
|
82
|
+
|
|
83
|
+
`proposeAmendment` now refuses such a proposal **before any write**, naming the one-character fix
|
|
84
|
+
(`rank:<key>+`). It is overridable with an explicit `acknowledge_self_removal` — the owner's "refuse or
|
|
85
|
+
loudly confirm" — because handing the franchise to a wider body and stepping back is a legitimate
|
|
86
|
+
constitutional act, and a system that cannot express it is telling owners what they may decide. The
|
|
87
|
+
acknowledgement is a **separate parameter, never a proposal field**: the strict validator refuses
|
|
88
|
+
unknown fields, which is what keeps it out of the ratified text.
|
|
89
|
+
|
|
90
|
+
### 5. The surfaces must describe the franchise they actually have
|
|
91
|
+
|
|
92
|
+
The hall's Constitution panel rendered the raw predicate, which would have read `rank:metic+` as
|
|
93
|
+
"builders holding the **metic+** rank" — naming a rank that does not exist and hiding what the suffix
|
|
94
|
+
does. **A panel that misdescribes who governs is this same bug one layer up**, so it now renders every
|
|
95
|
+
form in plain language, and says plainly that an unreadable predicate seats nobody.
|
|
96
|
+
|
|
97
|
+
## Consequences
|
|
98
|
+
|
|
99
|
+
- **No ratified constitution changes meaning.** A one-element rank set queries byte-identically to the
|
|
100
|
+
old exact match. `rank:archon` — the shipped default — resolves to the same board it always did.
|
|
101
|
+
- **The two DB reads take a set** (`= ANY($1)`), with **DISTINCT**: a set can now match a builder
|
|
102
|
+
through more than one key, and a double-counted member silently raises the bar a consent vote must
|
|
103
|
+
clear.
|
|
104
|
+
- **The structural filters had to survive the rewrite**, and are tested against a real Postgres: a
|
|
105
|
+
deactivated builder and a machine principal still never govern. A widened franchise that also admits
|
|
106
|
+
a bot would be a worse bug than the one being fixed.
|
|
107
|
+
- **The parse result shape changed** from `{ rankKey }` to `{ rankKeys }`. Deliberate rather than
|
|
108
|
+
cosmetic — a caller reading "the" key was reading a single-rank assumption that is no longer true.
|
|
109
|
+
- **Task 1003095 can now write the TRUE predicate for each charter** — Direct Democracy becomes
|
|
110
|
+
`rank:xenos+`, Timocratic Republic `rank:metic+` — rather than rewording around a limitation. That
|
|
111
|
+
is the sibling fix this ADR unblocks, and it is what makes R26's honesty half pass.
|
|
112
|
+
- **Still not a source of authority truth.** The predicate names ranks; who holds a rank stays a DB
|
|
113
|
+
row resolved per request, uncached. Nothing here names a builder.
|
package/docs/adr/README.md
CHANGED
|
@@ -252,5 +252,6 @@ This keeps the decision history honest and traceable.
|
|
|
252
252
|
| 0176 | [**Private-repo standup: a per-repo read-only deploy key, minted with the owner's token** ([task 1002735](https://cloudbongos.com/builders#/task/1002735), goal 1000032; walk finding N4, seen live on `mercury` 2026-08-17). A standalone instance's repo is cloned/pushed once with the owner's `repo`-scoped token (ADR 0155), but the durable `origin` rides the shared SSH deploy alias — which GitHub answers "Repository not found" for a private repo it isn't registered on, so every private standup failed at the recurring `git pull --ff-only origin main`, **token or not**. Fix: when the target repo answers *private* to the owner's stored token, provisioning mints the instance its OWN ed25519 deploy key beside the checkout (`.git/cloudbongos-deploy-key` — untracked, survives `git reset --hard`, dies with the checkout), registers it on the repo **read-only** as `cloudbongos-deploy-<slug>` (idempotent by title; a stale same-title key is replaced, never duplicated), then wires the CHECKOUT: `origin` re-pointed at plain `git@github.com` and `core.sshCommand` pinned to the key with `-F /dev/null` so no `~/.ssh/config` identity can shadow it. Public repos take the unchanged shared-alias path; pushes keep preferring the owner-token HTTPS URL (task 1002317), which is why read-only suffices; teardown best-effort revokes by title. Rejected: **GitHub App installation tokens** (hourly expiry means every unattended pull needs a minting sidecar or a hub round-trip) and **registering the shared platform key on the repo** (one shared secret granting the whole platform durable access to private code; per-repo keys keep the blast radius to one repo and are visible to the owner in their own repo settings). Follow-ups: the manage page's flip-to-private stays refused until already-provisioned instances get the same wiring, and builder dev boxes of a private-repo project need their own leg.](<redacted>.md) | provisioning / security |
|
|
253
253
|
| 0177 | [**A goal membership is a `membership_kind`, and the rename ships expand-then-contract** ([task 1003013](https://cloudbongos.com/builders#/task/1003013), goal 1000068). ADR 0174 reserved `role` for the three crafts and enforced it on the GOVERNMENT surface only — by design, since `builders.system_role` and Discord's own noun are legitimate. That left a **fourth** use uncatalogued: `goal_members.role` (`'lead' | 'member'`), the MANAGER predicate behind goal authorization. Renamed to **`membership_kind`** — values unchanged, because the noun was the problem; `rank` is wrong (not a ladder position) and `position` is worse (ADR 0174 uses it as the prose word for a rank, recreating the "rank-role" qualifier collision). A boolean `is_lead` was seriously considered and rejected: the text rename keeps the copied CHECK and stays a reversible change on a live authz column, where `ADD boolean; UPDATE; DROP text` is data-destructive. The migration is **expand-then-contract, never `RENAME COLUMN`** — auto-rollback reverts the code pin and NOT the DB, so `core_220` ADDs the column and keeps the pair in lockstep with a BEFORE trigger (direction decided by which side changed), and a later task DROPs the old column with the `-- forward-unsafe` pragma, a backup, and its own window; no `SET NOT NULL` in step 1, because a static NOT NULL on a column the previous release never heard of is what the forward-safety gate rightly flags. The API field renames with it and carries **no `role` alias** (ADR 0174's Lineage lesson: a time-boxed shim outlives its release), pinned by the test of its absence. The reservation gains a **second narrow wall** rather than a widened first one — line-scoped inside the 5k-line `db.js` to the lines that name `goal_members`, so no allowlist is needed. A **fifth** use (`project_memberships.role`) is catalogued and filed, with this as its pattern.](<redacted>.md) | naming / lifecycle |
|
|
254
254
|
| 0178 | [**The copy desk: a flag carries a judgement, and no column here could ever hold copy** ([task 1003113](https://cloudbongos.com/builders#/task/1003113), R02 of goal 1000074). [ADR 0081](<redacted>.md) made the repo the source of truth for design TOKENS; user-facing TEXT had no equivalent. R01 built the inventory (`copy-inventory.js` → `docs/copy-registry.json`), which answers *what does the product say* but cannot answer *what is wrong with it* — a judgement a person must record. This is that record plus the artist's queue. A new default-on module keyed **`copy-desk`, not `copy`**, for a concrete reason: the branding contract already owns a `copy` block, so the bare word reds the `core carries no module code` fitness check on the English word in `src/branding.js` — and the editorial "copy desk" is the more accurate name anyway. **The reason is required in the route AND in a CHECK constraint**: the route gives a person a usable error, the constraint means no future write path can create the reasonless flag the queue exists to not contain (a pointer with no judgement is *worse* than no flag — it still costs the artist a look). Closing requires a note by the same symmetry, since an unexplained close is indistinguishable from clearing a queue to move a number. The load-bearing decision is **NO LIVE CMS enforced by the schema's SHAPE**: `copy_desk_flags` has no column that could hold replacement text and must never gain one, because that non-goal does not erode via an "add a CMS" commit but via one plausible column, then one plausible read of it — `copy_no_cms.mjs` pins the write surface at exactly two verbs so R03's proposal path changes it in a reviewable diff. Rank is asymmetric: **filing is open to every rank** (the engineer who wrote the toast and the newcomer who could not read the button are the likeliest noticers, and neither is the artist — the reason floor is the quality gate, rank is not), closing is Metic+ **or the author** (withdrawing your own flag is a correction, not triage); nothing gates on craft, since an artist is a craft and not a rank. Duplicates are the only automatic signal (the one of stale/duplicated/off-voice needing no judgement layer) and the count **subtracts settled work**, or it never moves and teaches that the queue is decorative. A vanished target is **reported, never auto-closed** — the id is a hash over the text, so an edit routinely orphans a flag, and closing a human judgement because a hash moved is the family of error ADR 0162 rules out. Deliberately reports **no `generated_at`**: R01's contract is determinism, so a clock is the one field it cannot carry.](<redacted>.md) | design / artist loop |
|
|
255
|
+
| 0179 | [**`rank:metic+` means metic and above; a bare `rank:metic` still means exactly metic** ([task 1003094](https://cloudbongos.com/builders#/task/1003094), goal 1000069). [ADR 0175](<redacted>.md) gave the constitution ONE membership form, `rank:<key>`, resolved by an EXACT SQL match — so `rank:metic` seated metics ONLY (excluding Archons), `rank:xenos` seated NEWCOMERS ONLY rather than "everyone", and "Metic and above" was inexpressible. Everywhere else a rank is a LADDER and authority is cumulative (ADR 0034 inheritance, requireRank('metic','archon'), the floor-derived seed); board membership was the one place it meant "exactly", and nothing said so. Rehearsing R25 on a real Postgres, an Archon ratified `rank:metic` under monarchy — the apply worked perfectly — and the next vote came back 403 not_board_member: **the owner flipped the switch and fell off their own board**, with the board deadlocked at one member because an Archon can still PROPOSE but no longer VOTE. Owner decision: EXPLICIT GRAMMAR. `rank:metic+` (that rank and above), `rank:metic,archon` (union), and **a bare key still means EXACTLY that key** — rejecting the smaller diff (silently redefining the bare form) precisely because it would have changed the meaning of every already-ratified constitution, the monarchy default included, on upgrade: nothing in a government should change meaning without a vote. `+` is defined only on the STANDARD ladder — "and above" about a custom rank with no ladder position is a parse REFUSAL, not a guess, and a refusal seats nobody. The grammar had been written TWICE (a shape regex in the config sanitizer, a parse regex in the resolver, pinned by a property test) — sound for one literal form, unsound the moment a rule needs a LOOKUP, which is the same class of drift that produced the bug; both now call one pure `membership-predicate.js`. Adds the DISENFRANCHISEMENT GUARD: a proposal that would unseat its own proposer is refused before any write, naming the one-character fix, overridable with an explicit `acknowledge_self_removal` (a separate parameter, never a proposal field) because stepping back is a legitimate act. The two DB reads take a set via `= ANY($1)` + DISTINCT (a double-counted member silently raises the consent bar), the structural filters are re-proven against real Postgres (a deactivated builder and a machine principal still never govern), and the hall panel now describes every form in plain language — rendering `rank:metic+` as "the metic+ rank" would be the same bug one layer up. Unblocks task 1003095, which can now write each charter's TRUE predicate rather than wording around a limitation.](<redacted>.md) | government / the board |
|
|
255
256
|
|
|
256
257
|
> ⚠️ **Numbering collision (known):** two ADRs share `0008`. The numbers were assigned in parallel sessions before the lock could be enforced. The files are kept under their existing filenames to preserve git history, but the table disambiguates them as `0008-a` / `0008-b`. New ADRs should resume at `0040` to avoid further collisions. (`0024` is also a real on-disk duplicate: the cloneable-repo/local-first-memory ADR and the multi-agent-system-architecture ADR were both numbered `0024` in parallel; both keep their filenames, disambiguated as `0024-a` / `0024-b` like `0008`. Likewise `0025` ended up with two on-disk files — the offsite-backup ADR and the structured-criterion↔task-link ADR — both kept under their existing filenames and disambiguated as `0025-a` / `0025-b`; separately, the BFG ADR had also claimed `0025` on 2026-05-30 and yielded to `0026`. A third happened 2026-06-06: `0036` was claimed in parallel by the builders-subdomain ADR ([#726](https://example.com/builders#/task/726)) and this Discord-channels ADR ([#727](https://example.com/builders#/task/727)); the Discord-channels ADR yielded to `0037`. A fourth landed 2026-06-10: `0044` was claimed in parallel by the palette-replacement ADR and the per-box game-preview ADR ([#897](https://example.com/builders#/task/897)); both keep their filenames, disambiguated as `0044-a` / `0044-b` like `0008`. A fifth was caught pre-land 2026-06-13: `0050` was claimed in parallel by the device-flow-admission ADR ([#1000](https://example.com/builders#/task/1000)) and the sandbox-for-everyone ADR ([#1001](https://example.com/builders#/task/1001)); the latter yielded to `0052` before merging, so there is no `0050` duplicate. A sixth was caught pre-land 2026-06-21: the tool-agnostic-design-layer ADR ([#1375](https://example.com/builders#/task/1375)) first collided at `0079` with the value-gated-autonomous-engagement ADR, then at `0080` with the llm-cache-api-transport ADR (both part of the 0075–0080 LLM-cost-reduction series, landed on `main` in parallel); it yielded to `0081` before merging, so there is no `0079`/`0080` design-layer duplicate. A seventh landed 2026-06-23: `0084` was claimed in parallel by the auto-derived-per-task-rank-floor ADR and the remove-default-spend-cap ADR (task 1499); the rank-floor ADR landed on `main` first, so the spend-cap ADR yielded to `0085`, leaving no `0084` duplicate. An eighth landed 2026-06-27: `0095` was claimed in parallel by the borrowed-memory-and-retrieval-concepts ADR (PR #459, landed first) and the cross-agent-context-management ADR ([#1651](https://example.com/builders#/task/1651), PR #460); both keep their filenames, disambiguated as `0095-a` / `0095-b` like `0008`. These two are companions — `0095-a` adopts the memory/retrieval concepts and explicitly defers the grep-vs-semantic question to `0095-b`.)
|
package/docs/api/openapi.json
CHANGED
|
@@ -16483,6 +16483,9 @@
|
|
|
16483
16483
|
"rationale_md": {
|
|
16484
16484
|
"type": "string",
|
|
16485
16485
|
"maxLength": 5000
|
|
16486
|
+
},
|
|
16487
|
+
"acknowledge_self_removal": {
|
|
16488
|
+
"type": "boolean"
|
|
16486
16489
|
}
|
|
16487
16490
|
},
|
|
16488
16491
|
"required": [
|
package/docs/api-reference.md
CHANGED
|
@@ -233,7 +233,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
233
233
|
| POST | `/api/bongos/government/assignments` | `archon` | `builder_id`, `rank_key` | government.manage — assign a CUSTOM rank to a builder. |
|
|
234
234
|
| DELETE | `/api/bongos/government/assignments/:builderId/:rankKey` | `archon` | — | government.manage — remove a custom-rank assignment. |
|
|
235
235
|
| GET | `/api/bongos/government/board/items` | `any-builder` | — | board.vote.cast — the Board Room view: open items with their clock, the OPEN BALLOT, the closed history (R16 of goal 1000069). |
|
|
236
|
-
| POST | `/api/bongos/government/board/items` | `metic+archon` | `subject_type`, `proposal`, `rationale_md` | board.item.open — put an AMENDMENT to the board (R14 of goal 1000069, ADR 0175 §7). |
|
|
236
|
+
| POST | `/api/bongos/government/board/items` | `metic+archon` | `subject_type`, `proposal`, `rationale_md`, `acknowledge_self_removal` | board.item.open — put an AMENDMENT to the board (R14 of goal 1000069, ADR 0175 §7). |
|
|
237
237
|
| POST | `/api/bongos/government/board/items/:itemId/votes` | `any-builder` | `direction`, `reason_md`, `section_key` | board.vote.cast — cast a vote on an open board item (R08 of goal 1000069, ADR 0175). |
|
|
238
238
|
| GET | `/api/bongos/government/constitution` | `any-builder` | — | board.vote.cast — the Constitution view (R18 of goal 1000069): the live board block plus the full amendment history, passed and returned … |
|
|
239
239
|
| GET | `/api/bongos/government/permissions` | `archon` | — | government.manage — the permission catalog + principals, straight from code (the catalog is CODE, not a table); no DB read. |
|