@bongos/core 1.19.232 → 1.19.234

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 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.232",
6
- "core_contract": "1.19.232",
7
- "source_commit": "0410dea5ee4d77aab90467679874cc5abee21963",
5
+ "core_version": "1.19.234",
6
+ "core_contract": "1.19.234",
7
+ "source_commit": "e80285bcecac511d018c7d39b58a2ca05e6fc5a2",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-18T11:50:40.699Z",
9
+ "built_at": "2026-08-18T16:39:12.790Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 311,
13
13
  "agent_docs_stubbed": 21,
14
- "functional_verbatim": 1315,
14
+ "functional_verbatim": 1318,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1647,
20
- "tree_sha256": "94232cdfd24e7ff58e77b2642499e86fad84a4d09483c8b5150028b363080d4d",
19
+ "file_count": 1650,
20
+ "tree_sha256": "c706960c4712d454c74aca743c2612907744dcf50f641f8c9ee685e23db42f37",
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": "6be7d91bb8c0b620f3a67e071e96435946413330e05c97bf901fd7241bbd10f9"
350
+ "sha256": "5084f59df8019560f49971f00b40e9b8d1d6eb9511a9a78fc136ecaff025e57d"
351
351
  },
352
352
  {
353
353
  "path": "clients/bongos-client/bongos-client.global.js",
354
354
  "mode": "0000644",
355
- "sha256": "6af213e35ced87effe655c14a220b472768551c37f0cd7abddebcbdbcb7d81a9"
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": "18f00d369a1689973e2eeb33034302142884cb2836d3f1ea76b882f3379d0cf8"
365
+ "sha256": "5c99db7b6b1b59e595c3c6a7002cf7baa3b130a198213b007448d02a427883f1"
366
366
  },
367
367
  {
368
368
  "path": "clients/bongos-client/index.d.ts",
369
369
  "mode": "0000644",
370
- "sha256": "ed6940e45f5634264705869043f0a795ddb4bea264f036789b3c80cc08424b97"
370
+ "sha256": "14c4cc02b3965badc021489bb7dd0f80bb71c3cc57b2e1dc4dafa76d86128088"
371
371
  },
372
372
  {
373
373
  "path": "clients/bongos-client/index.mjs",
374
374
  "mode": "0000644",
375
- "sha256": "e3d388b90604d290b3e6346129701b7f2a0a15b4c3b6abe915a43c8702daa1e8"
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": "3212eaeaaf4f1f64889fa6b144fcc48845ed12c4fe08b2a74d3c2bcffc503064"
1375
+ "sha256": "c0a604b304ff9936d08998b2b4206e7ecad8ff668ec704b08161c5d79d0bc04c"
1376
1376
  },
1377
1377
  {
1378
1378
  "path": "docs/api/openapi.json",
1379
1379
  "mode": "0000644",
1380
- "sha256": "0709c0e537931a75055afea96cf48dd9f3820faa09cd781f07028b7d0b1f26c8"
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": "359de8efb5ec1365e6114a8c5b3d62aadd484ca7d94d1a36684b6c0c640aaa99"
2905
+ "sha256": "34a041cb7d001b71109b8243cfd2d34dd4185da4656d6eb31ed8af113490fd6b"
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": "cb5c481fcd1a9f6becb63b88a1f0ca4004e592a2f70e02c968a3cc10378dabd5"
2920
+ "sha256": "11f06bb6eb105a7f52a5a6aeb94356a3cb975f82b1e4ed381bacef82463feba2"
2921
2921
  },
2922
2922
  {
2923
2923
  "path": "modules/government/migrations/governance_001_tables.sql",
@@ -2989,6 +2989,11 @@
2989
2989
  "mode": "0000644",
2990
2990
  "sha256": "5629112a9bb932873a00b8ddefa847d9a21ed0f8f59cc8a77ffb9c9c5e0eea49"
2991
2991
  },
