@bongos/core 1.19.233 → 1.19.235
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 +34 -24
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +2 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/api/openapi.json +35 -1
- package/docs/api-reference.md +3 -2
- package/modules/government/board.js +52 -0
- package/modules/government/db.js +35 -0
- package/modules/government/routes/government.js +15 -0
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/government.js +144 -10
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/bongos/route-rank-check.js +3 -0
- package/src/module-api.js +5 -1
- package/tests/government_charter_library.mjs +77 -0
- package/tests/government_constitution_view.mjs +88 -0
- package/tests/government_routes.mjs +5 -2
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.235",
|
|
6
|
+
"core_contract": "1.19.235",
|
|
7
|
+
"source_commit": "b91cbff1532b00e3cdd2204e713b35cf093c9b02",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-18T16:
|
|
9
|
+
"built_at": "2026-08-18T16:46:17.722Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 311,
|
|
13
13
|
"agent_docs_stubbed": 21,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1319,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1651,
|
|
20
|
+
"tree_sha256": "d20a6edcee7ad8a7e81eefec0b07689a820b194634cdb5350c8c4263127990ea",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -347,12 +347,12 @@
|
|
|
347
347
|
{
|
|
348
348
|
"path": "clients/bongos-client/README.md",
|
|
349
349
|
"mode": "0000644",
|
|
350
|
-
"sha256": "
|
|
350
|
+
"sha256": "5084f59df8019560f49971f00b40e9b8d1d6eb9511a9a78fc136ecaff025e57d"
|
|
351
351
|
},
|
|
352
352
|
{
|
|
353
353
|
"path": "clients/bongos-client/bongos-client.global.js",
|
|
354
354
|
"mode": "0000644",
|
|
355
|
-
"sha256": "
|
|
355
|
+
"sha256": "84050a82e7dc6f855b6b02a4c89b1321717b1fcf45fdc426ac6c53961f10012f"
|
|
356
356
|
},
|
|
357
357
|
{
|
|
358
358
|
"path": "clients/bongos-client/examples/hello-world.mjs",
|
|
@@ -362,17 +362,17 @@
|
|
|
362
362
|
{
|
|
363
363
|
"path": "clients/bongos-client/index.cjs",
|
|
364
364
|
"mode": "0000644",
|
|
365
|
-
"sha256": "
|
|
365
|
+
"sha256": "5c99db7b6b1b59e595c3c6a7002cf7baa3b130a198213b007448d02a427883f1"
|
|
366
366
|
},
|
|
367
367
|
{
|
|
368
368
|
"path": "clients/bongos-client/index.d.ts",
|
|
369
369
|
"mode": "0000644",
|
|
370
|
-
"sha256": "
|
|
370
|
+
"sha256": "14c4cc02b3965badc021489bb7dd0f80bb71c3cc57b2e1dc4dafa76d86128088"
|
|
371
371
|
},
|
|
372
372
|
{
|
|
373
373
|
"path": "clients/bongos-client/index.mjs",
|
|
374
374
|
"mode": "0000644",
|
|
375
|
-
"sha256": "
|
|
375
|
+
"sha256": "d5de9d514dc1515fab89430301550566fe5c8c5941026c58eb107800746fb63c"
|
|
376
376
|
},
|
|
377
377
|
{
|
|
378
378
|
"path": "clients/bongos-client/package.json",
|
|
@@ -1372,12 +1372,12 @@
|
|
|
1372
1372
|
{
|
|
1373
1373
|
"path": "docs/api-reference.md",
|
|
1374
1374
|
"mode": "0000644",
|
|
1375
|
-
"sha256": "
|
|
1375
|
+
"sha256": "c0a604b304ff9936d08998b2b4206e7ecad8ff668ec704b08161c5d79d0bc04c"
|
|
1376
1376
|
},
|
|
1377
1377
|
{
|
|
1378
1378
|
"path": "docs/api/openapi.json",
|
|
1379
1379
|
"mode": "0000644",
|
|
1380
|
-
"sha256": "
|
|
1380
|
+
"sha256": "be4bc7b6dd627fa7ea85e4a90a908588b81d6d5a5d4eb5a6f2c1858f155538f7"
|
|
1381
1381
|
},
|
|
1382
1382
|
{
|
|
1383
1383
|
"path": "docs/architecture.md",
|
|
@@ -2902,7 +2902,7 @@
|
|
|
2902
2902
|
{
|
|
2903
2903
|
"path": "modules/government/board.js",
|
|
2904
2904
|
"mode": "0000644",
|
|
2905
|
-
"sha256": "
|
|
2905
|
+
"sha256": "9d3d9414ca1c1bafc901a7343cdccafc7261b0dba492134a9a1064e6b3d82b61"
|
|
2906
2906
|
},
|
|
2907
2907
|
{
|
|
2908
2908
|
"path": "modules/government/catalog.js",
|
|
@@ -2917,7 +2917,7 @@
|
|
|
2917
2917
|
{
|
|
2918
2918
|
"path": "modules/government/db.js",
|
|
2919
2919
|
"mode": "0000644",
|
|
2920
|
-
"sha256": "
|
|
2920
|
+
"sha256": "ab75e3e0fd2b7eee747e525403f8a2ea863dbf1f4332cb3c6ec1b413ed7af7e3"
|
|
2921
2921
|
},
|
|
2922
2922
|
{
|
|
2923
2923
|
"path": "modules/government/migrations/governance_001_tables.sql",
|
|
@@ -3027,7 +3027,7 @@
|
|
|
3027
3027
|
{
|
|
3028
3028
|
"path": "modules/government/routes/government.js",
|
|
3029
3029
|
"mode": "0000644",
|
|
3030
|
-
"sha256": "
|
|
3030
|
+
"sha256": "bf07f55f73d27d738840f4f0e0ffcb8a6d19225e7500dbb36e4a83a1109635db"
|
|
3031
3031
|
},
|
|
3032
3032
|
{
|
|
3033
3033
|
"path": "modules/government/routes/rank-sync.js",
|
|
@@ -3272,12 +3272,12 @@
|
|
|
3272
3272
|
{
|
|
3273
3273
|
"path": "modules/hall-ui/public/government.html",
|
|
3274
3274
|
"mode": "0000644",
|
|
3275
|
-
"sha256": "
|
|
3275
|
+
"sha256": "87ec2d8c002edf0a440e658cb29e567a07544bc36bb535d4cb0fd5130629a92e"
|
|
3276
3276
|
},
|
|
3277
3277
|
{
|
|
3278
3278
|
"path": "modules/hall-ui/public/government.js",
|
|
3279
3279
|
"mode": "0000644",
|
|
3280
|
-
"sha256": "
|
|
3280
|
+
"sha256": "505003cd76eb3760459ed53555b5c0803e1efaffb2e17ee15c7ed032a1607289"
|
|
3281
3281
|
},
|
|
3282
3282
|
{
|
|
3283
3283
|
"path": "modules/hall-ui/public/hall-kit.js",
|
|
@@ -4322,12 +4322,12 @@
|
|
|
4322
4322
|
{
|
|
4323
4323
|
"path": "package-lock.json",
|
|
4324
4324
|
"mode": "0000644",
|
|
4325
|
-
"sha256": "
|
|
4325
|
+
"sha256": "7731f20b3114fe6a083c06fe6b92d0048e4d5d136ba4fcc49f9a10f66c9577e5"
|
|
4326
4326
|
},
|
|
4327
4327
|
{
|
|
4328
4328
|
"path": "package.json",
|
|
4329
4329
|
"mode": "0000644",
|
|
4330
|
-
"sha256": "
|
|
4330
|
+
"sha256": "844c9c21b09d3d41cd8663a37d651ec717a5ba25d5fb24fda5e57930ca278cb2"
|
|
4331
4331
|
},
|
|
4332
4332
|
{
|
|
4333
4333
|
"path": "public-docs/index.html",
|
|
@@ -5807,7 +5807,7 @@
|
|
|
5807
5807
|
{
|
|
5808
5808
|
"path": "src/bongos/route-rank-check.js",
|
|
5809
5809
|
"mode": "0000644",
|
|
5810
|
-
"sha256": "
|
|
5810
|
+
"sha256": "93b2a09220f4d96f69294531cbca274b1132d164cf32be2f66d40d30e1953551"
|
|
5811
5811
|
},
|
|
5812
5812
|
{
|
|
5813
5813
|
"path": "src/bongos/routes.js",
|
|
@@ -5942,7 +5942,7 @@
|
|
|
5942
5942
|
{
|
|
5943
5943
|
"path": "src/module-api.js",
|
|
5944
5944
|
"mode": "0000644",
|
|
5945
|
-
"sha256": "
|
|
5945
|
+
"sha256": "c3a588bdcb0781e8f71dd492f26b09a2212ababebf52e511dccb9018ad84f89b"
|
|
5946
5946
|
},
|
|
5947
5947
|
{
|
|
5948
5948
|
"path": "src/module-loader/catalog.js",
|
|
@@ -6904,6 +6904,11 @@
|
|
|
6904
6904
|
"mode": "0000644",
|
|
6905
6905
|
"sha256": "74cfbd482b689df40348af3bdaf050f056e216ff18fdd6add4136d4589b7cd92"
|
|
6906
6906
|
},
|
|
6907
|
+
{
|
|
6908
|
+
"path": "tests/government_charter_library.mjs",
|
|
6909
|
+
"mode": "0000644",
|
|
6910
|
+
"sha256": "9a52ac8fbbc9e7b0fc4b45841b9dbf831615e5d64eb1e3f0d9c446fc70552629"
|
|
6911
|
+
},
|
|
6907
6912
|
{
|
|
6908
6913
|
"path": "tests/government_claim_eligibility.mjs",
|
|
6909
6914
|
"mode": "0000644",
|
|
@@ -6914,6 +6919,11 @@
|
|
|
6914
6919
|
"mode": "0000644",
|
|
6915
6920
|
"sha256": "330372412c80d8d33698eba68a5c19e79daff17b0d820a9a797c883071a5641f"
|
|
6916
6921
|
},
|
|
6922
|
+
{
|
|
6923
|
+
"path": "tests/government_constitution_view.mjs",
|
|
6924
|
+
"mode": "0000644",
|
|
6925
|
+
"sha256": "64fc8006d5b3a562b01a38bfd0ad00a1357969b0ac52ce270e77d4d93eddca80"
|
|
6926
|
+
},
|
|
6917
6927
|
{
|
|
6918
6928
|
"path": "tests/government_fold.mjs",
|
|
6919
6929
|
"mode": "0000644",
|
|
@@ -6972,7 +6982,7 @@
|
|
|
6972
6982
|
{
|
|
6973
6983
|
"path": "tests/government_routes.mjs",
|
|
6974
6984
|
"mode": "0000644",
|
|
6975
|
-
"sha256": "
|
|
6985
|
+
"sha256": "88f1c323de51a9b9f710bd4ad8cbf86da75b226d7b99db2bc2054e065b81a70a"
|
|
6976
6986
|
},
|
|
6977
6987
|
{
|
|
6978
6988
|
"path": "tests/government_seed.mjs",
|
|
@@ -5,7 +5,7 @@ A **generated**, zero-dependency typed client for the Bongos API — produced fr
|
|
|
5
5
|
by hand; it regenerates when the spec changes, so it can never drift from the routes.
|
|
6
6
|
|
|
7
7
|
- API version: **v1** (served at `/api/bongos/v1`)
|
|
8
|
-
-
|
|
8
|
+
- 285 operations across 46 resource groups
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
@@ -355,6 +355,8 @@ function createClient(opts = {}) {
|
|
|
355
355
|
postGovernmentBoardItems: (args) => request("POST", "/government/board/items", { hasBody: true }, args),
|
|
356
356
|
// POST /government/board/items/{itemId}/votes — rank: any-builder — POST /government/board/items/:itemId/votes
|
|
357
357
|
postGovernmentBoardItemsItemIdVotes: (args) => request("POST", "/government/board/items/{itemId}/votes", { hasBody: true }, args),
|
|
358
|
+
// GET /government/constitution — rank: any-builder — GET /government/constitution
|
|
359
|
+
getGovernmentConstitution: (args) => request("GET", "/government/constitution", { hasBody: false }, args),
|
|
358
360
|
// GET /government/permissions — rank: archon — GET /government/permissions
|
|
359
361
|
getGovernmentPermissions: (args) => request("GET", "/government/permissions", { hasBody: false }, args),
|
|
360
362
|
// GET /government/ranks — rank: archon — GET /government/ranks
|
|
@@ -354,6 +354,8 @@ function createClient(opts = {}) {
|
|
|
354
354
|
postGovernmentBoardItems: (args) => request("POST", "/government/board/items", { hasBody: true }, args),
|
|
355
355
|
// POST /government/board/items/{itemId}/votes — rank: any-builder — POST /government/board/items/:itemId/votes
|
|
356
356
|
postGovernmentBoardItemsItemIdVotes: (args) => request("POST", "/government/board/items/{itemId}/votes", { hasBody: true }, args),
|
|
357
|
+
// GET /government/constitution — rank: any-builder — GET /government/constitution
|
|
358
|
+
getGovernmentConstitution: (args) => request("GET", "/government/constitution", { hasBody: false }, args),
|
|
357
359
|
// GET /government/permissions — rank: archon — GET /government/permissions
|
|
358
360
|
getGovernmentPermissions: (args) => request("GET", "/government/permissions", { hasBody: false }, args),
|
|
359
361
|
// GET /government/ranks — rank: archon — GET /government/ranks
|
|
@@ -609,6 +609,8 @@ export interface BongosClient {
|
|
|
609
609
|
postGovernmentBoardItems(args: RequestArgs & { body: PostGovernmentBoardItemsRequest }): Promise<PostGovernmentBoardItemsResponse>;
|
|
610
610
|
/** POST /government/board/items/{itemId}/votes — rank: any-builder */
|
|
611
611
|
postGovernmentBoardItemsItemIdVotes(args: RequestArgs & { body: PostGovernmentBoardItemsItemIdVotesRequest }): Promise<ApiResponse>;
|
|
612
|
+
/** GET /government/constitution — rank: any-builder */
|
|
613
|
+
getGovernmentConstitution(args?: RequestArgs): Promise<ApiResponse>;
|
|
612
614
|
/** GET /government/permissions — rank: archon */
|
|
613
615
|
getGovernmentPermissions(args?: RequestArgs): Promise<GetGovernmentPermissionsResponse>;
|
|
614
616
|
/** GET /government/ranks — rank: archon */
|
|
@@ -351,6 +351,8 @@ export function createClient(opts = {}) {
|
|
|
351
351
|
postGovernmentBoardItems: (args) => request("POST", "/government/board/items", { hasBody: true }, args),
|
|
352
352
|
// POST /government/board/items/{itemId}/votes — rank: any-builder — POST /government/board/items/:itemId/votes
|
|
353
353
|
postGovernmentBoardItemsItemIdVotes: (args) => request("POST", "/government/board/items/{itemId}/votes", { hasBody: true }, args),
|
|
354
|
+
// GET /government/constitution — rank: any-builder — GET /government/constitution
|
|
355
|
+
getGovernmentConstitution: (args) => request("GET", "/government/constitution", { hasBody: false }, args),
|
|
354
356
|
// GET /government/permissions — rank: archon — GET /government/permissions
|
|
355
357
|
getGovernmentPermissions: (args) => request("GET", "/government/permissions", { hasBody: false }, args),
|
|
356
358
|
// GET /government/ranks — rank: archon — GET /government/ranks
|
package/docs/api/openapi.json
CHANGED
|
@@ -5479,6 +5479,40 @@
|
|
|
5479
5479
|
]
|
|
5480
5480
|
}
|
|
5481
5481
|
},
|
|
5482
|
+
"/government/constitution": {
|
|
5483
|
+
"get": {
|
|
5484
|
+
"operationId": "get_government_constitution",
|
|
5485
|
+
"tags": [
|
|
5486
|
+
"government"
|
|
5487
|
+
],
|
|
5488
|
+
"summary": "GET /government/constitution",
|
|
5489
|
+
"description": "board.vote.cast — the Constitution view (R18 of goal 1000069): the live board block plus the full amendment history, passed and returned alike. Same atom as the room — reading the rule you vote under is part of casting, and the section teaches (criterion C5). The live value resolves fresh per request, so an amendment applied a moment ago reads back immediately (the same instant-effect posture as everything else here).\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).\n\n**Permissions:** `board.vote.cast` (all required).",
|
|
5490
|
+
"x-rank": "any-builder",
|
|
5491
|
+
"x-source": "modules/government/routes/government.js",
|
|
5492
|
+
"x-permissions": [
|
|
5493
|
+
"board.vote.cast"
|
|
5494
|
+
],
|
|
5495
|
+
"responses": {
|
|
5496
|
+
"200": {
|
|
5497
|
+
"description": "Success."
|
|
5498
|
+
},
|
|
5499
|
+
"400": {
|
|
5500
|
+
"$ref": "#/components/responses/BadRequest"
|
|
5501
|
+
},
|
|
5502
|
+
"401": {
|
|
5503
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
5504
|
+
},
|
|
5505
|
+
"403": {
|
|
5506
|
+
"$ref": "#/components/responses/Forbidden"
|
|
5507
|
+
}
|
|
5508
|
+
},
|
|
5509
|
+
"security": [
|
|
5510
|
+
{
|
|
5511
|
+
"builderSession": []
|
|
5512
|
+
}
|
|
5513
|
+
]
|
|
5514
|
+
}
|
|
5515
|
+
},
|
|
5482
5516
|
"/government/permissions": {
|
|
5483
5517
|
"get": {
|
|
5484
5518
|
"operationId": "get_government_permissions",
|
|
@@ -18001,7 +18035,7 @@
|
|
|
18001
18035
|
"description": "A required dependency/feature is not configured or is temporarily down."
|
|
18002
18036
|
}
|
|
18003
18037
|
},
|
|
18004
|
-
"x-endpoint-count":
|
|
18038
|
+
"x-endpoint-count": 285,
|
|
18005
18039
|
"x-schema-count": 321,
|
|
18006
18040
|
"x-undocumented-bodies": 8,
|
|
18007
18041
|
"x-response-schemas": 215,
|
package/docs/api-reference.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Bongos API reference
|
|
4
4
|
|
|
5
|
-
> **Generated from the live route files** — the route file is authoritative.
|
|
5
|
+
> **Generated from the live route files** — the route file is authoritative. 285 endpoints across 46 route files.
|
|
6
6
|
> Machine-readable spec: [`docs/api/openapi.json`](api/openapi.json) (OpenAPI 3.1). Rendered docs site: **`/docs`** (e.g. `cloudbongos.com/docs`).
|
|
7
7
|
|
|
8
8
|
Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>.md)): `public` < `any-builder` < `metic+archon` < `archon`.
|
|
@@ -216,7 +216,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
216
216
|
| GET | `/api/bongos/goals/inbox` | `any-builder` | — | GET /goals/inbox — the caller's private-goals inbox (task 1952 / ADR 0112): { invitations: invites addressed to me (accept/decline); join… |
|
|
217
217
|
| POST | `/api/bongos/goals/reorder` | `metic+archon` | `version_id`, `ordered_ids` | POST /goals/reorder — Archon drag-to-reorder: set the priority order of every goal WITHIN one version (task 1760). |
|
|
218
218
|
|
|
219
|
-
## `government` (
|
|
219
|
+
## `government` (13)
|
|
220
220
|
|
|
221
221
|
| Method | Path | Rank | Body | Description |
|
|
222
222
|
|---|---|---|---|---|
|
|
@@ -226,6 +226,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
226
226
|
| 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). |
|
|
227
227
|
| 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). |
|
|
228
228
|
| 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). |
|
|
229
|
+
| 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 … |
|
|
229
230
|
| 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. |
|
|
230
231
|
| GET | `/api/bongos/government/ranks` | `archon` | — | government.manage — the full rank list with each rank's grants. |
|
|
231
232
|
| POST | `/api/bongos/government/ranks` | `archon` | `rank_key`, `label`, `description` | government.manage — create a CUSTOM rank (is_system false, no seeded_rank — the seeded four exist only via governance_002). |
|
|
@@ -520,6 +520,56 @@ async function boardRoomView({ closedLimit = 20 } = {}) {
|
|
|
520
520
|
return { open: open.map(project), closed: closed.map(project) };
|
|
521
521
|
}
|
|
522
522
|
|
|
523
|
+
// ── the Constitution view (BV1.R18 of goal 1000069, criterion C3/C5) ────────
|
|
524
|
+
//
|
|
525
|
+
// What this project RUNS — the live, sanitized board block, resolved fresh per
|
|
526
|
+
// request so an amendment applied a moment ago reads back immediately — plus
|
|
527
|
+
// the full change history: every closed amendment sitting, passed and
|
|
528
|
+
// RETURNED alike (a failed attempt to change the rules is part of the record;
|
|
529
|
+
// that is what "a switch can never be flipped quietly" means). Each history
|
|
530
|
+
// row carries the rule it was DECIDED UNDER (the item's snapshot), so the
|
|
531
|
+
// reader can verify every change was made legally by the constitution of its
|
|
532
|
+
// day — the §7 chain, auditable end to end.
|
|
533
|
+
// ── the charter ladder's recommendation (BV1.R19, criterion C5) ─────────────
|
|
534
|
+
//
|
|
535
|
+
// The developmental ladder from the Six Variables synthesis (Part III), as the
|
|
536
|
+
// spec's task text records it: formality is adopted only where informal
|
|
537
|
+
// coordination breaks. PURE and server-side so R26's proof can hold the
|
|
538
|
+
// recommendation against the real headcount — the library's one live datum.
|
|
539
|
+
// The bands deliberately track only the thresholds the synthesis NAMES
|
|
540
|
+
// (~9 name an executive, ~50 written charter + council, ~150 adjudication
|
|
541
|
+
// separates / federate beyond); everything else is reference prose, not code.
|
|
542
|
+
function recommendedCharter(headcount) {
|
|
543
|
+
const n = Number(headcount) || 0;
|
|
544
|
+
if (n <= 2) return { key: 'monarchy', rung: 'solo/pair', why: 'informal coordination has nothing to break yet — the founding monarchy is the honest shape' };
|
|
545
|
+
if (n <= 8) return { key: 'direct-democracy', rung: 'circle (3–8)', why: 'a consent circle of all trusted members — the rung this ladder was written on' };
|
|
546
|
+
if (n <= 49) return { key: 'presidential', rung: '~9: name an executive', why: 'past ~9, naming an executive beats everyone coordinating with everyone' };
|
|
547
|
+
if (n <= 149) return { key: 'foundation', rung: '~50: written charter + council', why: 'past ~50, the charter must be written down and a council carries it' };
|
|
548
|
+
return { key: 'federation', rung: '~150: adjudication separates — federate beyond', why: 'past ~150, one body cannot both govern and adjudicate; federate' };
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
async function constitutionView({ historyLimit = 50 } = {}) {
|
|
552
|
+
const { board: live } = loadGovernmentConfig();
|
|
553
|
+
const [rows, headcount] = await Promise.all([
|
|
554
|
+
db.listAmendmentHistory(historyLimit),
|
|
555
|
+
db.countActiveBuilders(),
|
|
556
|
+
]);
|
|
557
|
+
const history = rows.map((r) => ({
|
|
558
|
+
item_id: String(r.item_id),
|
|
559
|
+
amendment_id: String(r.amendment_id),
|
|
560
|
+
proposed: r.proposed,
|
|
561
|
+
rationale_md: r.rationale_md,
|
|
562
|
+
proposed_by: r.proposed_by == null ? null : String(r.proposed_by),
|
|
563
|
+
proposer_login: r.proposer_login || null,
|
|
564
|
+
proposer_name: r.proposer_name || null,
|
|
565
|
+
opened_at: r.opened_at,
|
|
566
|
+
closed_at: r.closed_at,
|
|
567
|
+
outcome: r.outcome,
|
|
568
|
+
decided_under: r.decided_under || null,
|
|
569
|
+
}));
|
|
570
|
+
return { board: live, history, headcount, recommended: recommendedCharter(headcount) };
|
|
571
|
+
}
|
|
572
|
+
|
|
523
573
|
// ── R13's read: the feedback a returned idea carries back to its author ─────
|
|
524
574
|
//
|
|
525
575
|
// One reasoned objection is what returns an idea (§3), so the feedback IS that
|
|
@@ -561,4 +611,6 @@ module.exports = {
|
|
|
561
611
|
validateProposedConstitution,
|
|
562
612
|
proposeAmendment,
|
|
563
613
|
boardRoomView,
|
|
614
|
+
constitutionView,
|
|
615
|
+
recommendedCharter,
|
|
564
616
|
};
|
package/modules/government/db.js
CHANGED
|
@@ -509,6 +509,39 @@ async function getAmendmentSubjects(amendmentIds) {
|
|
|
509
509
|
return rows;
|
|
510
510
|
}
|
|
511
511
|
|
|
512
|
+
// The live headcount (R19): active humans, the same two filters as board
|
|
513
|
+
// membership — a machine principal is not a team member, and neither is a
|
|
514
|
+
// departed builder. Feeds the charter library's "recommended at YOUR headcount"
|
|
515
|
+
// line, which R26's proof holds against this exact number.
|
|
516
|
+
async function countActiveBuilders() {
|
|
517
|
+
const { rows } = await pool.query(
|
|
518
|
+
`SELECT count(*)::int AS n FROM builders WHERE status = 'active' AND system_role IS NULL`,
|
|
519
|
+
);
|
|
520
|
+
return rows[0] ? rows[0].n : 0;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// The constitution's change HISTORY (R18): every CLOSED amendment sitting —
|
|
524
|
+
// passed AND returned — joined to what was proposed, newest first. Returned
|
|
525
|
+
// proposals are deliberately part of the record: "a switch can never be flipped
|
|
526
|
+
// quietly" cuts both ways, and a failed attempt to change the rules is exactly
|
|
527
|
+
// the kind of fact an open ballot exists to keep visible. Served by
|
|
528
|
+
// government_008's closed-items index; bounded.
|
|
529
|
+
async function listAmendmentHistory(limit = 50) {
|
|
530
|
+
const { rows } = await pool.query(
|
|
531
|
+
`SELECT i.id AS item_id, i.opened_at, i.closed_at, i.outcome, i.constitution AS decided_under,
|
|
532
|
+
a.id AS amendment_id, a.proposed, a.rationale_md, a.proposed_by,
|
|
533
|
+
b.github_login AS proposer_login, b.display_name AS proposer_name
|
|
534
|
+
FROM government_board_items i
|
|
535
|
+
JOIN government_amendments a ON a.id = i.subject_id
|
|
536
|
+
LEFT JOIN builders b ON b.id = a.proposed_by
|
|
537
|
+
WHERE i.subject_type = 'constitutional_amendment' AND i.closed_at IS NOT NULL
|
|
538
|
+
ORDER BY i.closed_at DESC
|
|
539
|
+
LIMIT $1`,
|
|
540
|
+
[limit],
|
|
541
|
+
);
|
|
542
|
+
return rows;
|
|
543
|
+
}
|
|
544
|
+
|
|
512
545
|
// --- amendments (BV1.R14 of goal 1000069) ------------------------------------
|
|
513
546
|
|
|
514
547
|
async function createAmendment({ proposed, rationaleMd = null, proposedBy }) {
|
|
@@ -630,4 +663,6 @@ module.exports = {
|
|
|
630
663
|
getVotesForItems,
|
|
631
664
|
getIdeaSubjects,
|
|
632
665
|
getAmendmentSubjects,
|
|
666
|
+
listAmendmentHistory,
|
|
667
|
+
countActiveBuilders,
|
|
633
668
|
};
|
|
@@ -432,6 +432,21 @@ module.exports = function buildGovernmentRouter() {
|
|
|
432
432
|
}
|
|
433
433
|
});
|
|
434
434
|
|
|
435
|
+
// rank: perm board.vote.cast — the Constitution view (R18 of goal 1000069):
|
|
436
|
+
// the live board block plus the full amendment history, passed and returned
|
|
437
|
+
// alike. Same atom as the room — reading the rule you vote under is part of
|
|
438
|
+
// casting, and the section teaches (criterion C5). The live value resolves
|
|
439
|
+
// fresh per request, so an amendment applied a moment ago reads back
|
|
440
|
+
// immediately (the same instant-effect posture as everything else here).
|
|
441
|
+
router.get('/government/constitution', api.requireBuilder, api.requirePermission('board.vote.cast'), async (req, res) => {
|
|
442
|
+
try {
|
|
443
|
+
res.json(await board.constitutionView({ historyLimit: 50 }));
|
|
444
|
+
} catch (err) {
|
|
445
|
+
console.error('[gds] GET /government/constitution', err);
|
|
446
|
+
res.fail('constitution_read_failed', { status: 500, message: 'internal error' });
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
435
450
|
// rank: perm board.item.open — put an AMENDMENT to the board (R14 of goal
|
|
436
451
|
// 1000069, ADR 0175 §7). The board's other subject never comes through here:
|
|
437
452
|
// a Full Idea's window opens AUTOMATICALLY on grade pass (R07), so requesting
|
|
@@ -99,6 +99,6 @@
|
|
|
99
99
|
<script src="/builders/palette.js" defer></script>
|
|
100
100
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
101
101
|
<script src="/builders/hall-kit.js"></script>
|
|
102
|
-
<script src="/builders/government.js?v=2026-08-18-
|
|
102
|
+
<script src="/builders/government.js?v=2026-08-18-charter-library"></script>
|
|
103
103
|
</body>
|
|
104
104
|
</html>
|
|
@@ -402,25 +402,158 @@
|
|
|
402
402
|
// ---- the three-section IA (R15) -------------------------------------------
|
|
403
403
|
|
|
404
404
|
// Tab order is the spec's — Constitution · Board Room · Ranks & Permissions.
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
//
|
|
405
|
+
// A non-archon lands on the Constitution (the page's IA claim: this is the
|
|
406
|
+
// page that answers "how is this project governed"); the Archon still lands
|
|
407
|
+
// on Ranks, their working surface.
|
|
408
408
|
function wireTabs({ isArchon }) {
|
|
409
409
|
window.OTBKit.makeTabs({
|
|
410
410
|
mount: document.getElementById('gov-tabs'),
|
|
411
411
|
hash: true,
|
|
412
|
-
initial: isArchon ? 'ranks' : '
|
|
412
|
+
initial: isArchon ? 'ranks' : 'constitution',
|
|
413
413
|
tabs: [
|
|
414
414
|
{ id: 'constitution', label: 'Constitution', panel: 'gov-panel-constitution' },
|
|
415
415
|
{ id: 'board-room', label: 'Board Room', panel: 'gov-panel-board' },
|
|
416
416
|
{ id: 'ranks', label: 'Ranks & Permissions', panel: 'gov-panel-ranks' },
|
|
417
417
|
],
|
|
418
418
|
});
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ---- the Constitution panel (R18) -------------------------------------------
|
|
422
|
+
|
|
423
|
+
// Plain language over the three dials, straight from the SERVER's resolved
|
|
424
|
+
// value (never hardcoded — an amendment applied a moment ago reads back on
|
|
425
|
+
// the next load), plus the change history: every closed amendment sitting,
|
|
426
|
+
// passed and RETURNED alike, each with the rule it was decided under — so a
|
|
427
|
+
// switch can never be flipped quietly, and every change is auditable against
|
|
428
|
+
// the constitution of its day.
|
|
429
|
+
|
|
430
|
+
function membershipSentence(board) {
|
|
431
|
+
const m = String(board.membership || '');
|
|
432
|
+
const rank = m.startsWith('rank:') ? m.slice(5) : null;
|
|
433
|
+
return rank
|
|
434
|
+
? `Every active builder holding the <strong>${escapeHtml(rank)}</strong> rank sits on the board — membership follows the rank, live.`
|
|
435
|
+
: `Membership predicate: <code>${escapeHtml(m || 'unset')}</code>.`;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function passRuleSentence(board) {
|
|
439
|
+
if (board.pass_rule === 'first_ratifier') {
|
|
440
|
+
return 'An item is decided by its <strong>first vote</strong> — yes ratifies, no returns. The day-one shape: one member acts, done.';
|
|
441
|
+
}
|
|
442
|
+
if (board.pass_rule === 'consent') {
|
|
443
|
+
return 'Items pass by <strong>consent</strong>: silence is consent, a single reasoned objection returns the item to its author, and a sitting that ends with no votes at all passes.';
|
|
444
|
+
}
|
|
445
|
+
return `Pass rule: <code>${escapeHtml(String(board.pass_rule || 'unset'))}</code>.`;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function windowSentence(board) {
|
|
449
|
+
if (board.window_minutes === null || board.window_minutes === undefined) {
|
|
450
|
+
return 'There is <strong>no clock</strong> — an item stays open until a vote decides it.';
|
|
451
|
+
}
|
|
452
|
+
const early = board.close_early_on_full_turnout
|
|
453
|
+
? ', closing early the moment every member has voted'
|
|
454
|
+
: '';
|
|
455
|
+
return `A sitting runs <strong>${escapeHtml(String(board.window_minutes))} minutes</strong>${early}. An unvoted window nulls that member's vote — absence never blocks.`;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function historyRow(h) {
|
|
459
|
+
const who = escapeHtml(h.proposer_name || h.proposer_login || (h.proposed_by ? `builder ${h.proposed_by}` : 'unknown'));
|
|
460
|
+
const when = escapeHtml(String(h.closed_at).slice(0, 16).replace('T', ' '));
|
|
461
|
+
const pill = h.outcome === 'passed'
|
|
462
|
+
? '<span class="gov-pill gov-pill--seeded">ratified</span>'
|
|
463
|
+
: '<span class="gov-pill gov-pill--locked">returned</span>';
|
|
464
|
+
const under = h.decided_under && h.decided_under.pass_rule
|
|
465
|
+
? ` · decided under <code>${escapeHtml(h.decided_under.pass_rule)}</code>`
|
|
466
|
+
: '';
|
|
467
|
+
return `<div class="gov-assign-row">
|
|
468
|
+
<span class="gov-assign-row__who">${who} proposed <code>${escapeHtml(JSON.stringify(h.proposed))}</code>${h.rationale_md ? ` — ${escapeHtml(h.rationale_md)}` : ''}</span>
|
|
469
|
+
${pill} <span class="muted">${when}${under}</span>
|
|
470
|
+
</div>`;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// ---- the charter library (R19) ---------------------------------------------
|
|
474
|
+
|
|
475
|
+
// The REFERENCE half of the Charter Builder — the seven structures, the three
|
|
476
|
+
// trajectories, the ladder — and deliberately not the configuration machinery
|
|
477
|
+
// (the scale argument in ADR 0175: formality is adopted only where informal
|
|
478
|
+
// coordination breaks). `selectable` means EXPRESSIBLE TODAY through the real
|
|
479
|
+
// config — a rank-predicate membership plus first_ratifier|consent — so nobody
|
|
480
|
+
// is misled about what a settings change can and cannot reach. Source: the
|
|
481
|
+
// Six-Variables synthesis (Verro Labs, Aug 2026), Parts III–IV, via the spec.
|
|
482
|
+
const CHARTERS = [
|
|
483
|
+
{ key: 'monarchy', name: 'Monarchy / BDFL', selectable: true,
|
|
484
|
+
how: "membership rank:archon · first ratifier · no clock — the shipped default",
|
|
485
|
+
blurb: 'One trusted executive decides. Fast, legible, and honest at founding scale; the shape every project actually starts in.' },
|
|
486
|
+
{ key: 'timocratic-republic', name: 'Timocratic Republic', selectable: true,
|
|
487
|
+
how: 'membership by a trusted rank (e.g. rank:metic) · consent · a timed sitting',
|
|
488
|
+
blurb: 'Standing accrues to those actively building: the franchise follows earned rank, not headcount.' },
|
|
489
|
+
{ key: 'direct-democracy', name: 'Direct Democracy', selectable: true,
|
|
490
|
+
how: 'membership rank:xenos (everyone) · consent · a timed sitting',
|
|
491
|
+
blurb: 'Every builder sits on the board. Consent keeps it unblockable: silence passes, one reasoned objection returns.' },
|
|
492
|
+
{ key: 'presidential', name: 'Presidential', selectable: false,
|
|
493
|
+
blurb: 'An ELECTED executive with a fixed term. Needs election machinery this system does not have yet — documented for later.' },
|
|
494
|
+
{ key: 'cooperative', name: 'Cooperative', selectable: false,
|
|
495
|
+
blurb: 'Formal one-member-one-vote with membership shares and exit rules. Needs the membership-share machinery — documented for later.' },
|
|
496
|
+
{ key: 'federation', name: 'Federation', selectable: false,
|
|
497
|
+
blurb: 'Multiple chambers or sub-projects, each self-governing, coordinating through a compact. The >150 shape — documented for later.' },
|
|
498
|
+
{ key: 'foundation', name: 'Foundation', selectable: true,
|
|
499
|
+
how: 'a CUSTOM rank as the trustee body (the Ranks tab creates it) · consent · a timed sitting',
|
|
500
|
+
blurb: 'A written charter carried by a small appointed council. Expressible today by governing through a custom rank.' },
|
|
501
|
+
];
|
|
502
|
+
const TRAJECTORIES = [
|
|
503
|
+
{ name: 'Locked', blurb: 'The constitution stays as founded until deliberately amended — no automatic movement.' },
|
|
504
|
+
{ name: 'Laddered', blurb: 'Steps up the developmental ladder as the team crosses each headcount threshold.' },
|
|
505
|
+
{ name: 'Scheduled', blurb: 'Pre-commits the dates or milestones at which each step happens, decided in advance.' },
|
|
506
|
+
];
|
|
507
|
+
const LADDER = 'solo/pair → circle (3–8) → ~9 name an executive → ~25 → ~50 written charter + council → ~150 adjudication separates → beyond: federate';
|
|
508
|
+
|
|
509
|
+
function charterCard(c, recommendedKey) {
|
|
510
|
+
const pill = c.selectable
|
|
511
|
+
? '<span class="gov-pill gov-pill--seeded">selectable today</span>'
|
|
512
|
+
: '<span class="gov-pill gov-pill--locked">documented for later</span>';
|
|
513
|
+
const rec = c.key === recommendedKey ? ' <span class="gov-pill gov-pill--custom">recommended at your headcount</span>' : '';
|
|
514
|
+
const how = c.how ? `<div class="gov-perm__guards">as configuration: ${escapeHtml(c.how)}</div>` : '';
|
|
515
|
+
return `<div class="gov-assign-row"><span class="gov-assign-row__who"><strong>${escapeHtml(c.name)}</strong> — ${escapeHtml(c.blurb)}${how}</span>${pill}${rec}</div>`;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function charterLibraryHtml(data) {
|
|
519
|
+
const rec = data.recommended || {};
|
|
520
|
+
return `
|
|
521
|
+
<h2 class="gov-h">Charter library</h2>
|
|
522
|
+
<p class="gov-grid-note">The seven government structures, for getting a team up to speed before deciding when to flip a switch. <strong>Selectable today</strong> means expressible through the real configuration — a rank-based membership plus a pass rule — with no new code. This project has <strong>${escapeHtml(String(data.headcount ?? '?'))}</strong> active builder${data.headcount === 1 ? '' : 's'}: ${escapeHtml(rec.why || '')} <em>(${escapeHtml(rec.rung || '')})</em>.</p>
|
|
523
|
+
<div class="gov-section">${CHARTERS.map((c) => charterCard(c, rec.key)).join('')}</div>
|
|
524
|
+
<h2 class="gov-h">Trajectories</h2>
|
|
525
|
+
<p class="gov-grid-note">How a project moves between structures over time — summarized from the synthesis:</p>
|
|
526
|
+
<div class="gov-section">${TRAJECTORIES.map((t) => `<div class="gov-assign-row"><span class="gov-assign-row__who"><strong>${escapeHtml(t.name)}</strong> — ${escapeHtml(t.blurb)}</span></div>`).join('')}</div>
|
|
527
|
+
<p class="gov-grid-note muted">The developmental ladder: ${escapeHtml(LADDER)}. Formality is adopted only where informal coordination breaks.</p>
|
|
528
|
+
`;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
async function loadConstitution() {
|
|
532
|
+
const box = document.getElementById('gov-constitution');
|
|
533
|
+
let data;
|
|
534
|
+
try {
|
|
535
|
+
data = await fetchJson('/government/constitution');
|
|
536
|
+
} catch (err) {
|
|
537
|
+
if (err && err.kind === '401') return;
|
|
538
|
+
box.innerHTML = `<p class="gov-empty">Could not load the constitution just now.</p>`;
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const b = data.board || {};
|
|
542
|
+
const history = data.history || [];
|
|
543
|
+
box.innerHTML = `
|
|
544
|
+
<section class="gov-section">
|
|
545
|
+
<div class="gov-section__head">What this project runs today</div>
|
|
546
|
+
<p class="gov-grid-note">${membershipSentence(b)}</p>
|
|
547
|
+
<p class="gov-grid-note">${passRuleSentence(b)}</p>
|
|
548
|
+
<p class="gov-grid-note">${windowSentence(b)}</p>
|
|
549
|
+
<p class="gov-grid-note muted">As configuration: <code>${escapeHtml(JSON.stringify(b))}</code>. Changing it is a board act — propose an amendment, and it is decided under the rule above.</p>
|
|
550
|
+
</section>
|
|
551
|
+
<h2 class="gov-h">How it has changed</h2>
|
|
552
|
+
${history.length
|
|
553
|
+
? `<div class="gov-section">${history.map(historyRow).join('')}</div>`
|
|
554
|
+
: `<p class="gov-empty">Never amended — this project still runs its founding constitution.</p>`}
|
|
555
|
+
${charterLibraryHtml(data)}
|
|
556
|
+
`;
|
|
424
557
|
}
|
|
425
558
|
|
|
426
559
|
// ---- the Board Room panel (R16) --------------------------------------------
|
|
@@ -554,7 +687,8 @@
|
|
|
554
687
|
document.getElementById('gov-sub').textContent = 'How this project is governed — the constitution it runs, the Board Room, and the ranks & permissions that administer it.';
|
|
555
688
|
document.getElementById('gov-body').hidden = false;
|
|
556
689
|
wireTabs({ isArchon });
|
|
557
|
-
loadBoard();
|
|
690
|
+
loadBoard(); // the room loads for everyone the page admits
|
|
691
|
+
loadConstitution(); // so does the rule it runs under (R18)
|
|
558
692
|
|
|
559
693
|
if (!isArchon) { sealRanksPanel(); return; }
|
|
560
694
|
wireForms();
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.235",
|
|
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.235",
|
|
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.235",
|
|
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",
|
|
@@ -257,6 +257,9 @@ const GOVERNMENT_PINS = {
|
|
|
257
257
|
// R16: the Board Room view — the open ballot is visible to whoever may cast
|
|
258
258
|
// (xenos-floored atom; the room teaches as well as governs, criterion C5).
|
|
259
259
|
'GET /government/board/items': 'perm:board.vote.cast',
|
|
260
|
+
// R18: the Constitution view — the live rule + the amendment history; reading
|
|
261
|
+
// the rule you vote under is part of casting.
|
|
262
|
+
'GET /government/constitution': 'perm:board.vote.cast',
|
|
260
263
|
};
|
|
261
264
|
|
|
262
265
|
// Module-scoped privileged-rank pins: a privileged route that lives in a FEATURE
|
package/src/module-api.js
CHANGED
|
@@ -893,8 +893,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
893
893
|
// landed since 1.19.231 with no explicit bump. run 32133691409. (task 1002620)
|
|
894
894
|
// 1.19.233 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
895
895
|
// landed since 1.19.232 with no explicit bump. run 32160823445. (task 1002620)
|
|
896
|
+
// 1.19.234 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
897
|
+
// landed since 1.19.233 with no explicit bump. run 32161368847. (task 1002620)
|
|
898
|
+
// 1.19.235 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
899
|
+
// landed since 1.19.234 with no explicit bump. run 32161981393. (task 1002620)
|
|
896
900
|
// ---------------------------------------------------------------------------
|
|
897
|
-
const CORE_VERSION = '1.19.
|
|
901
|
+
const CORE_VERSION = '1.19.235'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
898
902
|
|
|
899
903
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
900
904
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// tests/government_charter_library.mjs — the charter library (BV1.R19 of goal
|
|
2
|
+
// 1000069, criterion C5): seven constitutions, three trajectories, the ladder —
|
|
3
|
+
// and ONE live datum, the recommendation at this project's actual headcount.
|
|
4
|
+
//
|
|
5
|
+
// The recommendation is SERVER-SIDE and pure precisely so R26's proof can hold
|
|
6
|
+
// it against the real number; the library prose is client reference content.
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
import { test } from 'node:test';
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
16
|
+
const GOV = path.join(ROOT, 'modules', 'government');
|
|
17
|
+
const board = require(path.join(GOV, 'board.js'));
|
|
18
|
+
const db = require(path.join(GOV, 'db.js'));
|
|
19
|
+
|
|
20
|
+
const { recommendedCharter } = board;
|
|
21
|
+
|
|
22
|
+
test('the ladder bands track exactly the thresholds the synthesis names', () => {
|
|
23
|
+
assert.equal(recommendedCharter(1).key, 'monarchy');
|
|
24
|
+
assert.equal(recommendedCharter(2).key, 'monarchy', 'this project today: two builders → the founding monarchy');
|
|
25
|
+
assert.equal(recommendedCharter(3).key, 'direct-democracy', 'the circle rung starts at 3');
|
|
26
|
+
assert.equal(recommendedCharter(8).key, 'direct-democracy');
|
|
27
|
+
assert.equal(recommendedCharter(9).key, 'presidential', '~9: name an executive');
|
|
28
|
+
assert.equal(recommendedCharter(49).key, 'presidential');
|
|
29
|
+
assert.equal(recommendedCharter(50).key, 'foundation', '~50: written charter + council');
|
|
30
|
+
assert.equal(recommendedCharter(149).key, 'foundation');
|
|
31
|
+
assert.equal(recommendedCharter(150).key, 'federation', '~150: adjudication separates — federate');
|
|
32
|
+
assert.equal(recommendedCharter(0).key, 'monarchy', 'a zero/unknown headcount fails to the founding shape');
|
|
33
|
+
assert.equal(recommendedCharter('nonsense').key, 'monarchy');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('constitutionView carries the live headcount and its recommendation', async () => {
|
|
37
|
+
const originals = { listAmendmentHistory: db.listAmendmentHistory, countActiveBuilders: db.countActiveBuilders };
|
|
38
|
+
db.listAmendmentHistory = async () => [];
|
|
39
|
+
db.countActiveBuilders = async () => 7;
|
|
40
|
+
try {
|
|
41
|
+
const view = await board.constitutionView();
|
|
42
|
+
assert.equal(view.headcount, 7);
|
|
43
|
+
assert.equal(view.recommended.key, 'direct-democracy');
|
|
44
|
+
assert.ok(view.recommended.rung.includes('circle'));
|
|
45
|
+
} finally { Object.assign(db, originals); }
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('the headcount counts the same population membership does (SQL pin)', () => {
|
|
49
|
+
const src = fs.readFileSync(path.join(GOV, 'db.js'), 'utf8');
|
|
50
|
+
const block = src.slice(src.indexOf('async function countActiveBuilders'), src.indexOf("// The constitution's change HISTORY"));
|
|
51
|
+
assert.match(block, /status = 'active'/);
|
|
52
|
+
assert.match(block, /system_role IS NULL/, 'a machine principal is not a team member');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('the library presents all seven structures with honest selectable marks (client source pins)', () => {
|
|
56
|
+
const client = fs.readFileSync(path.join(ROOT, 'modules', 'hall-ui', 'public', 'government.js'), 'utf8');
|
|
57
|
+
const lib = client.slice(client.indexOf('const CHARTERS'), client.indexOf('function charterCard'));
|
|
58
|
+
for (const name of ['Monarchy / BDFL', 'Timocratic Republic', 'Direct Democracy', 'Presidential', 'Cooperative', 'Federation', 'Foundation']) {
|
|
59
|
+
assert.ok(lib.includes(`name: '${name}'`), `the library must present ${name}`);
|
|
60
|
+
}
|
|
61
|
+
// Selectable ⇔ expressible through the REAL config (a rank predicate + a
|
|
62
|
+
// shipped pass rule). Every selectable entry must SAY HOW; every
|
|
63
|
+
// documented-for-later entry must not pretend.
|
|
64
|
+
const selectableTrue = (lib.match(/selectable: true/g) || []).length;
|
|
65
|
+
const selectableFalse = (lib.match(/selectable: false/g) || []).length;
|
|
66
|
+
assert.equal(selectableTrue + selectableFalse, 7, 'every structure is marked, none left ambiguous');
|
|
67
|
+
assert.equal(selectableTrue, 4, 'monarchy, timocratic republic, direct democracy, foundation — the config-expressible four');
|
|
68
|
+
const trueBlocks = lib.split('selectable: true').slice(1);
|
|
69
|
+
for (const b of trueBlocks) {
|
|
70
|
+
assert.ok(b.slice(0, 200).includes('how:'), 'a selectable structure names its configuration');
|
|
71
|
+
}
|
|
72
|
+
assert.match(client, /recommended at your headcount/, 'the one live datum is rendered');
|
|
73
|
+
assert.match(client, /c\.key === recommendedKey/, "the pill is driven by the SERVER's recommendation, never re-derived client-side");
|
|
74
|
+
for (const t of ['Locked', 'Laddered', 'Scheduled']) {
|
|
75
|
+
assert.ok(client.includes(`name: '${t}'`), `trajectory ${t} present`);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// tests/government_constitution_view.mjs — the Constitution section (BV1.R18 of
|
|
2
|
+
// goal 1000069, criteria C3/C5): what we run, and how it changed.
|
|
3
|
+
//
|
|
4
|
+
// DB-free (db monkeypatched). The invariants:
|
|
5
|
+
// 1. THE LIVE VALUE IS RESOLVED, NEVER HARDCODED — the view calls R05's
|
|
6
|
+
// config resolution per request, so an amendment applied a moment ago
|
|
7
|
+
// reads back immediately, and this vanilla checkout reads as monarchy.
|
|
8
|
+
// 2. THE HISTORY SHOWS RETURNED PROPOSALS TOO — a failed attempt to change
|
|
9
|
+
// the rules is part of the record ("a switch can never be flipped
|
|
10
|
+
// quietly" cuts both ways), each row carrying the rule it was DECIDED
|
|
11
|
+
// UNDER so every change is auditable against the constitution of its day.
|
|
12
|
+
// 3. THE READ RIDES THE VOTE ATOM — reading the rule you vote under is part
|
|
13
|
+
// of casting; never the manage atom.
|
|
14
|
+
import assert from 'node:assert/strict';
|
|
15
|
+
import { test } from 'node:test';
|
|
16
|
+
import { createRequire } from 'node:module';
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
23
|
+
const GOV = path.join(ROOT, 'modules', 'government');
|
|
24
|
+
const board = require(path.join(GOV, 'board.js'));
|
|
25
|
+
const db = require(path.join(GOV, 'db.js'));
|
|
26
|
+
const { BOARD_DEFAULTS } = require(path.join(GOV, 'config.js'));
|
|
27
|
+
|
|
28
|
+
async function withDb(stubs, fn) {
|
|
29
|
+
const originals = {};
|
|
30
|
+
for (const [k, v] of Object.entries(stubs)) { originals[k] = db[k]; db[k] = v; }
|
|
31
|
+
try { return await fn(); }
|
|
32
|
+
finally { for (const [k, v] of Object.entries(originals)) db[k] = v; }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test('the live board block is RESOLVED per request — this vanilla checkout reads monarchy', async () => {
|
|
36
|
+
const view = await withDb({ listAmendmentHistory: async () => [], countActiveBuilders: async () => 2 }, () => board.constitutionView());
|
|
37
|
+
assert.deepEqual(view.board, BOARD_DEFAULTS,
|
|
38
|
+
'no instance pack here, so the resolved value IS the neutral monarchy — never a hardcoded copy');
|
|
39
|
+
assert.deepEqual(view.history, []);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('the history projects passed AND returned, with proposer identity and the decided-under snapshot', async () => {
|
|
43
|
+
const MONARCHY = { membership: 'rank:archon', pass_rule: 'first_ratifier', window_minutes: null, close_early_on_full_turnout: true };
|
|
44
|
+
const view = await withDb({
|
|
45
|
+
countActiveBuilders: async () => 2,
|
|
46
|
+
listAmendmentHistory: async (limit) => {
|
|
47
|
+
assert.equal(limit, 50);
|
|
48
|
+
return [
|
|
49
|
+
{ item_id: 30n, amendment_id: 5n, proposed: { pass_rule: 'consent' }, rationale_md: 'open up',
|
|
50
|
+
proposed_by: 42n, proposer_login: 'sole-archon', proposer_name: 'Sole Archon',
|
|
51
|
+
opened_at: 'T1', closed_at: 'T2', outcome: 'passed', decided_under: MONARCHY },
|
|
52
|
+
{ item_id: 29, amendment_id: 4, proposed: { window_minutes: 5 }, rationale_md: null,
|
|
53
|
+
proposed_by: null, proposer_login: null, proposer_name: null,
|
|
54
|
+
opened_at: 'T0', closed_at: 'T1', outcome: 'returned', decided_under: MONARCHY },
|
|
55
|
+
];
|
|
56
|
+
},
|
|
57
|
+
}, () => board.constitutionView());
|
|
58
|
+
assert.equal(view.history.length, 2);
|
|
59
|
+
const [passed, returned] = view.history;
|
|
60
|
+
assert.equal(passed.outcome, 'passed');
|
|
61
|
+
assert.equal(passed.proposer_login, 'sole-archon');
|
|
62
|
+
assert.deepEqual(passed.decided_under, MONARCHY, 'auditable against the constitution of its day (§7 chain)');
|
|
63
|
+
assert.equal(returned.outcome, 'returned', 'a failed attempt to change the rules stays on the record');
|
|
64
|
+
assert.equal(returned.proposed_by, null, 'a departed proposer projects null, honestly');
|
|
65
|
+
for (const h of view.history) {
|
|
66
|
+
assert.equal(typeof h.item_id, 'string');
|
|
67
|
+
assert.equal(typeof h.amendment_id, 'string');
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('the history read includes returned outcomes and is bounded + close-ordered (SQL pins)', () => {
|
|
72
|
+
const src = fs.readFileSync(path.join(GOV, 'db.js'), 'utf8');
|
|
73
|
+
const block = src.slice(src.indexOf('async function listAmendmentHistory'), src.indexOf('// --- amendments'));
|
|
74
|
+
assert.ok(!block.includes("outcome = 'passed'"), 'returned proposals are part of the record — no outcome filter');
|
|
75
|
+
assert.match(block, /closed_at IS NOT NULL/, 'closed sittings only — an open proposal is the Board Room tab, not history');
|
|
76
|
+
assert.match(block, /ORDER BY i\.closed_at DESC/, 'newest change first');
|
|
77
|
+
assert.match(block, /LIMIT \$1/, 'bounded');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('the read rides the vote atom, and the client renders the SERVER value (source pins)', () => {
|
|
81
|
+
const rrc = require(path.join(ROOT, 'src', 'bongos', 'route-rank-check.js'));
|
|
82
|
+
assert.equal(rrc.MODULE_EXPECTED_RANKS.government['GET /government/constitution'],
|
|
83
|
+
`${rrc.PERM_PIN_PREFIX}board.vote.cast`);
|
|
84
|
+
const client = fs.readFileSync(path.join(ROOT, 'modules', 'hall-ui', 'public', 'government.js'), 'utf8');
|
|
85
|
+
assert.match(client, /fetchJson\('\/government\/constitution'\)/, 'the panel loads the resolved value');
|
|
86
|
+
assert.ok(!/'first_ratifier'[^]*hardcod/i.test(client), 'sanity');
|
|
87
|
+
assert.match(client, /loadConstitution\(\)/, 'the panel loads for everyone the page admits');
|
|
88
|
+
});
|
|
@@ -89,8 +89,11 @@ test('every /governance route gates via REAL middleware on exactly its PINNED at
|
|
|
89
89
|
assert.equal(r.rank, FLOOR_LABEL[floor], `${r.key}: classifies to ${atom}'s floor label (${FLOOR_LABEL[floor]})`);
|
|
90
90
|
}
|
|
91
91
|
// The admin surface's own rule survives the generalization, stated directly:
|
|
92
|
-
// everything
|
|
93
|
-
|
|
92
|
+
// everything outside the Board Room family — the /board/ routes and the
|
|
93
|
+
// /constitution read (R18: reading the rule you vote under is part of
|
|
94
|
+
// casting) — still gates on government.manage.
|
|
95
|
+
const boardFamily = (key) => key.includes('/board/') || key.endsWith('/government/constitution');
|
|
96
|
+
for (const r of classified.filter((x) => !boardFamily(x.key))) {
|
|
94
97
|
assert.deepEqual(r.perms, ['government.manage'], `${r.key}: the admin surface gates on the manage atom`);
|
|
95
98
|
}
|
|
96
99
|
});
|