@bongos/core 1.19.936 → 1.19.937

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.936",
6
- "core_contract": "1.19.936",
7
- "source_commit": "c584621a2bdc28b094881d2a51384398c3062cfb",
5
+ "core_version": "1.19.937",
6
+ "core_contract": "1.19.937",
7
+ "source_commit": "2aee2eb9e23c44abf42a24b09f9603555ba33673",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-21T04:31:32.976Z",
9
+ "built_at": "2026-09-21T05:57:31.197Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 511,
13
13
  "agent_docs_stubbed": 26,
14
- "functional_verbatim": 2329,
14
+ "functional_verbatim": 2334,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2866,
20
- "tree_sha256": "04c64a49cb815692ebbc6ee7382d47e4a296616b707d8e566e0894bf52364655",
19
+ "file_count": 2871,
20
+ "tree_sha256": "026277292d3a56e019a060c711325a482164c64ae62daa9c92ee820321bd0a81",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -347,12 +347,12 @@
347
347
  {
348
348
  "path": "clients/bongos-client/README.md",
349
349
  "mode": "0000644",
350
- "sha256": "56d3e08ff4e4893d4d6b30c9292f2c1b0730deb2bff99ea64d7d5df7971995c1"
350
+ "sha256": "ce2ce4bd71f9ea04c46e8c1f4e7173a941458162be0e3b32759aa0c5f9d97297"
351
351
  },
352
352
  {
353
353
  "path": "clients/bongos-client/bongos-client.global.js",
354
354
  "mode": "0000644",
355
- "sha256": "7cfab2d88dc9d36fc57334db3b43c0c31b3656e2bff7ebff11d0c18b12a61850"
355
+ "sha256": "47d36e3b0c228706df44c4a91d5b22bd6005a345c95d8ddda2650bf9b8170242"
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": "8a5ed1946540c3adb30ff986f1845bdc65b5a6e441698723f8c31eb8e1374ef2"
365
+ "sha256": "5368fb77dc06a2ea22ab2db87ec3ac89a9857388bc12117c31dc327e598f63a1"
366
366
  },
367
367
  {
368
368
  "path": "clients/bongos-client/index.d.ts",
369
369
  "mode": "0000644",
370
- "sha256": "61b51506067b743fef5b475bf3b61914054d18474c0f8368d0bc4520a591ab6d"
370
+ "sha256": "5d076131337103c34e8b0e46cd613ae898dfa9edeffa6104617f046eb4f651b0"
371
371
  },
372
372
  {
373
373
  "path": "clients/bongos-client/index.mjs",
374
374
  "mode": "0000644",
375
- "sha256": "7676427e266c9528793e47c3c9431c329b67f0b29edff522724f430b4c29a37f"
375
+ "sha256": "2e7178e059cb520cf0509a46151624bb00160cc2cd263582015980576b98482d"
376
376
  },