2992
+ {
2993
+ "path": "modules/government/migrations/government_008_closed_items_index.sql",
2994
+ "mode": "0000644",
2995
+ "sha256": "2392050b4295f9fdb4105b8434a4c44833b5f0e850f11b41afe82ea1860fef34"
2996
+ },
2992
2997
  {
2993
2998
  "path": "modules/government/module.json",
2994
2999
  "mode": "0000644",
@@ -3022,7 +3027,7 @@
3022
3027
  {
3023
3028
  "path": "modules/government/routes/government.js",
3024
3029
  "mode": "0000644",
3025
- "sha256": "2bde6d18afc3f24ae2eaeac08b16d83d3ab6ddb22b61da4b9b2f261ee4e844a6"
3030
+ "sha256": "bf07f55f73d27d738840f4f0e0ffcb8a6d19225e7500dbb36e4a83a1109635db"
3026
3031
  },
3027
3032
  {
3028
3033
  "path": "modules/government/routes/rank-sync.js",
@@ -3267,12 +3272,12 @@
3267
3272
  {
3268
3273
  "path": "modules/hall-ui/public/government.html",
3269
3274
  "mode": "0000644",
3270
- "sha256": "23ceb462eddfb0f66ba3030231ae1394d72eb0b6689a7f127c1ba52554c874bf"
3275
+ "sha256": "a76e7263c87178427061ff6b02588e524ca885b3964e54f693e4fc88c9ef5c26"
3271
3276
  },
3272
3277
  {
3273
3278
  "path": "modules/hall-ui/public/government.js",
3274
3279
  "mode": "0000644",
3275
- "sha256": "4778f9a83d63900aca6969a85e7cb67850ffb591a6cbae1fb4be270393741cd1"
3280
+ "sha256": "7d6ce981e085a0a5d3fed711088720c412a82b54c4c2b9f436f1e112b453c2d3"
3276
3281
  },
3277
3282
  {
3278
3283
  "path": "modules/hall-ui/public/hall-kit.js",
@@ -4317,12 +4322,12 @@
4317
4322
  {
4318
4323
  "path": "package-lock.json",
4319
4324
  "mode": "0000644",
4320
- "sha256": "de0ff156427786537d3a4231d4fae7721d52027b9a25d6185bc53efac35b24d2"
4325
+ "sha256": "f08d3eab8dc3b1ef943b3ef19523e21c7676ba91e57f1791ce984b7847c27078"
4321
4326
  },
4322
4327
  {
4323
4328
  "path": "package.json",
4324
4329
  "mode": "0000644",
4325
- "sha256": "c5fa9bf830e64903a205cf28feabd9255772343cc1f002f344fa92ede02f7f7d"
4330
+ "sha256": "fe66476a7d77fe1f6ddaf04090e1af767696443fab948f0fc64713ed48c64b5d"
4326
4331
  },
4327
4332
  {
4328
4333
  "path": "public-docs/index.html",
@@ -5802,7 +5807,7 @@
5802
5807
  {
5803
5808
  "path": "src/bongos/route-rank-check.js",
5804
5809
  "mode": "0000644",
5805
- "sha256": "34850de6361ab78ce8788e20c483ded22d3de9ff9fb42bf14497509b0870f66c"
5810
+ "sha256": "93b2a09220f4d96f69294531cbca274b1132d164cf32be2f66d40d30e1953551"
5806
5811
  },
5807
5812
  {
5808
5813
  "path": "src/bongos/routes.js",
@@ -5902,7 +5907,7 @@
5902
5907
  {
5903
5908
  "path": "src/bongos/serve-internal.js",
5904
5909
  "mode": "0000644",
5905
- "sha256": "94af8e061c690b6fb979cd95ec3a65e21b0f830898a05ab868c5a9f34d9dee72"
5910
+ "sha256": "885076ed4dce0676735055df3f841a72ac41846568d39434a80d48eacced3e16"
5906
5911
  },
5907
5912
  {
5908
5913
  "path": "src/bongos/stealth-gate.js",
@@ -5937,7 +5942,7 @@
5937
5942
  {
5938
5943
  "path": "src/module-api.js",
5939
5944
  "mode": "0000644",
5940
- "sha256": "1302cbcd4f4b6b35032165625c203741ccc846304680876d3789ce212d0bc746"
5945
+ "sha256": "509de05d397e1c27f123e67e666147efa117ce28e98ef00f1386687ca15a4097"
5941
5946
  },
5942
5947
  {
5943
5948
  "path": "src/module-loader/catalog.js",
@@ -6879,6 +6884,11 @@
6879
6884
  "mode": "0000644",
6880
6885
  "sha256": "1a6f90e319c88ecd3e5588d5a6df44a9848c0adf08efd13bfcc52ac99e84d5d0"
6881
6886
  },
6887
+ {
6888
+ "path": "tests/government_board_view.mjs",
6889
+ "mode": "0000644",
6890
+ "sha256": "104dbfd5510f4abf4580cf89f57e4be15667b4b6e6f171a2635786bfd4d65f9e"
6891
+ },
6882
6892
  {
6883
6893
  "path": "tests/government_board_vote.mjs",
6884
6894
  "mode": "0000644",
@@ -6904,6 +6914,11 @@
6904
6914
  "mode": "0000644",
6905
6915
  "sha256": "330372412c80d8d33698eba68a5c19e79daff17b0d820a9a797c883071a5641f"
6906
6916
  },
6917
+ {
6918
+ "path": "tests/government_constitution_view.mjs",
6919
+ "mode": "0000644",
6920
+ "sha256": "daf26af68fcaa19a6de3793e0c9ab81b2981d8ed5e4d5ec5f860c686ac7610fb"
6921
+ },
6907
6922
  {
6908
6923
  "path": "tests/government_fold.mjs",
6909
6924
  "mode": "0000644",
@@ -6962,12 +6977,12 @@
6962
6977
  {
6963
6978
  "path": "tests/government_routes.mjs",
6964
6979
  "mode": "0000644",
6965
- "sha256": "d32b42a6046361fdcb6ff26e92dfe2abd46fb76a218168c719377ca39614112b"
6980
+ "sha256": "88f1c323de51a9b9f710bd4ad8cbf86da75b226d7b99db2bc2054e065b81a70a"
6966
6981
  },
6967
6982
  {
6968
6983
  "path": "tests/government_seed.mjs",
6969
6984
  "mode": "0000644",
6970
- "sha256": "9c0c1049180013f30a3403a4e308112ed871119d9e60c459e9573108b35a5a27"
6985
+ "sha256": "cd67c76a30e1c69838f99afbb8edd17a0ec2109a8105b61805b9564e27464137"
6971
6986
  },
6972
6987
  {
6973
6988
  "path": "tests/government_session_scope.mjs",
@@ -7057,7 +7072,7 @@
7057
7072
  {
7058
7073
  "path": "tests/hall_page_gate_map.mjs",
7059
7074
  "mode": "0000644",
7060
- "sha256": "cb23b7a54220c3c53906fed559fb0c8a8e97375aa48dfc3cf64a1af88339e110"
7075
+ "sha256": "98a5317bf8599941802794a5a0679e1ad2ff84dbaa7c36c88140a5f5fcd30780"
7061
7076
  },
7062
7077
  {
7063
7078
  "path": "tests/hall_palette.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
- - 283 operations across 46 resource groups
8
+ - 285 operations across 46 resource groups
9
9
 
10
10
  ## Install
11
11
 
@@ -349,10 +349,14 @@ function createClient(opts = {}) {
349
349
  postGovernmentAssignments: (args) => request("POST", "/government/assignments", { hasBody: true }, args),
350
350
  // DELETE /government/assignments/{builderId}/{rankKey} — rank: archon — DELETE /government/assignments/:builderId/:rankKey
351
351
  deleteGovernmentAssignmentsBuilderIdRankKey: (args) => request("DELETE", "/government/assignments/{builderId}/{rankKey}", { hasBody: false }, args),
352
+ // GET /government/board/items — rank: any-builder — GET /government/board/items
353
+ getGovernmentBoardItems: (args) => request("GET", "/government/board/items", { hasBody: false }, args),
352
354
  // POST /government/board/items — rank: metic+archon — POST /government/board/items
353
355
  postGovernmentBoardItems: (args) => request("POST", "/government/board/items", { hasBody: true }, args),
354
356
  // POST /government/board/items/{itemId}/votes — rank: any-builder — POST /government/board/items/:itemId/votes
355
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),
356
360
  // GET /government/permissions — rank: archon — GET /government/permissions
357
361
  getGovernmentPermissions: (args) => request("GET", "/government/permissions", { hasBody: false }, args),
358
362
  // GET /government/ranks — rank: archon — GET /government/ranks
@@ -348,10 +348,14 @@ function createClient(opts = {}) {
348
348
  postGovernmentAssignments: (args) => request("POST", "/government/assignments", { hasBody: true }, args),
349
349
  // DELETE /government/assignments/{builderId}/{rankKey} — rank: archon — DELETE /government/assignments/:builderId/:rankKey
350
350
  deleteGovernmentAssignmentsBuilderIdRankKey: (args) => request("DELETE", "/government/assignments/{builderId}/{rankKey}", { hasBody: false }, args),
351
+ // GET /government/board/items — rank: any-builder — GET /government/board/items
352
+ getGovernmentBoardItems: (args) => request("GET", "/government/board/items", { hasBody: false }, args),
351
353
  // POST /government/board/items — rank: metic+archon — POST /government/board/items
352
354
  postGovernmentBoardItems: (args) => request("POST", "/government/board/items", { hasBody: true }, args),
353
355
  // POST /government/board/items/{itemId}/votes — rank: any-builder — POST /government/board/items/:itemId/votes
354
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),
355
359
  // GET /government/permissions — rank: archon — GET /government/permissions
356
360
  getGovernmentPermissions: (args) => request("GET", "/government/permissions", { hasBody: false }, args),
357
361
  // GET /government/ranks — rank: archon — GET /government/ranks
@@ -603,10 +603,14 @@ export interface BongosClient {
603
603
  postGovernmentAssignments(args: RequestArgs & { body: PostGovernmentAssignmentsRequest }): Promise<ApiResponse>;
604
604
  /** DELETE /government/assignments/{builderId}/{rankKey} — rank: archon */
605
605
  deleteGovernmentAssignmentsBuilderIdRankKey(args?: RequestArgs): Promise<DeleteGovernmentAssignmentsBuilderIdRankKeyResponse>;
606
+ /** GET /government/board/items — rank: any-builder */
607
+ getGovernmentBoardItems(args?: RequestArgs): Promise<ApiResponse>;
606
608
  /** POST /government/board/items — rank: metic+archon */
607
609
  postGovernmentBoardItems(args: RequestArgs & { body: PostGovernmentBoardItemsRequest }): Promise<PostGovernmentBoardItemsResponse>;
608
610
  /** POST /government/board/items/{itemId}/votes — rank: any-builder */
609
611
  postGovernmentBoardItemsItemIdVotes(args: RequestArgs & { body: PostGovernmentBoardItemsItemIdVotesRequest }): Promise<ApiResponse>;
612
+ /** GET /government/constitution — rank: any-builder */
613
+ getGovernmentConstitution(args?: RequestArgs): Promise<ApiResponse>;
610
614
  /** GET /government/permissions — rank: archon */
611
615
  getGovernmentPermissions(args?: RequestArgs): Promise<GetGovernmentPermissionsResponse>;
612
616
  /** GET /government/ranks — rank: archon */
@@ -345,10 +345,14 @@ export function createClient(opts = {}) {
345
345
  postGovernmentAssignments: (args) => request("POST", "/government/assignments", { hasBody: true }, args),
346
346
  // DELETE /government/assignments/{builderId}/{rankKey} — rank: archon — DELETE /government/assignments/:builderId/:rankKey
347
347
  deleteGovernmentAssignmentsBuilderIdRankKey: (args) => request("DELETE", "/government/assignments/{builderId}/{rankKey}", { hasBody: false }, args),
348
+ // GET /government/board/items — rank: any-builder — GET /government/board/items
349
+ getGovernmentBoardItems: (args) => request("GET", "/government/board/items", { hasBody: false }, args),
348
350
  // POST /government/board/items — rank: metic+archon — POST /government/board/items
349
351
  postGovernmentBoardItems: (args) => request("POST", "/government/board/items", { hasBody: true }, args),
350
352
  // POST /government/board/items/{itemId}/votes — rank: any-builder — POST /government/board/items/:itemId/votes
351
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),
352
356
  // GET /government/permissions — rank: archon — GET /government/permissions
353
357
  getGovernmentPermissions: (args) => request("GET", "/government/permissions", { hasBody: false }, args),
354
358
  // GET /government/ranks — rank: archon — GET /government/ranks
@@ -5337,6 +5337,38 @@
5337
5337
  }
5338
5338
  },
5339
5339
  "/government/board/items": {
5340
+ "get": {
5341
+ "operationId": "get_government_board_items",
5342
+ "tags": [
5343
+ "government"
5344
+ ],
5345
+ "summary": "GET /government/board/items",
5346
+ "description": "board.vote.cast — the Board Room view: open items with their clock, the OPEN BALLOT, the closed history (R16 of goal 1000069). Gated on the XENOS-floored vote atom, deliberately: seeing the ballot is part of casting (§9 — a board whose votes are invisible constrains nobody), and the room teaches as well as governs (criterion C5). NOT government.manage — that is the admin tab's atom; the room must be visible to whoever the constitution may enfranchise. Membership still gates nothing here: this is a read, and the write (the vote) re-checks membership in-handler.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).\n\n**Permissions:** `board.vote.cast` (all required).",
5347
+ "x-rank": "any-builder",
5348
+ "x-source": "modules/government/routes/government.js",
5349
+ "x-permissions": [
5350
+ "board.vote.cast"
5351
+ ],
5352
+ "responses": {
5353
+ "200": {
5354
+ "description": "Success."
5355
+ },
5356
+ "400": {
5357
+ "$ref": "#/components/responses/BadRequest"
5358
+ },
5359
+ "401": {
5360
+ "$ref": "#/components/responses/Unauthorized"
5361
+ },
5362
+ "403": {
5363
+ "$ref": "#/components/responses/Forbidden"
5364
+ }
5365
+ },
5366
+ "security": [
5367
+ {
5368
+ "builderSession": []
5369
+ }
5370
+ ]
5371
+ },
5340
5372
  "post": {
5341
5373
  "operationId": "post_government_board_items",
5342
5374
  "tags": [
@@ -5447,6 +5479,40 @@
5447
5479
  ]
5448
5480
  }
5449
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
+ },
5450
5516
  "/government/permissions": {
5451
5517
  "get": {
5452
5518
  "operationId": "get_government_permissions",
@@ -17969,7 +18035,7 @@
17969
18035
  "description": "A required dependency/feature is not configured or is temporarily down."
17970
18036
  }
17971
18037
  },
17972
- "x-endpoint-count": 283,
18038
+ "x-endpoint-count": 285,
17973
18039
  "x-schema-count": 321,
17974
18040
  "x-undocumented-bodies": 8,
17975
18041
  "x-response-schemas": 215,
@@ -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. 283 endpoints across 46 route files.
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,15 +216,17 @@ 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` (11)
219
+ ## `government` (13)
220
220
 
221
221
  | Method | Path | Rank | Body | Description |
222
222
  |---|---|---|---|---|
223
223
  | GET | `/api/bongos/government/assignments` | `archon` | — | government.manage — every builder↔rank assignment row. |
224
224
  | POST | `/api/bongos/government/assignments` | `archon` | `builder_id`, `rank_key` | government.manage — assign a CUSTOM rank to a builder. |
225
225
  | DELETE | `/api/bongos/government/assignments/:builderId/:rankKey` | `archon` | — | government.manage — remove a custom-rank assignment. |
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). |
226
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). |
227
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 … |
228
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. |
229
231
  | GET | `/api/bongos/government/ranks` | `archon` | — | government.manage — the full rank list with each rank's grants. |
230
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). |
@@ -453,6 +453,102 @@ async function proposeAmendment({ proposal, rationaleMd = null, proposedBy } = {
453
453
  return { ok: true, amendment, item };
454
454
  }
455
455
 
456
+ // ── the Board Room view (BV1.R16 of goal 1000069, ADR 0175 §9) ──────────────
457
+ //
458
+ // One assembled read for the room: open items with their clock, the OPEN
459
+ // BALLOT (people, directions, timestamps, objections), and the bounded closed
460
+ // history — enriched with what each subject IS (an idea's title, an
461
+ // amendment's proposed constitution). Deliberately a VIEW, not a policy
462
+ // surface: nothing here decides anything, and the payload carries the same
463
+ // constitution SNAPSHOT the deciding paths use, so the room describes the rule
464
+ // each item actually runs under. `remaining_seconds` comes from the database
465
+ // clock per row — the client renders a countdown, it never does clock math.
466
+ //
467
+ // The subject AUTHOR rides along (an idea's captor / an amendment's proposer)
468
+ // so R17's vote UI can grey out the anti-self case for ideas without a second
469
+ // round trip — the SERVER still refuses regardless (castVote), this is only
470
+ // the courtesy rendering.
471
+ async function boardRoomView({ closedLimit = 20 } = {}) {
472
+ const [open, closed] = await Promise.all([
473
+ db.listOpenBoardItems(),
474
+ db.listClosedBoardItems(closedLimit),
475
+ ]);
476
+ const all = [...open, ...closed];
477
+ const votes = await db.getVotesForItems(all.map((i) => i.id));
478
+ const ideaIds = [...new Set(all.filter((i) => i.subject_type === 'full_idea').map((i) => i.subject_id))];
479
+ const amendmentIds = [...new Set(all.filter((i) => i.subject_type === 'constitutional_amendment').map((i) => i.subject_id))];
480
+ const [ideas, amendments] = await Promise.all([
481
+ db.getIdeaSubjects(ideaIds),
482
+ db.getAmendmentSubjects(amendmentIds),
483
+ ]);
484
+ const ideaById = new Map(ideas.map((r) => [String(r.id), r]));
485
+ const amendmentById = new Map(amendments.map((r) => [String(r.id), r]));
486
+ const votesByItem = new Map();
487
+ for (const v of votes) {
488
+ const key = String(v.item_id);
489
+ if (!votesByItem.has(key)) votesByItem.set(key, []);
490
+ votesByItem.get(key).push({
491
+ voter_id: String(v.voter_id),
492
+ github_login: v.github_login,
493
+ display_name: v.display_name,
494
+ direction: v.direction,
495
+ reason_md: v.reason_md,
496
+ section_key: v.section_key,
497
+ cast_at: v.cast_at,
498
+ });
499
+ }
500
+ const project = (item) => {
501
+ const subject = item.subject_type === 'full_idea'
502
+ ? (() => { const r = ideaById.get(String(item.subject_id)); return r ? { title: r.title, author_id: String(r.captured_by) } : null; })()
503
+ : (() => { const r = amendmentById.get(String(item.subject_id)); return r ? { proposed: r.proposed, rationale_md: r.rationale_md, author_id: r.proposed_by == null ? null : String(r.proposed_by) } : null; })();
504
+ return {
505
+ id: String(item.id),
506
+ subject_type: item.subject_type,
507
+ subject_id: String(item.subject_id),
508
+ subject, // null when the subject row is gone — the room says so honestly
509
+ opened_by: item.opened_by == null ? null : String(item.opened_by),
510
+ opened_at: item.opened_at,
511
+ closes_at: item.closes_at,
512
+ remaining_seconds: item.remaining_seconds ?? null,
513
+ expired: item.expired === true,
514
+ closed_at: item.closed_at || null,
515
+ outcome: item.outcome || null,
516
+ constitution: item.constitution || null,
517
+ votes: votesByItem.get(String(item.id)) || [],
518
+ };
519
+ };
520
+ return { open: open.map(project), closed: closed.map(project) };
521
+ }
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
+ async function constitutionView({ historyLimit = 50 } = {}) {
534
+ const { board: live } = loadGovernmentConfig();
535
+ const rows = await db.listAmendmentHistory(historyLimit);
536
+ const history = rows.map((r) => ({
537
+ item_id: String(r.item_id),
538
+ amendment_id: String(r.amendment_id),
539
+ proposed: r.proposed,
540
+ rationale_md: r.rationale_md,
541
+ proposed_by: r.proposed_by == null ? null : String(r.proposed_by),
542
+ proposer_login: r.proposer_login || null,
543
+ proposer_name: r.proposer_name || null,
544
+ opened_at: r.opened_at,
545
+ closed_at: r.closed_at,
546
+ outcome: r.outcome,
547
+ decided_under: r.decided_under || null,
548
+ }));
549
+ return { board: live, history };
550
+ }
551
+
456
552
  // ── R13's read: the feedback a returned idea carries back to its author ─────
457
553
  //
458
554
  // One reasoned objection is what returns an idea (§3), so the feedback IS that
@@ -493,4 +589,6 @@ module.exports = {
493
589
  latestRejectionForIdea,
494
590
  validateProposedConstitution,
495
591
  proposeAmendment,
592
+ boardRoomView,
593
+ constitutionView,
496
594
  };
@@ -437,6 +437,100 @@ async function getIdeaCompleteness(ideaId) {
437
437
  return rows[0] ? rows[0].completeness_score : null;
438
438
  }
439
439
 
440
+ // --- the Board Room reads (BV1.R16 of goal 1000069) --------------------------
441
+ //
442
+ // The room's view: open items with their clock, the OPEN BALLOT (voter
443
+ // identity, direction, timestamp, objection), and the closed history. Remaining
444
+ // time is computed by the DATABASE clock per row (the same posture as expiry),
445
+ // so the client never does clock math against its own skew. Concurrent windows
446
+ // are normal — the reads are lists, ordered.
447
+
448
+ async function listOpenBoardItems() {
449
+ const { rows } = await pool.query(
450
+ `SELECT *,
451
+ (closes_at IS NOT NULL AND closes_at < now()) AS expired,
452
+ CASE WHEN closes_at IS NULL THEN NULL
453
+ ELSE GREATEST(0, EXTRACT(EPOCH FROM (closes_at - now()))::int) END AS remaining_seconds
454
+ FROM government_board_items
455
+ WHERE closed_at IS NULL
456
+ ORDER BY opened_at DESC`,
457
+ );
458
+ return rows;
459
+ }
460
+
461
+ // Bounded closed history, newest close first. Served by government_008's
462
+ // closed-items index — the closed set grows forever (resubmission is
463
+ // unbounded), and this runs on every Board Room page load.
464
+ async function listClosedBoardItems(limit = 20) {
465
+ const { rows } = await pool.query(
466
+ `SELECT * FROM government_board_items
467
+ WHERE closed_at IS NOT NULL
468
+ ORDER BY closed_at DESC
469
+ LIMIT $1`,
470
+ [limit],
471
+ );
472
+ return rows;
473
+ }
474
+
475
+ // The open ballot for a SET of items in one query — voter identity joined so
476
+ // the room shows people, not ids (§9: a board whose votes are invisible
477
+ // constrains nobody).
478
+ async function getVotesForItems(itemIds) {
479
+ if (!itemIds || !itemIds.length) return [];
480
+ const { rows } = await pool.query(
481
+ `SELECT v.*, b.github_login, b.display_name
482
+ FROM government_board_votes v
483
+ JOIN builders b ON b.id = v.voter_id
484
+ WHERE v.item_id = ANY($1::bigint[])
485
+ ORDER BY v.cast_at, v.id`,
486
+ [itemIds],
487
+ );
488
+ return rows;
489
+ }
490
+
491
+ // Subject enrichment, one query per subject family. Shared-table reads (the
492
+ // boundary is code imports, not table access) — a full_idea shows its title,
493
+ // an amendment its proposed block + rationale.
494
+ async function getIdeaSubjects(ideaIds) {
495
+ if (!ideaIds || !ideaIds.length) return [];
496
+ const { rows } = await pool.query(
497
+ `SELECT id, title, captured_by FROM idea_inbox WHERE id = ANY($1::bigint[])`,
498
+ [ideaIds],
499
+ );
500
+ return rows;
501
+ }
502
+
503
+ async function getAmendmentSubjects(amendmentIds) {
504
+ if (!amendmentIds || !amendmentIds.length) return [];
505
+ const { rows } = await pool.query(
506
+ `SELECT id, proposed, rationale_md, proposed_by FROM government_amendments WHERE id = ANY($1::bigint[])`,
507
+ [amendmentIds],
508
+ );
509
+ return rows;
510
+ }
511
+
512
+ // The constitution's change HISTORY (R18): every CLOSED amendment sitting —
513
+ // passed AND returned — joined to what was proposed, newest first. Returned
514
+ // proposals are deliberately part of the record: "a switch can never be flipped
515
+ // quietly" cuts both ways, and a failed attempt to change the rules is exactly
516
+ // the kind of fact an open ballot exists to keep visible. Served by
517
+ // government_008's closed-items index; bounded.
518
+ async function listAmendmentHistory(limit = 50) {
519
+ const { rows } = await pool.query(
520
+ `SELECT i.id AS item_id, i.opened_at, i.closed_at, i.outcome, i.constitution AS decided_under,
521
+ a.id AS amendment_id, a.proposed, a.rationale_md, a.proposed_by,
522
+ b.github_login AS proposer_login, b.display_name AS proposer_name
523
+ FROM government_board_items i
524
+ JOIN government_amendments a ON a.id = i.subject_id
525
+ LEFT JOIN builders b ON b.id = a.proposed_by
526
+ WHERE i.subject_type = 'constitutional_amendment' AND i.closed_at IS NOT NULL
527
+ ORDER BY i.closed_at DESC
528
+ LIMIT $1`,
529
+ [limit],
530
+ );
531
+ return rows;
532
+ }
533
+
440
534
  // --- amendments (BV1.R14 of goal 1000069) ------------------------------------
441
535
 
442
536
  async function createAmendment({ proposed, rationaleMd = null, proposedBy }) {
@@ -553,4 +647,10 @@ module.exports = {
553
647
  getLatestReturnedItemWithObjection,
554
648
  createAmendment,
555
649
  getAmendment,
650
+ listOpenBoardItems,
651
+ listClosedBoardItems,
652
+ getVotesForItems,
653
+ getIdeaSubjects,
654
+ getAmendmentSubjects,
655
+ listAmendmentHistory,
556
656
  };
@@ -0,0 +1,17 @@
1
+ -- government_008_closed_items_index.sql — index the closed-history read
2
+ -- (task 1003029 / R16 of goal 1000069).
3
+ --
4
+ -- The Board Room's closed history reads "newest closes first, LIMIT n" on every
5
+ -- page load, and the closed set grows forever (one row per window; resubmission
6
+ -- is unbounded by design). The table's other indexes are partial on OPEN items
7
+ -- (the sweep, the one-open-per-subject wall) or on outcome alone — none order
8
+ -- by close time. Same reasoning as government_006's returned-items index.
9
+ -- Additive, idempotent, no rows touched.
10
+
11
+ BEGIN;
12
+
13
+ CREATE INDEX IF NOT EXISTS government_board_items_closed_by_time
14
+ ON government_board_items (closed_at DESC)
15
+ WHERE closed_at IS NOT NULL;
16
+
17
+ COMMIT;