@bongos/core 1.19.224 → 1.19.226
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 +41 -26
- 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 +3 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/api/openapi.json +82 -2
- package/docs/api-reference.md +3 -2
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +2 -0
- package/modules/government/board.js +211 -0
- package/modules/government/db.js +125 -0
- package/modules/government/routes/government.js +54 -0
- package/modules/ideas/completeness.js +44 -1
- package/modules/ideas/module.json +2 -1
- package/modules/ideas/routes/inbox.js +39 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/bongos/route-rank-check.js +4 -0
- package/src/module-api.js +5 -1
- package/tests/government_board_vote.mjs +247 -0
- package/tests/government_board_window.mjs +144 -0
- package/tests/government_routes.mjs +26 -5
- package/tests/idea_completeness.mjs +30 -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.226",
|
|
6
|
+
"core_contract": "1.19.226",
|
|
7
|
+
"source_commit": "b99c0b52cf19ffd33ad98d0fc3dd763011559401",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-18T04:
|
|
9
|
+
"built_at": "2026-08-18T04:50:04.434Z",
|
|
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": 1308,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1640,
|
|
20
|
+
"tree_sha256": "024eb9d71d1dd27389cb4db58a347eadfad7412b37e48d140a9c113cc8ee9234",
|
|
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": "f963a7ac5bccdc2811ea13f288ec9ff706b4c090ff0df185e9d3111d62194a5e"
|
|
351
351
|
},
|
|
352
352
|
{
|
|
353
353
|
"path": "clients/bongos-client/bongos-client.global.js",
|
|
354
354
|
"mode": "0000644",
|
|
355
|
-
"sha256": "
|
|
355
|
+
"sha256": "8b5bf5d1a077d73a402cbd3ddb215b021d764b9a0167185b072dcf41c802bc67"
|
|
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": "6bfe5877a0ee3bea5f6162298792a67bd40741786a3add959e237052255929c2"
|
|
366
366
|
},
|
|
367
367
|
{
|
|
368
368
|
"path": "clients/bongos-client/index.d.ts",
|
|
369
369
|
"mode": "0000644",
|
|
370
|
-
"sha256": "
|
|
370
|
+
"sha256": "68708a1f6a8c4a01dcc62306ee1d09a40f961698bbb0605454a2749514738da2"
|
|
371
371
|
},
|
|
372
372
|
{
|
|
373
373
|
"path": "clients/bongos-client/index.mjs",
|
|
374
374
|
"mode": "0000644",
|
|
375
|
-
"sha256": "
|
|
375
|
+
"sha256": "8b8a1df89fcd53cae49b2f398ac335dc35fbfd3632a9d6c6e1fa49b8e8f4244b"
|
|
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": "981ce74f7c526b079ec880c5343d91f28bfba16c5446b19726d11669679ffdb7"
|
|
1376
1376
|
},
|
|
1377
1377
|
{
|
|
1378
1378
|
"path": "docs/api/openapi.json",
|
|
1379
1379
|
"mode": "0000644",
|
|
1380
|
-
"sha256": "
|
|
1380
|
+
"sha256": "af44a3a2e2b866f4d7effd64b910047aeda53d894e17b9e7fdbf7f33ad0b8502"
|
|
1381
1381
|
},
|
|
1382
1382
|
{
|
|
1383
1383
|
"path": "docs/architecture.md",
|
|
@@ -2687,7 +2687,7 @@
|
|
|
2687
2687
|
{
|
|
2688
2688
|
"path": "modules/dev-box/app/src/vendor/bongos-client.cjs",
|
|
2689
2689
|
"mode": "0000644",
|
|
2690
|
-
"sha256": "
|
|
2690
|
+
"sha256": "6bfe5877a0ee3bea5f6162298792a67bd40741786a3add959e237052255929c2"
|
|
2691
2691
|
},
|
|
2692
2692
|
{
|
|
2693
2693
|
"path": "modules/dev-box/app/test/unit.mjs",
|
|
@@ -2899,6 +2899,11 @@
|
|
|
2899
2899
|
"mode": "0000644",
|
|
2900
2900
|
"sha256": "5bf474bee95bc8a8982e4a52c9e5b19955a20195dc1affd51c07e99540c8c059"
|
|
2901
2901
|
},
|
|
2902
|
+
{
|
|
2903
|
+
"path": "modules/government/board.js",
|
|
2904
|
+
"mode": "0000644",
|
|
2905
|
+
"sha256": "0711f087cc61ed19f91fb207705a9e4831422c9acf29ae8402f87926a31d2a51"
|
|
2906
|
+
},
|
|
2902
2907
|
{
|
|
2903
2908
|
"path": "modules/government/catalog.js",
|
|
2904
2909
|
"mode": "0000644",
|
|
@@ -2912,7 +2917,7 @@
|
|
|
2912
2917
|
{
|
|
2913
2918
|
"path": "modules/government/db.js",
|
|
2914
2919
|
"mode": "0000644",
|
|
2915
|
-
"sha256": "
|
|
2920
|
+
"sha256": "1f50cef895c7937aebbb365aa7a9ce1b1681bd097034c0045d94458cd369f50a"
|
|
2916
2921
|
},
|
|
2917
2922
|
{
|
|
2918
2923
|
"path": "modules/government/migrations/governance_001_tables.sql",
|
|
@@ -3002,7 +3007,7 @@
|
|
|
3002
3007
|
{
|
|
3003
3008
|
"path": "modules/government/routes/government.js",
|
|
3004
3009
|
"mode": "0000644",
|
|
3005
|
-
"sha256": "
|
|
3010
|
+
"sha256": "41ac023e96901b9b3cf8a5059a5c26ff5f86b564d7bb37dc8d5a55a206db85fb"
|
|
3006
3011
|
},
|
|
3007
3012
|
{
|
|
3008
3013
|
"path": "modules/government/routes/rank-sync.js",
|
|
@@ -3512,7 +3517,7 @@
|
|
|
3512
3517
|
{
|
|
3513
3518
|
"path": "modules/ideas/completeness.js",
|
|
3514
3519
|
"mode": "0000644",
|
|
3515
|
-
"sha256": "
|
|
3520
|
+
"sha256": "07e5666362c91f92d34388b4f6f4507cc5387866d317970478e6cae417c19975"
|
|
3516
3521
|
},
|
|
3517
3522
|
{
|
|
3518
3523
|
"path": "modules/ideas/inbox.js",
|
|
@@ -3532,7 +3537,7 @@
|
|
|
3532
3537
|
{
|
|
3533
3538
|
"path": "modules/ideas/module.json",
|
|
3534
3539
|
"mode": "0000644",
|
|
3535
|
-
"sha256": "
|
|
3540
|
+
"sha256": "c9a5e2bab38b9e45e4934f578f4ebbf0966f9e971318548d5c445d2f6a3de3a7"
|
|
3536
3541
|
},
|
|
3537
3542
|
{
|
|
3538
3543
|
"path": "modules/ideas/routes/blockers.js",
|
|
@@ -3542,7 +3547,7 @@
|
|
|
3542
3547
|
{
|
|
3543
3548
|
"path": "modules/ideas/routes/inbox.js",
|
|
3544
3549
|
"mode": "0000644",
|
|
3545
|
-
"sha256": "
|
|
3550
|
+
"sha256": "9e9288725b66b696bdc565e693c82f046bd78665ae070e3f571ff987f82a7b7b"
|
|
3546
3551
|
},
|
|
3547
3552
|
{
|
|
3548
3553
|
"path": "modules/ideas/templates.js",
|
|
@@ -4297,12 +4302,12 @@
|
|
|
4297
4302
|
{
|
|
4298
4303
|
"path": "package-lock.json",
|
|
4299
4304
|
"mode": "0000644",
|
|
4300
|
-
"sha256": "
|
|
4305
|
+
"sha256": "93e2a0f8b239aa724d99178fa5cfcd7794d7a09c9e820a9ba3f8da94a81ec2b0"
|
|
4301
4306
|
},
|
|
4302
4307
|
{
|
|
4303
4308
|
"path": "package.json",
|
|
4304
4309
|
"mode": "0000644",
|
|
4305
|
-
"sha256": "
|
|
4310
|
+
"sha256": "378978670cd1efdcc3929f44178118e321f8e381bb923aff175716af2b24134d"
|
|
4306
4311
|
},
|
|
4307
4312
|
{
|
|
4308
4313
|
"path": "public-docs/index.html",
|
|
@@ -5782,7 +5787,7 @@
|
|
|
5782
5787
|
{
|
|
5783
5788
|
"path": "src/bongos/route-rank-check.js",
|
|
5784
5789
|
"mode": "0000644",
|
|
5785
|
-
"sha256": "
|
|
5790
|
+
"sha256": "a37f6723510eb9d3a4adc3a927213336716810c8814ec82b4b9547eed1983188"
|
|
5786
5791
|
},
|
|
5787
5792
|
{
|
|
5788
5793
|
"path": "src/bongos/routes.js",
|
|
@@ -5917,7 +5922,7 @@
|
|
|
5917
5922
|
{
|
|
5918
5923
|
"path": "src/module-api.js",
|
|
5919
5924
|
"mode": "0000644",
|
|
5920
|
-
"sha256": "
|
|
5925
|
+
"sha256": "d7a3216e0393ff4a4cb0fe3c7823b9c8a294ff45549a4a698b95c78ea93e4ad8"
|
|
5921
5926
|
},
|
|
5922
5927
|
{
|
|
5923
5928
|
"path": "src/module-loader/catalog.js",
|
|
@@ -6844,6 +6849,16 @@
|
|
|
6844
6849
|
"mode": "0000644",
|
|
6845
6850
|
"sha256": "1a6f90e319c88ecd3e5588d5a6df44a9848c0adf08efd13bfcc52ac99e84d5d0"
|
|
6846
6851
|
},
|
|
6852
|
+
{
|
|
6853
|
+
"path": "tests/government_board_vote.mjs",
|
|
6854
|
+
"mode": "0000644",
|
|
6855
|
+
"sha256": "c1b9b0963dde0c033bd0eb411b63032a51edaa3d91751100593b7e3b4999c017"
|
|
6856
|
+
},
|
|
6857
|
+
{
|
|
6858
|
+
"path": "tests/government_board_window.mjs",
|
|
6859
|
+
"mode": "0000644",
|
|
6860
|
+
"sha256": "bb78d4fe2610e08a2d538d4e4a64cd3b7198da12b09da1313a58ed32a96dab5a"
|
|
6861
|
+
},
|
|
6847
6862
|
{
|
|
6848
6863
|
"path": "tests/government_catalog.mjs",
|
|
6849
6864
|
"mode": "0000644",
|
|
@@ -6917,7 +6932,7 @@
|
|
|
6917
6932
|
{
|
|
6918
6933
|
"path": "tests/government_routes.mjs",
|
|
6919
6934
|
"mode": "0000644",
|
|
6920
|
-
"sha256": "
|
|
6935
|
+
"sha256": "d32b42a6046361fdcb6ff26e92dfe2abd46fb76a218168c719377ca39614112b"
|
|
6921
6936
|
},
|
|
6922
6937
|
{
|
|
6923
6938
|
"path": "tests/government_seed.mjs",
|
|
@@ -7062,7 +7077,7 @@
|
|
|
7062
7077
|
{
|
|
7063
7078
|
"path": "tests/idea_completeness.mjs",
|
|
7064
7079
|
"mode": "0000644",
|
|
7065
|
-
"sha256": "
|
|
7080
|
+
"sha256": "7e2024b3f1fa79a6bdec55b134be6337d4867df0a77fedb205f32b783a3d6193"
|
|
7066
7081
|
},
|
|
7067
7082
|
{
|
|
7068
7083
|
"path": "tests/idea_full_capture.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
|
+
- 281 operations across 46 resource groups
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
@@ -349,6 +349,8 @@ 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
|
+
// POST /government/board/items/{itemId}/votes — rank: any-builder — POST /government/board/items/:itemId/votes
|
|
353
|
+
postGovernmentBoardItemsItemIdVotes: (args) => request("POST", "/government/board/items/{itemId}/votes", { hasBody: true }, args),
|
|
352
354
|
// GET /government/permissions — rank: archon — GET /government/permissions
|
|
353
355
|
getGovernmentPermissions: (args) => request("GET", "/government/permissions", { hasBody: false }, args),
|
|
354
356
|
// GET /government/ranks — rank: archon — GET /government/ranks
|
|
@@ -348,6 +348,8 @@ 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
|
+
// POST /government/board/items/{itemId}/votes — rank: any-builder — POST /government/board/items/:itemId/votes
|
|
352
|
+
postGovernmentBoardItemsItemIdVotes: (args) => request("POST", "/government/board/items/{itemId}/votes", { hasBody: true }, args),
|
|
351
353
|
// GET /government/permissions — rank: archon — GET /government/permissions
|
|
352
354
|
getGovernmentPermissions: (args) => request("GET", "/government/permissions", { hasBody: false }, args),
|
|
353
355
|
// GET /government/ranks — rank: archon — GET /government/ranks
|
|
@@ -232,6 +232,7 @@ export interface PostGoalsReorderResponse { ok: boolean; goals: unknown }
|
|
|
232
232
|
export interface PostGoalsRequest { version_id: string; title: string; description?: string; scope_modules?: string[]; succeeds_goal_id?: number }
|
|
233
233
|
export interface PostGoalsResponse { ok: boolean; goal: unknown; succeeds_goal_id: unknown }
|
|
234
234
|
export interface PostGovernmentAssignmentsRequest { builder_id: number; rank_key: string }
|
|
235
|
+
export interface PostGovernmentBoardItemsItemIdVotesRequest { direction: string; reason_md?: string; section_key?: string }
|
|
235
236
|
export interface PostGovernmentRanksRankKeyResetResponse { ok: boolean; rank_key: unknown; seeded_rank: unknown; permission_count: unknown }
|
|
236
237
|
export interface PostGovernmentRanksRequest { rank_key: string; label: string; description?: string }
|
|
237
238
|
export interface PostGovernmentRanksResponse { rank: unknown }
|
|
@@ -599,6 +600,8 @@ export interface BongosClient {
|
|
|
599
600
|
postGovernmentAssignments(args: RequestArgs & { body: PostGovernmentAssignmentsRequest }): Promise<ApiResponse>;
|
|
600
601
|
/** DELETE /government/assignments/{builderId}/{rankKey} — rank: archon */
|
|
601
602
|
deleteGovernmentAssignmentsBuilderIdRankKey(args?: RequestArgs): Promise<DeleteGovernmentAssignmentsBuilderIdRankKeyResponse>;
|
|
603
|
+
/** POST /government/board/items/{itemId}/votes — rank: any-builder */
|
|
604
|
+
postGovernmentBoardItemsItemIdVotes(args: RequestArgs & { body: PostGovernmentBoardItemsItemIdVotesRequest }): Promise<ApiResponse>;
|
|
602
605
|
/** GET /government/permissions — rank: archon */
|
|
603
606
|
getGovernmentPermissions(args?: RequestArgs): Promise<GetGovernmentPermissionsResponse>;
|
|
604
607
|
/** GET /government/ranks — rank: archon */
|
|
@@ -345,6 +345,8 @@ 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
|
+
// POST /government/board/items/{itemId}/votes — rank: any-builder — POST /government/board/items/:itemId/votes
|
|
349
|
+
postGovernmentBoardItemsItemIdVotes: (args) => request("POST", "/government/board/items/{itemId}/votes", { hasBody: true }, args),
|
|
348
350
|
// GET /government/permissions — rank: archon — GET /government/permissions
|
|
349
351
|
getGovernmentPermissions: (args) => request("GET", "/government/permissions", { hasBody: false }, args),
|
|
350
352
|
// GET /government/ranks — rank: archon — GET /government/ranks
|
package/docs/api/openapi.json
CHANGED
|
@@ -5336,6 +5336,65 @@
|
|
|
5336
5336
|
]
|
|
5337
5337
|
}
|
|
5338
5338
|
},
|
|
5339
|
+
"/government/board/items/{itemId}/votes": {
|
|
5340
|
+
"post": {
|
|
5341
|
+
"operationId": "post_government_board_items_itemId_votes",
|
|
5342
|
+
"tags": [
|
|
5343
|
+
"government"
|
|
5344
|
+
],
|
|
5345
|
+
"summary": "POST /government/board/items/:itemId/votes",
|
|
5346
|
+
"description": "board.vote.cast — cast a vote on an open board item (R08 of goal 1000069, ADR 0175). The atom FLOORS AT XENOS deliberately (the R04 carry- note): the permission is only the coarse gate, and the real wall is the in-handler membership re-check against the constitution SNAPSHOTTED on the item — do not \"tighten\" the floor to archon; that would fork authority truth and make widening the franchise a migration instead of a config change. Open ballot, anti-self, idempotent (UNIQUE(item_id, voter_id)), voter karma +1 on a fresh in-window cast. The policy lives in board.js castVote; this handler is the HTTP mapping.\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
|
+
"parameters": [
|
|
5353
|
+
{
|
|
5354
|
+
"name": "itemId",
|
|
5355
|
+
"in": "path",
|
|
5356
|
+
"required": true,
|
|
5357
|
+
"schema": {
|
|
5358
|
+
"type": "string"
|
|
5359
|
+
},
|
|
5360
|
+
"description": "Path parameter `itemId`."
|
|
5361
|
+
}
|
|
5362
|
+
],
|
|
5363
|
+
"requestBody": {
|
|
5364
|
+
"required": true,
|
|
5365
|
+
"content": {
|
|
5366
|
+
"application/json": {
|
|
5367
|
+
"schema": {
|
|
5368
|
+
"$ref": "#/components/schemas/PostGovernmentBoardItemsItemIdVotesRequest"
|
|
5369
|
+
}
|
|
5370
|
+
}
|
|
5371
|
+
},
|
|
5372
|
+
"x-validated": true
|
|
5373
|
+
},
|
|
5374
|
+
"responses": {
|
|
5375
|
+
"200": {
|
|
5376
|
+
"description": "Success."
|
|
5377
|
+
},
|
|
5378
|
+
"400": {
|
|
5379
|
+
"$ref": "#/components/responses/ValidationFailed"
|
|
5380
|
+
},
|
|
5381
|
+
"401": {
|
|
5382
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
5383
|
+
},
|
|
5384
|
+
"403": {
|
|
5385
|
+
"$ref": "#/components/responses/Forbidden"
|
|
5386
|
+
},
|
|
5387
|
+
"404": {
|
|
5388
|
+
"$ref": "#/components/responses/NotFound"
|
|
5389
|
+
}
|
|
5390
|
+
},
|
|
5391
|
+
"security": [
|
|
5392
|
+
{
|
|
5393
|
+
"builderSession": []
|
|
5394
|
+
}
|
|
5395
|
+
]
|
|
5396
|
+
}
|
|
5397
|
+
},
|
|
5339
5398
|
"/government/permissions": {
|
|
5340
5399
|
"get": {
|
|
5341
5400
|
"operationId": "get_government_permissions",
|
|
@@ -15962,6 +16021,27 @@
|
|
|
15962
16021
|
],
|
|
15963
16022
|
"additionalProperties": false
|
|
15964
16023
|
},
|
|
16024
|
+
"PostGovernmentBoardItemsItemIdVotesRequest": {
|
|
16025
|
+
"type": "object",
|
|
16026
|
+
"properties": {
|
|
16027
|
+
"direction": {
|
|
16028
|
+
"type": "string",
|
|
16029
|
+
"maxLength": 3
|
|
16030
|
+
},
|
|
16031
|
+
"reason_md": {
|
|
16032
|
+
"type": "string",
|
|
16033
|
+
"maxLength": 5000
|
|
16034
|
+
},
|
|
16035
|
+
"section_key": {
|
|
16036
|
+
"type": "string",
|
|
16037
|
+
"maxLength": 32
|
|
16038
|
+
}
|
|
16039
|
+
},
|
|
16040
|
+
"required": [
|
|
16041
|
+
"direction"
|
|
16042
|
+
],
|
|
16043
|
+
"additionalProperties": false
|
|
16044
|
+
},
|
|
15965
16045
|
"PostGovernmentRanksRankKeyResetResponse": {
|
|
15966
16046
|
"type": "object",
|
|
15967
16047
|
"properties": {
|
|
@@ -17729,8 +17809,8 @@
|
|
|
17729
17809
|
"description": "A required dependency/feature is not configured or is temporarily down."
|
|
17730
17810
|
}
|
|
17731
17811
|
},
|
|
17732
|
-
"x-endpoint-count":
|
|
17733
|
-
"x-schema-count":
|
|
17812
|
+
"x-endpoint-count": 281,
|
|
17813
|
+
"x-schema-count": 318,
|
|
17734
17814
|
"x-undocumented-bodies": 8,
|
|
17735
17815
|
"x-response-schemas": 214,
|
|
17736
17816
|
"x-generated-by": "scripts/gds/gen-api-docs.js"
|
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. 281 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,13 +216,14 @@ 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` (10)
|
|
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
|
+
| 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). |
|
|
226
227
|
| 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. |
|
|
227
228
|
| GET | `/api/bongos/government/ranks` | `archon` | — | government.manage — the full rank list with each rank's grants. |
|
|
228
229
|
| 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). |
|
|
@@ -348,6 +348,8 @@ 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
|
+
// POST /government/board/items/{itemId}/votes — rank: any-builder — POST /government/board/items/:itemId/votes
|
|
352
|
+
postGovernmentBoardItemsItemIdVotes: (args) => request("POST", "/government/board/items/{itemId}/votes", { hasBody: true }, args),
|
|
351
353
|
// GET /government/permissions — rank: archon — GET /government/permissions
|
|
352
354
|
getGovernmentPermissions: (args) => request("GET", "/government/permissions", { hasBody: false }, args),
|
|
353
355
|
// GET /government/ranks — rank: archon — GET /government/ranks
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/government/board.js — the Board Room's OPEN half: a ratification
|
|
4
|
+
// window opens automatically when a Full Idea passes the completeness grade
|
|
5
|
+
// (BV1.R07 of goal 1000069, ADR 0175 §5).
|
|
6
|
+
//
|
|
7
|
+
// THE GRADE IS THE FLOOR, THE VOTE IS THE MERIT. The deterministic completeness
|
|
8
|
+
// scorer (modules/ideas/completeness.js) asks "is the thought fully expressed";
|
|
9
|
+
// the board asks "is it worth doing". Board time is never spent on what a free
|
|
10
|
+
// deterministic check can bounce, so a sub-bar idea NEVER reaches the board —
|
|
11
|
+
// and that gate is enforced HERE, inside the module that owns the board, not by
|
|
12
|
+
// trusting callers to pre-filter. The ideas module fires this through the
|
|
13
|
+
// `government` kernel port and passes the stored score; this file decides.
|
|
14
|
+
//
|
|
15
|
+
// THE BAR IS THE SAME ONE THE IDEATOR CREDIT LANE USES (ADR 0175 §5 pins it to
|
|
16
|
+
// economy's IDEA_FULL_PASS_BAR = 60). It is declared here as this module's own
|
|
17
|
+
// constant rather than imported — a module must not require() another module
|
|
18
|
+
// (ADR 0083); the two constants are held equal by tests/government_board_window
|
|
19
|
+
// .mjs instead, the same no-second-list-that-drifts discipline the seed tests
|
|
20
|
+
// use. If the bar ever becomes per-instance policy, both modules should read it
|
|
21
|
+
// from ONE config surface in the same change.
|
|
22
|
+
//
|
|
23
|
+
// AUTOMATIC MEANS CONCURRENT. A window opens the moment an idea passes (owner
|
|
24
|
+
// decision 10), so overlapping windows are normal, expected, and each carries
|
|
25
|
+
// its own clock. What cannot exist is two OPEN windows for the same subject —
|
|
26
|
+
// government_003's partial unique index makes that structural, and the INSERT
|
|
27
|
+
// below leans on it (ON CONFLICT DO NOTHING against the partial index), so a
|
|
28
|
+
// re-grade, a resubmission race, or a double-fired hook opens nothing twice.
|
|
29
|
+
//
|
|
30
|
+
// THE CONSTITUTION IS SNAPSHOTTED ON THE ITEM. The rule in force when a window
|
|
31
|
+
// opens is what decides it (government_003's `constitution` column): an
|
|
32
|
+
// amendment ratified mid-sitting must not rewrite how an already-open item
|
|
33
|
+
// closes. The snapshot is R05's SANITIZED board block — loadGovernmentConfig
|
|
34
|
+
// runs the fail-closed sanitizer, so a malformed config file snapshots as the
|
|
35
|
+
// strict default (monarchy), never as a wider board.
|
|
36
|
+
//
|
|
37
|
+
// The config read is two synchronous file reads. config.js says boot-time, not
|
|
38
|
+
// per request — and window-opening is the rare event (a Full Idea capture, a
|
|
39
|
+
// few a day at most), not a hot path; reading LIVE is what makes an amendment
|
|
40
|
+
// take effect on the very next window without a process restart, the same
|
|
41
|
+
// instant-effect posture the resolver takes (ADR 0016).
|
|
42
|
+
|
|
43
|
+
const db = require('./db');
|
|
44
|
+
const { loadGovernmentConfig } = require('./config');
|
|
45
|
+
const { isBoardMember } = require('./board-membership');
|
|
46
|
+
|
|
47
|
+
// ADR 0175 §5: the completeness bar a Full Idea must clear to reach the board.
|
|
48
|
+
// Held equal to economy's IDEA_FULL_PASS_BAR by test — see the header.
|
|
49
|
+
const FULL_IDEA_PASS_BAR = 60;
|
|
50
|
+
|
|
51
|
+
// Pure: does this stored completeness score clear the bar? Fail-closed — a
|
|
52
|
+
// null score ("not scored" and "could not be scored" deliberately look the
|
|
53
|
+
// same, R142), a non-number, or NaN never reaches the board.
|
|
54
|
+
function ideaPassesBar(score) {
|
|
55
|
+
const n = Number(score);
|
|
56
|
+
return score !== null && score !== undefined && Number.isFinite(n) && n >= FULL_IDEA_PASS_BAR;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The open entry point the ideas capture path fires (through the `government`
|
|
60
|
+
// port, fail-open on its side). Returns, never throws into the capture path:
|
|
61
|
+
// { opened: true, item } — a window is now open
|
|
62
|
+
// { opened: false, reason: 'below_bar' } — sub-bar; R13 returns it instead
|
|
63
|
+
// { opened: false, reason: 'already_open', item } — this subject already sits
|
|
64
|
+
// { opened: false, reason: 'bad_subject' } — unidentifiable idea id
|
|
65
|
+
async function openWindowForFullIdea({ ideaId, completenessScore } = {}) {
|
|
66
|
+
const id = Number(ideaId);
|
|
67
|
+
if (!Number.isFinite(id) || id <= 0) return { opened: false, reason: 'bad_subject' };
|
|
68
|
+
if (!ideaPassesBar(completenessScore)) return { opened: false, reason: 'below_bar' };
|
|
69
|
+
const { board } = loadGovernmentConfig();
|
|
70
|
+
const item = await db.openBoardItem({
|
|
71
|
+
subjectType: 'full_idea',
|
|
72
|
+
subjectId: id,
|
|
73
|
+
openedBy: null, // the grade opened this, nobody did (government_003's design)
|
|
74
|
+
constitution: board,
|
|
75
|
+
windowMinutes: board.window_minutes,
|
|
76
|
+
});
|
|
77
|
+
if (!item) return { opened: false, reason: 'already_open', item: await db.getOpenBoardItem('full_idea', id) };
|
|
78
|
+
return { opened: true, item };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ── the vote (BV1.R08 of goal 1000069, ADR 0175 §§4/6/9) ────────────────────
|
|
82
|
+
//
|
|
83
|
+
// OPEN BALLOT, ONE VOTE, NO EDITING. A vote is member + direction + timestamp,
|
|
84
|
+
// visible to everyone (§9 — a board whose votes are invisible constrains
|
|
85
|
+
// nobody). One vote per member per item is government_003's UNIQUE(item_id,
|
|
86
|
+
// voter_id); casting again returns the existing vote rather than erroring,
|
|
87
|
+
// which is what "idempotent" means here — a double-click or a retried request
|
|
88
|
+
// is one vote, and there is no vote-changing in v1.
|
|
89
|
+
//
|
|
90
|
+
// THE FLOOR IS COARSE, MEMBERSHIP IS THE GATE — and that split is deliberate
|
|
91
|
+
// (the R04 carry-note): `board.vote.cast` floors at XENOS so that widening the
|
|
92
|
+
// franchise is a CONFIG change, never a grant migration. The real wall is the
|
|
93
|
+
// in-handler membership re-check against the constitution SNAPSHOTTED ON THE
|
|
94
|
+
// ITEM — per request, uncached (ADR 0016), so a demotion between open and vote
|
|
95
|
+
// refuses the vote, and an amendment mid-sitting cannot change who may vote on
|
|
96
|
+
// an already-open item.
|
|
97
|
+
//
|
|
98
|
+
// ANTI-SELF IS A HARD SERVER-SIDE RULE (the spec's bright line: it is what
|
|
99
|
+
// keeps ratification from being ceremonial). The author of a full_idea — and
|
|
100
|
+
// the proposer of an amendment — may not vote on their own item, mirroring the
|
|
101
|
+
// peer_votes guard.
|
|
102
|
+
//
|
|
103
|
+
// AN OBJECTION MUST BE ACTIONABLE (§4, criterion C2): a 'no' carries free text
|
|
104
|
+
// AND exactly one of the five Full Idea section keys; a 'yes' carries neither.
|
|
105
|
+
// The DB CHECK enforces it structurally; the validation here exists to return
|
|
106
|
+
// a NAMED refusal instead of a constraint error. The section keys mirror the
|
|
107
|
+
// government_003 CHECK, which mirrors modules/ideas/templates.js — a module
|
|
108
|
+
// may not require a sibling (ADR 0083), so the three lists are held equal by
|
|
109
|
+
// tests/government_board_vote.mjs, the same discipline as the pass bar above.
|
|
110
|
+
const FULL_IDEA_SECTION_KEYS = Object.freeze(['purpose', 'big_picture', 'why_had', 'why_matters', 'fit']);
|
|
111
|
+
|
|
112
|
+
// Author karma on RATIFICATION: +3, mirroring the criterion-proposal ratify
|
|
113
|
+
// award (modules/ideas/inbox.js KARMA_RATIFY_DELTA — tuned down from +5 in
|
|
114
|
+
// V3.R28; the +5 still printed in planning-session/SKILL.md and openapi.json is
|
|
115
|
+
// STALE, do not copy it). Ratification is the CLOSE outcome, so R10's tally is
|
|
116
|
+
// what fires the payout — this file owns the constant and the payout so the
|
|
117
|
+
// close path cannot invent its own number. Held equal to the ideas constant by
|
|
118
|
+
// tests/government_board_vote.mjs (ADR 0083 forbids the sibling import — the
|
|
119
|
+
// same held-equal discipline as the pass bar and the section keys).
|
|
120
|
+
const AUTHOR_RATIFY_KARMA_DELTA = 3;
|
|
121
|
+
|
|
122
|
+
// Pay the item's author their ratification karma. IDEMPOTENT BY REASON KEY —
|
|
123
|
+
// `board.item.ratified:<itemId>` is unique per item and the INSERT is a single
|
|
124
|
+
// WHERE-NOT-EXISTS statement (the economy credit-lane idiom), so R10's close,
|
|
125
|
+
// a re-driven close sweep, or a concurrent double-close can never double-pay.
|
|
126
|
+
// A subject with no author (an auto-opened item whose idea row is gone) pays
|
|
127
|
+
// nobody, silently — there is no one to pay, not an error.
|
|
128
|
+
async function awardAuthorRatifyKarma(item) {
|
|
129
|
+
const authorId = await db.getBoardItemAuthor(item);
|
|
130
|
+
if (authorId == null) return { paid: false, reason: 'no_author' };
|
|
131
|
+
const fresh = await db.insertAuthorRatifyKarma({
|
|
132
|
+
authorId, itemId: item.id, delta: AUTHOR_RATIFY_KARMA_DELTA,
|
|
133
|
+
});
|
|
134
|
+
return fresh ? { paid: true, karma_delta: AUTHOR_RATIFY_KARMA_DELTA } : { paid: false, reason: 'already_paid' };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Voter karma: +1 per in-window vote (owner decision 8 — accepted with the
|
|
138
|
+
// yes-clicking bias named; the item volume is too low to farm). Booked only on
|
|
139
|
+
// a FRESH insert, so the idempotent re-cast can never double-pay.
|
|
140
|
+
const VOTE_KARMA_DELTA = 1;
|
|
141
|
+
|
|
142
|
+
// Pure: validate a vote payload into a normalized shape, or name the refusal.
|
|
143
|
+
function validateVote({ direction, reasonMd, sectionKey } = {}) {
|
|
144
|
+
if (direction !== 'yes' && direction !== 'no') {
|
|
145
|
+
return { ok: false, reason: 'bad_direction' };
|
|
146
|
+
}
|
|
147
|
+
if (direction === 'yes') {
|
|
148
|
+
if (reasonMd != null || sectionKey != null) return { ok: false, reason: 'yes_carries_nothing' };
|
|
149
|
+
return { ok: true, direction, reasonMd: null, sectionKey: null };
|
|
150
|
+
}
|
|
151
|
+
const reason = typeof reasonMd === 'string' ? reasonMd.trim() : '';
|
|
152
|
+
if (!reason) return { ok: false, reason: 'objection_needs_reason' };
|
|
153
|
+
if (!FULL_IDEA_SECTION_KEYS.includes(sectionKey)) return { ok: false, reason: 'objection_needs_section' };
|
|
154
|
+
return { ok: true, direction, reasonMd: reason, sectionKey };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Cast a vote on an open item. Returns a typed result, never throws for a
|
|
158
|
+
// policy refusal — the route maps each to its status:
|
|
159
|
+
// { ok: true, vote, fresh: true, karma_delta } — recorded now
|
|
160
|
+
// { ok: true, vote, fresh: false } — idempotent re-cast
|
|
161
|
+
// { ok: false, reason: 'item_not_found' | 'item_closed' | 'vote_window_expired'
|
|
162
|
+
// | 'not_board_member' | 'anti_self_vote' | <validateVote reason> }
|
|
163
|
+
async function castVote({ itemId, voterId, direction, reasonMd = null, sectionKey = null } = {}) {
|
|
164
|
+
const id = Number(itemId);
|
|
165
|
+
if (!Number.isFinite(id) || id <= 0) return { ok: false, reason: 'item_not_found' };
|
|
166
|
+
const v = validateVote({ direction, reasonMd, sectionKey });
|
|
167
|
+
if (!v.ok) return v;
|
|
168
|
+
|
|
169
|
+
const item = await db.getBoardItemForVote(id);
|
|
170
|
+
if (!item) return { ok: false, reason: 'item_not_found' };
|
|
171
|
+
if (item.closed_at) return { ok: false, reason: 'item_closed' };
|
|
172
|
+
// The clock is checked here as well as by the R11 poller: a vote landing in
|
|
173
|
+
// the gap between expiry and the sweep would be a vote after time. The
|
|
174
|
+
// comparison ran on the DATABASE clock (getBoardItemForVote computes it).
|
|
175
|
+
if (item.expired) return { ok: false, reason: 'vote_window_expired' };
|
|
176
|
+
|
|
177
|
+
// The membership re-check runs against the item's SNAPSHOT, live rows,
|
|
178
|
+
// uncached — the fourth entry in the resolver family's ADR 0016 ledger.
|
|
179
|
+
const constitution = item.constitution || {};
|
|
180
|
+
if (!(await isBoardMember(voterId, constitution))) return { ok: false, reason: 'not_board_member' };
|
|
181
|
+
|
|
182
|
+
const authorId = await db.getBoardItemAuthor(item);
|
|
183
|
+
if (authorId != null && String(authorId) === String(voterId)) {
|
|
184
|
+
return { ok: false, reason: 'anti_self_vote' };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const inserted = await db.insertBoardVote({
|
|
188
|
+
itemId: id, voterId, direction: v.direction, reasonMd: v.reasonMd, sectionKey: v.sectionKey,
|
|
189
|
+
});
|
|
190
|
+
if (!inserted) {
|
|
191
|
+
// UNIQUE(item_id, voter_id) — the idempotent path. Return what stands.
|
|
192
|
+
const existing = await db.getBoardVote(id, voterId);
|
|
193
|
+
return { ok: true, vote: existing, fresh: false };
|
|
194
|
+
}
|
|
195
|
+
// Karma rides the FRESH insert only, and the whole route refused a closed or
|
|
196
|
+
// expired item above, so every paid vote is by construction in-window.
|
|
197
|
+
await db.insertVoteKarma({ voterId, itemId: id, delta: VOTE_KARMA_DELTA });
|
|
198
|
+
return { ok: true, vote: inserted, fresh: true, karma_delta: VOTE_KARMA_DELTA };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = {
|
|
202
|
+
FULL_IDEA_PASS_BAR,
|
|
203
|
+
ideaPassesBar,
|
|
204
|
+
openWindowForFullIdea,
|
|
205
|
+
FULL_IDEA_SECTION_KEYS,
|
|
206
|
+
VOTE_KARMA_DELTA,
|
|
207
|
+
AUTHOR_RATIFY_KARMA_DELTA,
|
|
208
|
+
awardAuthorRatifyKarma,
|
|
209
|
+
validateVote,
|
|
210
|
+
castVote,
|
|
211
|
+
};
|