377
377
  {
378
378
  "path": "clients/bongos-client/package.json",
@@ -2112,12 +2112,12 @@
2112
2112
  {
2113
2113
  "path": "docs/api-reference.md",
2114
2114
  "mode": "0000644",
2115
- "sha256": "eb6f7b98ac6b54bdfb35adf224c47de318d5c07aa064276d98bb023633a79ded"
2115
+ "sha256": "d72a950df3fa37b7834a167d60e621434a0d0806f1a3f298b079d0538254be50"
2116
2116
  },
2117
2117
  {
2118
2118
  "path": "docs/api/openapi.json",
2119
2119
  "mode": "0000644",
2120
- "sha256": "257966d20d4076a5b3e96e95f1966971a7df6a3218212ee57e980b9adc89a83f"
2120
+ "sha256": "f89f852f211ffee8ccc13f4331fc867837eae6b6ac2a0a24292abc4537aa9591"
2121
2121
  },
2122
2122
  {
2123
2123
  "path": "docs/architecture.md",
@@ -2677,7 +2677,7 @@
2677
2677
  {
2678
2678
  "path": "docs/module-api-changelog.md",
2679
2679
  "mode": "0000644",
2680
- "sha256": "cce34badfbc776279489f414db3459706aeb2433726a0d4b1e9d7dd84c5f4731"
2680
+ "sha256": "3fddd5b37bb0a00a12bf88b81992eb2510e0115b8cc7f67c8fd67c51b65ae678"
2681
2681
  },
2682
2682
  {
2683
2683
  "path": "docs/modules-contract.md",
@@ -4317,7 +4317,7 @@
4317
4317
  {
4318
4318
  "path": "modules/government/board.js",
4319
4319
  "mode": "0000644",
4320
- "sha256": "22b5c7a2f2b41faa222281e1ad9acb1aebed074b065de892c7550f628900d6c2"
4320
+ "sha256": "cce76edcf96a8ece8bb3321f5edb505e0e93acae96babe7169da607fd1888f3b"
4321
4321
  },
4322
4322
  {
4323
4323
  "path": "modules/government/catalog.js",
@@ -4332,7 +4332,7 @@
4332
4332
  {
4333
4333
  "path": "modules/government/db.js",
4334
4334
  "mode": "0000644",
4335
- "sha256": "10363058d52e097ac4e0f8cdb3957062d446840f42c2ca65ef95b541b6e27aad"
4335
+ "sha256": "dc56a5747bb5275ba372dc297cce585219c649984778a924ce22bab7249889a8"
4336
4336
  },
4337
4337
  {
4338
4338
  "path": "modules/government/membership-predicate.js",
@@ -4459,6 +4459,11 @@
4459
4459
  "mode": "0000644",
4460
4460
  "sha256": "7603087ac279d05eb2c69527557035c72c3bbb1510c16fdb2230b2e377f0df74"
4461
4461
  },
4462
+ {
4463
+ "path": "modules/government/migrations/government_018_passed_items_index.sql",
4464
+ "mode": "0000644",
4465
+ "sha256": "c85b1615c66ae68b630c727faa6ab56358a35dea37a0c2da507d11d3909176fd"
4466
+ },
4462
4467
  {
4463
4468
  "path": "modules/government/module.json",
4464
4469
  "mode": "0000644",
@@ -4492,7 +4497,7 @@
4492
4497
  {
4493
4498
  "path": "modules/government/routes/government.js",
4494
4499
  "mode": "0000644",
4495
- "sha256": "6c38766b7b2a76a5fc035ad4eefce41fe45739730e70fb26b40546bded7b66b9"
4500
+ "sha256": "40c2cd95481690b88a065afad62636ae732845a5dc81f082ebd623f7fb060de4"
4496
4501
  },
4497
4502
  {
4498
4503
  "path": "modules/government/routes/rank-sync.js",
@@ -5462,7 +5467,7 @@
5462
5467
  {
5463
5468
  "path": "modules/ideas/inbox.js",
5464
5469
  "mode": "0000644",
5465
- "sha256": "b56a305c8c3b269e6a273b8f1c56da745bc91a57b1028f5c1d2ee33735e3c68d"
5470
+ "sha256": "da8ac68f553980fef162b7d9fbc85481fd60cd885954cf316894a132f845ef6c"
5466
5471
  },
5467
5472
  {
5468
5473
  "path": "modules/ideas/landing-preview.js",
@@ -5514,10 +5519,15 @@
5514
5519
  "mode": "0000644",
5515
5520
  "sha256": "53c66f25e685931e73334d6b904f7d3c0f3838a15f160bd5387864ef27836aa7"
5516
5521
  },
5522
+ {
5523
+ "path": "modules/ideas/migrations/ideas_010_goal_ratification.sql",
5524
+ "mode": "0000644",
5525
+ "sha256": "7ba109e2f5415daec8b0767ecfabb110fc02d8944f4e6cc0664ca2d205e05480"
5526
+ },
5517
5527
  {
5518
5528
  "path": "modules/ideas/module.json",
5519
5529
  "mode": "0000644",
5520
- "sha256": "c23099e7c3a4768dc68778292c42988ee21234ed81d1832347e9fd272f6137d9"
5530
+ "sha256": "a869f9dec25e003006a495b3fbee45ac99c80b306cec7f16808693d70133e717"
5521
5531
  },
5522
5532
  {
5523
5533
  "path": "modules/ideas/pollers/descent-writer.js",
@@ -5532,7 +5542,7 @@
5532
5542
  {
5533
5543
  "path": "modules/ideas/ratify.js",
5534
5544
  "mode": "0000644",
5535
- "sha256": "2988a93940cd1eba73fb0c9e04f8326108466ba81901f86b3768b29115f17237"
5545
+ "sha256": "05789550a8e6345f27ac78ccef3ac93a7837e99a6b067871189b1e983b4d374f"
5536
5546
  },
5537
5547
  {
5538
5548
  "path": "modules/ideas/reads.js",
@@ -5549,6 +5559,11 @@
5549
5559
  "mode": "0000644",
5550
5560
  "sha256": "da91fe0641109b3b8adc0cca44480337f15cbd481b590f42d022e9eaa78654b7"
5551
5561
  },
5562
+ {
5563
+ "path": "modules/ideas/routes/ratify-goal.js",
5564
+ "mode": "0000644",
5565
+ "sha256": "92c10dbffef40038dac64ea646372e3fcd1c70c5d2bda1d731ddbdcd508d9181"
5566
+ },
5552
5567
  {
5553
5568
  "path": "modules/ideas/routing.js",
5554
5569
  "mode": "0000644",
@@ -5562,7 +5577,7 @@
5562
5577
  {
5563
5578
  "path": "modules/ideas/templates.js",
5564
5579
  "mode": "0000644",
5565
- "sha256": "deef66d514a2ea090274acc05e85ffefdfd6c0bf0279b64f7bee16af7b2e1471"
5580
+ "sha256": "345cbf4d87be780ee98c33042d2ec914b40bceeab6122a67a00a3852580b681b"
5566
5581
  },
5567
5582
  {
5568
5583
  "path": "modules/ideas/trail-descent.js",
@@ -5769,6 +5784,11 @@
5769
5784
  "mode": "0000644",
5770
5785
  "sha256": "c22a0782ddf7a93deb40f700a2afab15a313925647bbdf424db777da5aa29aa1"
5771
5786
  },
5787
+ {
5788
+ "path": "modules/lifecycle/goal-from-idea.js",
5789
+ "mode": "0000644",
5790
+ "sha256": "21d091ef596bc67f08a4f0785d17eaddf47d2da702219ccd5af6164413944428"
5791
+ },
5772
5792
  {
5773
5793
  "path": "modules/lifecycle/goal-rollup.js",
5774
5794
  "mode": "0000644",
@@ -5802,7 +5822,7 @@
5802
5822
  {
5803
5823
  "path": "modules/lifecycle/lifecycle.js",
5804
5824
  "mode": "0000644",
5805
- "sha256": "41ef55efff7f334ee8b19490f03944ca25f22454c718dfb81445539bc6a77aa4"
5825
+ "sha256": "74e09c7d1a978da826f99cff9e863b1051ab19a6437ae398a1e83ec8293e794f"
5806
5826
  },
5807
5827
  {
5808
5828
  "path": "modules/lifecycle/merge-lock.js",
@@ -7982,12 +8002,12 @@
7982
8002
  {
7983
8003
  "path": "package-lock.json",
7984
8004
  "mode": "0000644",
7985
- "sha256": "50c742fdaa94ee84b79406b7654fc544f6c355c71e5664e6431785c8fbe882b3"
8005
+ "sha256": "f1999bdf0489eddf32aabd9a5d2aec6b82bd9765a598e0eff299c31a367b6ff6"
7986
8006
  },
7987
8007
  {
7988
8008
  "path": "package.json",
7989
8009
  "mode": "0000644",
7990
- "sha256": "62828bf612cefb41d1c28f7d331d711d2cc2ed5575f6ce506b410c007e47bccb"
8010
+ "sha256": "0be6855a6ff73d2592bff19899727f708abd44265f2ea4538cc48491982a5aac"
7991
8011
  },
7992
8012
  {
7993
8013
  "path": "public-docs/index.html",
@@ -9987,7 +10007,7 @@
9987
10007
  {
9988
10008
  "path": "src/module-api.js",
9989
10009
  "mode": "0000644",
9990
- "sha256": "ab8bbe4ee49dbf220c6a5550d22c38ef22ea41988b50d8774933e297fc93f496"
10010
+ "sha256": "546941331565d646b5b815a2a66c4833993abd3affcd2ba309ddad87191d766a"
9991
10011
  },
9992
10012
  {
9993
10013
  "path": "src/module-loader/catalog.js",
@@ -11687,7 +11707,7 @@
11687
11707
  {
11688
11708
  "path": "tests/government_seed.mjs",
11689
11709
  "mode": "0000644",
11690
- "sha256": "e31b024ce9a64a13737a32720ba7bef3d92f2644f56581143965afb1bb65f1fd"
11710
+ "sha256": "e165fe5c67ee4e80c58f489514ac77ba34e51984dabd2329ecdd70052b81a9cc"
11691
11711
  },
11692
11712
  {
11693
11713
  "path": "tests/government_session_scope.mjs",
@@ -12064,6 +12084,11 @@
12064
12084
  "mode": "0000644",
12065
12085
  "sha256": "63afe2afd59ff64e290234a0cc8d33a0f77b784d8c10c2d7147fe2ab3c114cd1"
12066
12086
  },
12087
+ {
12088
+ "path": "tests/idea_goal_ratification.mjs",
12089
+ "mode": "0000644",
12090
+ "sha256": "3c624db8e11ae02f503d19c6b2556b286d3ca59229daacd6ea2319782530c84f"
12091
+ },
12067
12092
  {
12068
12093
  "path": "tests/idea_governance_pass_credit.mjs",
12069
12094
  "mode": "0000644",
@@ -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
- - 403 operations across 61 resource groups
8
+ - 404 operations across 61 resource groups
9
9
 
10
10
  ## Use it from your project
11
11
 
@@ -511,6 +511,8 @@ function createClient(opts = {}) {
511
511
  postInboxIdDevelop: (args) => request("POST", "/inbox/{id}/develop", { hasBody: true }, args),
512
512
  // POST /inbox/{id}/ratify — rank: metic+archon — POST /inbox/:id/ratify
513
513
  postInboxIdRatify: (args) => request("POST", "/inbox/{id}/ratify", { hasBody: true }, args),
514
+ // POST /inbox/{id}/ratify-goal — rank: metic+archon — POST /inbox/:id/ratify-goal
515
+ postInboxIdRatifyGoal: (args) => request("POST", "/inbox/{id}/ratify-goal", { hasBody: true }, args),
514
516
  // POST /inbox/{id}/resubmit — rank: any-builder — POST /inbox/:id/resubmit
515
517
  postInboxIdResubmit: (args) => request("POST", "/inbox/{id}/resubmit", { hasBody: true }, args),
516
518
  // DELETE /inbox/{id}/spark — rank: any-builder — DELETE /inbox/:id/spark
@@ -510,6 +510,8 @@ function createClient(opts = {}) {
510
510
  postInboxIdDevelop: (args) => request("POST", "/inbox/{id}/develop", { hasBody: true }, args),
511
511
  // POST /inbox/{id}/ratify — rank: metic+archon — POST /inbox/:id/ratify
512
512
  postInboxIdRatify: (args) => request("POST", "/inbox/{id}/ratify", { hasBody: true }, args),
513
+ // POST /inbox/{id}/ratify-goal — rank: metic+archon — POST /inbox/:id/ratify-goal
514
+ postInboxIdRatifyGoal: (args) => request("POST", "/inbox/{id}/ratify-goal", { hasBody: true }, args),
513
515
  // POST /inbox/{id}/resubmit — rank: any-builder — POST /inbox/:id/resubmit
514
516
  postInboxIdResubmit: (args) => request("POST", "/inbox/{id}/resubmit", { hasBody: true }, args),
515
517
  // DELETE /inbox/{id}/spark — rank: any-builder — DELETE /inbox/:id/spark
@@ -323,6 +323,7 @@ export interface PostGovernmentRanksResponse { rank: unknown }
323
323
  export interface PostHelpRequestsRequest { needs_craft?: string; needs_builder_id?: number; what_is_stuck: string; task_id?: number; goal_id?: number }
324
324
  export interface PostHelpRequestsResponse { ok: boolean; help_request: unknown }
325
325
  export interface PostInboxIdDevelopRequest { field_values: Record<string, unknown>; template_key?: string; recommendation?: string }
326
+ export interface PostInboxIdRatifyGoalRequest { target_version: string; scope_modules?: string[]; admit_reason?: string; notes_md?: string }
326
327
  export interface PostInboxIdRatifyRequest { notes_md?: string }
327
328
  export interface PostInboxIdResubmitRequest { field_values: Record<string, unknown>; template_key?: string; recommendation?: string }
328
329
  export interface PostInboxIdSparkResponse { idea: unknown; sparked: boolean; unchanged: boolean }
@@ -886,6 +887,8 @@ export interface BongosClient {
886
887
  postInboxIdDevelop(args: RequestArgs & { body: PostInboxIdDevelopRequest }): Promise<ApiResponse>;
887
888
  /** POST /inbox/{id}/ratify — rank: metic+archon */
888
889
  postInboxIdRatify(args?: RequestArgs & { body?: PostInboxIdRatifyRequest }): Promise<ApiResponse>;
890
+ /** POST /inbox/{id}/ratify-goal — rank: metic+archon */
891
+ postInboxIdRatifyGoal(args: RequestArgs & { body: PostInboxIdRatifyGoalRequest }): Promise<ApiResponse>;
889
892
  /** POST /inbox/{id}/resubmit — rank: any-builder */
890
893
  postInboxIdResubmit(args: RequestArgs & { body: PostInboxIdResubmitRequest }): Promise<ApiResponse>;
891
894
  /** DELETE /inbox/{id}/spark — rank: any-builder */
@@ -507,6 +507,8 @@ export function createClient(opts = {}) {
507
507
  postInboxIdDevelop: (args) => request("POST", "/inbox/{id}/develop", { hasBody: true }, args),
508
508
  // POST /inbox/{id}/ratify — rank: metic+archon — POST /inbox/:id/ratify
509
509
  postInboxIdRatify: (args) => request("POST", "/inbox/{id}/ratify", { hasBody: true }, args),
510
+ // POST /inbox/{id}/ratify-goal — rank: metic+archon — POST /inbox/:id/ratify-goal
511
+ postInboxIdRatifyGoal: (args) => request("POST", "/inbox/{id}/ratify-goal", { hasBody: true }, args),
510
512
  // POST /inbox/{id}/resubmit — rank: any-builder — POST /inbox/:id/resubmit
511
513
  postInboxIdResubmit: (args) => request("POST", "/inbox/{id}/resubmit", { hasBody: true }, args),
512
514
  // DELETE /inbox/{id}/spark — rank: any-builder — DELETE /inbox/:id/spark
@@ -8979,6 +8979,65 @@
8979
8979
  ]
8980
8980
  }
8981
8981
  },
8982
+ "/inbox/{id}/ratify-goal": {
8983
+ "post": {
8984
+ "operationId": "post_inbox_id_ratify_goal",
8985
+ "tags": [
8986
+ "inbox"
8987
+ ],
8988
+ "summary": "POST /inbox/:id/ratify-goal",
8989
+ "description": "BV2.R17 (task 1004096, spec R23, goal 1000095) — ratify a FULL IDEA into a GOAL, atomically: - INSERT goals (title from the idea, description = its five answers) - INSERT done_when_criteria on that goal from the \"what does a successful implementation look like\" answer - UPDATE idea_inbox SET status='promoted', reviewed_at=now() - INSERT ideas_goal_ratifications — who ratified, under which board decision, with the author's own recommendation recorded beside it All of it in one transaction; a partial failure rolls back to an open idea. A SEPARATE ROUTE FROM /ratify, not a mode of it. The two take different subjects (a `criterion-proposal` idea whose body_md is JSON vs. a graded Full Idea whose answers are prose), produce different rows, and differ on whether the board has a say. One route with a discriminator would have to document which half of its own body applies, and a caller who got the discriminator wrong would be told about a field rather than about the act. THE SCOPE AND THE VERSION COME FROM THE REQUEST because nothing else can supply them: no prose answer says which version a goal belongs to or which modules it may touch. THE GATES DO NOT come from the request — the handler passes the caller's LIVE rank through to the lifecycle port, which applies every check POST /goals applies (unknown module key, protected scope → Archon, a building version's goal set closed without a named Archon admission). `idea.ratify`'s floor is Metic, so those gates are load-bearing here rather than ceremonial. Statuses: 400 a bad body / an unratifiable idea / an unmappable template · 403 a rank gate · 404 no such idea · 409 the idea is not open, the board has not passed it, the version will not take a goal, or it is already ratified.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `idea.ratify` (all required).",
8990
+ "x-rank": "metic+archon",
8991
+ "x-source": "modules/ideas/routes/ratify-goal.js",
8992
+ "x-permissions": [
8993
+ "idea.ratify"
8994
+ ],
8995
+ "parameters": [
8996
+ {
8997
+ "name": "id",
8998
+ "in": "path",
8999
+ "required": true,
9000
+ "schema": {
9001
+ "type": "string"
9002
+ },
9003
+ "description": "Path parameter `id`."
9004
+ }
9005
+ ],
9006
+ "requestBody": {
9007
+ "required": true,
9008
+ "content": {
9009
+ "application/json": {
9010
+ "schema": {
9011
+ "$ref": "#/components/schemas/PostInboxIdRatifyGoalRequest"
9012
+ }
9013
+ }
9014
+ },
9015
+ "x-validated": true
9016
+ },
9017
+ "responses": {
9018
+ "200": {
9019
+ "description": "Success."
9020
+ },
9021
+ "400": {
9022
+ "$ref": "#/components/responses/ValidationFailed"
9023
+ },
9024
+ "401": {
9025
+ "$ref": "#/components/responses/Unauthorized"
9026
+ },
9027
+ "403": {
9028
+ "$ref": "#/components/responses/Forbidden"
9029
+ },
9030
+ "404": {
9031
+ "$ref": "#/components/responses/NotFound"
9032
+ }
9033
+ },
9034
+ "security": [
9035
+ {
9036
+ "builderSession": []
9037
+ }
9038
+ ]
9039
+ }
9040
+ },
8982
9041
  "/inbox/{id}/resubmit": {
8983
9042
  "post": {
8984
9043
  "operationId": "post_inbox_id_resubmit",
@@ -23241,6 +23300,36 @@
23241
23300
  ],
23242
23301
  "additionalProperties": false
23243
23302
  },
23303
+ "PostInboxIdRatifyGoalRequest": {
23304
+ "type": "object",
23305
+ "properties": {
23306
+ "target_version": {
23307
+ "type": "string",
23308
+ "minLength": 1,
23309
+ "maxLength": 64
23310
+ },
23311
+ "scope_modules": {
23312
+ "type": "array",
23313
+ "items": {
23314
+ "type": "string"
23315
+ },
23316
+ "maxItems": 64
23317
+ },
23318
+ "admit_reason": {
23319
+ "type": "string",
23320
+ "minLength": 8,
23321
+ "maxLength": 500
23322
+ },
23323
+ "notes_md": {
23324
+ "type": "string",
23325
+ "maxLength": 10000
23326
+ }
23327
+ },
23328
+ "required": [
23329
+ "target_version"
23330
+ ],
23331
+ "additionalProperties": false
23332
+ },
23244
23333
  "PostInboxIdRatifyRequest": {
23245
23334
  "type": "object",
23246
23335
  "properties": {
@@ -25564,8 +25653,8 @@
25564
25653
  "description": "A required dependency/feature is not configured or is temporarily down."
25565
25654
  }
25566
25655
  },
25567
- "x-endpoint-count": 403,
25568
- "x-schema-count": 442,
25656
+ "x-endpoint-count": 404,
25657
+ "x-schema-count": 443,
25569
25658
  "x-undocumented-bodies": 9,
25570
25659
  "x-response-schemas": 301,
25571
25660
  "x-generated-by": "scripts/gds/gen-api-docs.js"
@@ -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. 403 endpoints across 65 route files.
5
+ > **Generated from the live route files** — the route file is authoritative. 404 endpoints across 66 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/0016-trust-boundary-server-enforced-permissions.md)): `public` < `any-builder` < `metic+archon` < `archon`.
@@ -337,7 +337,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
337
337
  | GET | `/api/bongos/help-requests/for-me` | `any-builder` | — | The addressed read — C01's answer to "and fires a notification". |
338
338
  | GET | `/api/bongos/help-requests/mine` | `any-builder` | — | your own asks, so you can settle them. |
339
339
 
340
- ## `inbox` (17)
340
+ ## `inbox` (18)
341
341
 
342
342
  | Method | Path | Rank | Body | Description |
343
343
  |---|---|---|---|---|
@@ -348,6 +348,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
348
348
  | PATCH | `/api/bongos/inbox/:id/body` | `any-builder` | `body_md` | Metic+ (task 360 / ADR 0018): idea triage (promote/discard/merge) shapes the backlog. |
349
349
  | POST | `/api/bongos/inbox/:id/develop` | `any-builder` | `field_values`, `template_key`, `recommendation` | POST /inbox/:id/develop — DEVELOP A SPARK INTO A FULL IDEA (task 1002922 / BV1.R146, goal 1000062). |
350
350
  | POST | `/api/bongos/inbox/:id/ratify` | `metic+archon` | `notes_md` | V3.R31 (task 252) — Archon ratifies a criterion-proposal idea, atomically: - INSERT done_when_criteria row on the target version - UPDATE… |
351
+ | POST | `/api/bongos/inbox/:id/ratify-goal` | `metic+archon` | `target_version`, `scope_modules`, `admit_reason`, `notes_md` | BV2.R17 (task 1004096, spec R23, goal 1000095) — ratify a FULL IDEA into a GOAL, atomically: - INSERT goals (title from the idea, descrip… |
351
352
  | POST | `/api/bongos/inbox/:id/resubmit` | `any-builder` | `field_values`, `template_key`, `recommendation` | RESUBMIT a Full Idea after a rejection (R13 of goal 1000069, criterion C2: an idea never dies and never stalls). |
352
353
  | POST | `/api/bongos/inbox/:id/spark` | `any-builder` | — | Mark a Quick Idea as a Big Idea Spark — "I have the spark but not the full picture, someone develop this". |
353
354
  | DELETE | `/api/bongos/inbox/:id/spark` | `any-builder` | — | Withdraw the Big Idea Spark mark — the reversibility half of R144. |
@@ -2346,5 +2346,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
2346
2346
  landed since 1.19.934 with no explicit bump. run 35558269194. (task 1002620)
2347
2347
  1.19.936 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2348
2348
  landed since 1.19.935 with no explicit bump. run 35561285958. (task 1002620)
2349
+ 1.19.937 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2350
+ landed since 1.19.936 with no explicit bump. run 35566446478. (task 1002620)
2349
2351
  ---------------------------------------------------------------------------
2350
2352
  ```
@@ -1041,6 +1041,36 @@ async function latestRejectionForIdea(ideaId) {
1041
1041
  });
1042
1042
  }
1043
1043
 
1044
+ // ── BV2.R17's read: did the board CARRY this idea? (task 1004096) ───────────
1045
+ //
1046
+ // The ratification half of the ideator surface turns a Full Idea into a goal,
1047
+ // and spec decision 9 is that the ideator RECOMMENDS while the board DECIDES.
1048
+ // The ideas module holds the author's recommendation on its own grade row, so
1049
+ // without this read "the board decides" would be a sentence in a comment: the
1050
+ // only fact stopping a ratification from being one person's opinion is that a
1051
+ // sitting actually closed `passed`.
1052
+ //
1053
+ // Null means "no passing decision on record", which the caller must treat as a
1054
+ // refusal and never as an absence it may work around. A returned item, an item
1055
+ // still open, and an idea that never reached the board all read the same here,
1056
+ // on purpose — none of them is a mandate, and distinguishing them would invite a
1057
+ // caller to accept one of them.
1058
+ //
1059
+ // Frozen, like latestRejectionForIdea, so a caller cannot decorate the record of
1060
+ // a decision and pass it on as though the board had said more than it did.
1061
+ async function boardPassForIdea(ideaId) {
1062
+ const id = Number(ideaId);
1063
+ if (!Number.isFinite(id) || id <= 0) return null;
1064
+ const row = await db.getLatestPassedItem('full_idea', id);
1065
+ if (!row) return null;
1066
+ return Object.freeze({
1067
+ item_id: row.item_id,
1068
+ outcome: 'passed',
1069
+ opened_at: row.opened_at,
1070
+ closed_at: row.closed_at,
1071
+ });
1072
+ }
1073
+
1044
1074
  // ── the summons: what is waiting on THIS member's vote (task 1003271) ───────
1045
1075
  //
1046
1076
  // THE PROBLEM THIS EXISTS TO FIX. Nine items sat open for two days with zero
@@ -1127,6 +1157,7 @@ module.exports = {
1127
1157
  closeItem,
1128
1158
  maybeCloseAfterVote,
1129
1159
  latestRejectionForIdea,
1160
+ boardPassForIdea,
1130
1161
  validateProposedConstitution,
1131
1162
  proposeAmendment,
1132
1163
  boardRoomView,
@@ -610,6 +610,38 @@ async function getLatestReturnedItemWithObjection(subjectType, subjectId) {
610
610
  return rows[0] || null;
611
611
  }
612
612
 
613
+ // BV2.R17's read (task 1004096): the most recent PASSED item for a subject.
614
+ //
615
+ // The mirror of getLatestReturnedItemWithObjection above, and the reason it
616
+ // exists separately rather than as a `outcome` parameter on that one: the
617
+ // returned read exists to carry an OBJECTION back to an author and joins the
618
+ // votes table to find it, while this one exists to answer a single yes/no —
619
+ // "did the board carry this?" — and must not depend on any vote row existing.
620
+ // A passed item commonly has NO votes at all (a zero-vote sitting passes under
621
+ // the absence rules, ADR 0175 §3), so the LEFT JOIN that is merely defensive
622
+ // there would be load-bearing here, which is a good sign the two are different
623
+ // questions.
624
+ //
625
+ // Ordering is closed_at DESC, id DESC: an idea returned once and resubmitted can
626
+ // have several closed items, and it is the latest decision that stands.
627
+ //
628
+ // Served by the partial index `government_board_items_passed_by_subject`
629
+ // (government_018), the mirror of government_006's index for the returned read.
630
+ // It needs its own: the table only grows, government_003's two indexes both key
631
+ // on OPEN items and government_008's keys on closed_at alone for the whole-board
632
+ // history feed, so none of them helps a lookup filtered by subject.
633
+ async function getLatestPassedItem(subjectType, subjectId) {
634
+ const { rows } = await pool.query(
635
+ `SELECT id AS item_id, closed_at, opened_at, constitution AS decided_under
636
+ FROM government_board_items
637
+ WHERE subject_type = $1 AND subject_id = $2 AND outcome = 'passed'
638
+ ORDER BY closed_at DESC, id DESC
639
+ LIMIT 1`,
640
+ [subjectType, subjectId],
641
+ );
642
+ return rows[0] || null;
643
+ }
644
+
613
645
  // The expiry sweep's read (R11): every OPEN item whose deadline has passed, on
614
646
  // the database clock, oldest deadline first. Served by the partial index
615
647
  // government_board_items_open_by_deadline (government_003 built it for exactly
@@ -728,6 +760,7 @@ module.exports = {
728
760
  getIdeaCompleteness,
729
761
  getExpiredOpenBoardItems,
730
762
  getLatestReturnedItemWithObjection,
763
+ getLatestPassedItem,
731
764
  createAmendment,
732
765
  getAmendment,
733
766
  listOpenBoardItems,
@@ -0,0 +1,31 @@
1
+ -- government_018_passed_items_index.sql — index the passed-item lookup
2
+ -- (task 1004096 / BV2.R17; a grader efficiency finding, and right — exactly as
3
+ -- government_006 was for the sibling read one outcome over).
4
+ --
5
+ -- `getLatestPassedItem` answers "did the board CARRY this subject?" and runs on
6
+ -- every Full-Idea→goal ratification. It is the mirror of
7
+ -- `getLatestReturnedItemWithObjection`, and it inherits that read's problem in
8
+ -- full: `government_board_items` only grows — one row per window, and a
9
+ -- returned idea can be resubmitted without bound — while the table's two
10
+ -- OPEN-item indexes (`government_003`) both key on `closed_at IS NULL` and the
11
+ -- one closed-item index (`government_008`) keys on `closed_at` alone, for the
12
+ -- whole-board history feed. None of them helps a lookup that filters by
13
+ -- subject. Without this, a ratification scans historical volume that scales
14
+ -- with every sitting the project has ever held.
15
+ --
16
+ -- Partial on outcome='passed' — the only outcome this read asks for — and keyed
17
+ -- (subject_type, subject_id, closed_at DESC) to match the query's WHERE and
18
+ -- ORDER BY exactly, the government_006 shape. Two narrow partial indexes rather
19
+ -- than one wider index over `outcome`: each read asks for exactly one outcome
20
+ -- and never ranges across them, so a shared index would be larger and no more
21
+ -- useful to either.
22
+ --
23
+ -- Additive, idempotent, no rows touched — safe on any database state.
24
+
25
+ BEGIN;
26
+
27
+ CREATE INDEX IF NOT EXISTS government_board_items_passed_by_subject
28
+ ON government_board_items (subject_type, subject_id, closed_at DESC)
29
+ WHERE outcome = 'passed';
30
+
31
+ COMMIT;
@@ -103,6 +103,13 @@ const governmentPort = {
103
103
  // resubmit path in modules/ideas resolves this to show what was objected to
104
104
  // and to check whether the flagged section actually changed.
105
105
  latestRejectionForIdea: board.latestRejectionForIdea,
106
+ // BV2.R17 (task 1004096): the record of the board having CARRIED an idea, or
107
+ // null. The ideas module's ratification path resolves this to check the one
108
+ // fact that makes turning a Full Idea into a goal a board decision rather
109
+ // than a ratifier's preference. Read-only, like every other entry here — there
110
+ // is no counterpart that records an outcome, and there must never be one: an
111
+ // outcome is written by a close, never by the module reading it.
112
+ boardPassForIdea: board.boardPassForIdea,
106
113
  };
107
114
 
108
115
  function registerSeams() {
@@ -848,4 +848,15 @@ module.exports = {
848
848
  parseProposalBody: ratify.parseProposalBody,
849
849
  KARMA_RATIFY_DELTA: ratify.KARMA_RATIFY_DELTA,
850
850
  KARMA_RATIFY_REASON: ratify.KARMA_RATIFY_REASON,
851
+ // Full-Idea → GOAL ratification, the goal half beside it (task 1004096 /
852
+ // BV2.R17); see ratify.js on why the two halves are not symmetrical.
853
+ //
854
+ // ONLY THE TRANSACTION IS RE-EXPORTED. `composeGoalFromFullIdea` and
855
+ // `GOAL_SHAPE_BY_TEMPLATE` are deliberately NOT on the facade: nothing outside
856
+ // ratify.js calls either, and a re-export no caller needs is an unused export
857
+ // the dead-code ratchet counts. The facade's job is to keep the ~20 callers of
858
+ // the pre-carve surface working, not to publish everything a new file declares
859
+ // — anything that wants the pure composer requires ./ratify directly, as the
860
+ // test does.
861
+ ratifyFullIdeaAsGoal: ratify.ratifyFullIdeaAsGoal,
851
862
  };