@bongos/core 1.19.963 → 1.19.965

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.
Files changed (40) hide show
  1. package/.bongos-core.json +70 -40
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +2 -0
  4. package/clients/bongos-client/index.cjs +2 -0
  5. package/clients/bongos-client/index.d.ts +2 -0
  6. package/clients/bongos-client/index.mjs +2 -0
  7. package/docs/adr/0244-a-catalog-search-is-a-view-over-the-rows-never-an-edit-to-the-selection.md +41 -0
  8. package/docs/adr/0326-an-upgrade-may-add-a-deny-rule-to-a-file-the-instance-owns.md +5 -0
  9. package/docs/adr/README.md +2 -2
  10. package/docs/api/openapi.json +47 -2
  11. package/docs/api-reference.md +3 -2
  12. package/docs/copy-inventory.md +134 -128
  13. package/docs/copy-registry.json +212 -158
  14. package/docs/module-api-changelog.md +4 -0
  15. package/modules/hall-ui/public/goal-forest-lib.js +229 -0
  16. package/modules/hall-ui/public/goal-forest-render.js +151 -0
  17. package/modules/hall-ui/public/goals-page.js +6 -0
  18. package/modules/hall-ui/public/goals-render.js +9 -1
  19. package/modules/hall-ui/public/goals.css +26 -0
  20. package/modules/hall-ui/public/goals.html +19 -0
  21. package/modules/lifecycle/goal-forest.js +135 -0
  22. package/modules/lifecycle/routes/goals.js +8 -8
  23. package/modules/provisioning/starter-bundles.js +26 -2
  24. package/modules/public-landing/public/projects.html +17 -4
  25. package/modules/ui-design/kit/serve.js +2 -2
  26. package/package-lock.json +2 -2
  27. package/package.json +1 -1
  28. package/scripts/gds/claude-materialize.js +29 -6
  29. package/scripts/hall-preview/server.js +1 -0
  30. package/src/module-api.js +1 -1
  31. package/tests/claude_materialize.mjs +48 -4
  32. package/tests/goal_category_ui.mjs +2 -0
  33. package/tests/goal_criterion_confirm_ui.mjs +2 -0
  34. package/tests/goal_forest.mjs +160 -0
  35. package/tests/goal_forest_lib.mjs +153 -0
  36. package/tests/goal_forest_page.mjs +196 -0
  37. package/tests/goal_invite_consent.mjs +1 -1
  38. package/tests/helpers.mjs +7 -3
  39. package/tests/projects_hub_module_picker.mjs +79 -4
  40. package/tests/provisioning_starter_bundles.mjs +25 -1
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.963",
6
- "core_contract": "1.19.963",
7
- "source_commit": "faa62c8ba55aa4e1fa7cf848ae917ed2c737dd8a",
5
+ "core_version": "1.19.965",
6
+ "core_contract": "1.19.965",
7
+ "source_commit": "bbaec9cc0649177ca52efa5afa2cea91b9fead9b",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-22T18:58:15.932Z",
9
+ "built_at": "2026-09-22T22:06:32.886Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 515,
13
13
  "agent_docs_stubbed": 26,
14
- "functional_verbatim": 2358,
14
+ "functional_verbatim": 2364,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2899,
20
- "tree_sha256": "cd552e778cb5f18f556a89d67c09fe6be5f78390a2473d72be88a34eea0c2a2d",
19
+ "file_count": 2905,
20
+ "tree_sha256": "686c7cbaf16a9143fa9e322b03a8c6dc0fe6d16fa76848b028507a7f528f295f",
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": "7d0b5d24baaf5f06e7fafe32a7c7cd1ea6c69746fb92c95a0a611ad776432cd8"
350
+ "sha256": "d1f7b1bdb4daf98ee7e0c11fd8bd51d202413fb8c1d2cf684d9389d58974b24f"
351
351
  },
352
352
  {
353
353
  "path": "clients/bongos-client/bongos-client.global.js",
354
354
  "mode": "0000644",
355
- "sha256": "71e5d4495bd293695bf42331bc20a6a3283fad8703663718209bd257953014f1"
355
+ "sha256": "c3e0241775c4cad76f8bc17edcc131bfdf649b6280b1ae9559bccf863a8b2658"
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": "525d496a716e92467593447bcbb71369d6bdb817605631023d9b081cd75ce4c1"
365
+ "sha256": "7a788085abb4096f48136d5908affd272da6c0b4129ed4de073b0934451e3028"
366
366
  },
367
367
  {
368
368
  "path": "clients/bongos-client/index.d.ts",
369
369
  "mode": "0000644",
370
- "sha256": "f134e3ba8380879dae51531cd0867bbda06c5229a5d0dd8b29fa2e0f026490e5"
370
+ "sha256": "692a295c1c9602f09da7f296b5e0550f7ae381a2f9fe34a060019cee6443287e"
371
371
  },
372
372
  {
373
373
  "path": "clients/bongos-client/index.mjs",
374
374
  "mode": "0000644",
375
- "sha256": "2fe6fa6938205dce4bd1f48a3adc21b0898b78d3f125a9ee8c743497eaae6d22"
375
+ "sha256": "d420a781a25d4d4b8f038894b7829a1e024aafbd51a6d32266ed120cbb7a50c9"
376
376
  },
377
377
  {
378
378
  "path": "clients/bongos-client/package.json",
@@ -1712,7 +1712,7 @@
1712
1712
  {
1713
1713
  "path": "docs/adr/0244-a-catalog-search-is-a-view-over-the-rows-never-an-edit-to-the-selection.md",
1714
1714
  "mode": "0000644",
1715
- "sha256": "7e2a6ae0a599ae73899bb047d3a76e6efa62e76661f623c0cbc69ae659079131"
1715
+ "sha256": "cc2efab0dcf56a590daa249fc9ee177f9f1aed4e7fce9f80e06ff927677c964e"
1716
1716
  },
1717
1717
  {
1718
1718
  "path": "docs/adr/0245-the-way-back-into-an-optional-step-is-that-step-s-own-answer-route.md",
@@ -2122,22 +2122,22 @@
2122
2122
  {
2123
2123
  "path": "docs/adr/0326-an-upgrade-may-add-a-deny-rule-to-a-file-the-instance-owns.md",
2124
2124
  "mode": "0000644",
2125
- "sha256": "993ce83a347058782cff6e07e4191ced95bd0c07b61285f6327feb419e9b0dfd"
2125
+ "sha256": "7484e3b187c1aa84b23ef359baf8e86eabcf61164c9f75076af1e53ef04020f1"
2126
2126
  },
2127
2127
  {
2128
2128
  "path": "docs/adr/README.md",
2129
2129
  "mode": "0000644",
2130
- "sha256": "7bfacf7f4f5ed67202cf4f2798b4e9b04f8fd1a596e22bc72628b29344f134ee"
2130
+ "sha256": "116fb18808cd324f04600f3c7fd15bb849b0c5c81871db1995f2dcee9bb7e77b"
2131
2131
  },
2132
2132
  {
2133
2133
  "path": "docs/api-reference.md",
2134
2134
  "mode": "0000644",
2135
- "sha256": "9b60667dcf534053345a0de7ced96b840718de0ac89c7a4099a5a2c1ec24f17f"
2135
+ "sha256": "efc898a856bca98c7a182ef23d87bb0f5eb15da9574473bb9b23272ce71d506a"
2136
2136
  },
2137
2137
  {
2138
2138
  "path": "docs/api/openapi.json",
2139
2139
  "mode": "0000644",
2140
- "sha256": "dd53ef353bb82815a0d5de1959065ebcfd021584577257837990ec85ec8294b0"
2140
+ "sha256": "b64eee9aa839aa1b528436dfbaa8070f790f80f9d8d992bb6fd3cb06818a23d1"
2141
2141
  },
2142
2142
  {
2143
2143
  "path": "docs/architecture.md",
@@ -2162,12 +2162,12 @@
2162
2162
  {
2163
2163
  "path": "docs/copy-inventory.md",
2164
2164
  "mode": "0000644",
2165
- "sha256": "77cb83609d2a80e2c7520746181ef858839d37e8c0997b5dce42127d0c4c119c"
2165
+ "sha256": "a9b42bc18dbb2dddee7892c2e785011313eba87c8df0b86c3358b7af9050840b"
2166
2166
  },
2167
2167
  {
2168
2168
  "path": "docs/copy-registry.json",
2169
2169
  "mode": "0000644",
2170
- "sha256": "8d83ca0d16a1f9d0efc89f0331f6940592ce34e0dd6f92c448575497bce96548"
2170
+ "sha256": "bbd6bbd017e16f884388664bafcbe4bc6532e40968bc96546cced0b965c28f99"
2171
2171
  },
2172
2172
  {
2173
2173
  "path": "docs/design/apex-pass-2-direction.md",
@@ -2697,7 +2697,7 @@
2697
2697
  {
2698
2698
  "path": "docs/module-api-changelog.md",
2699
2699
  "mode": "0000644",
2700
- "sha256": "a3606d100eb285f3fb82a01c8602db6d342a4d47a969214b87b8616ba67fd35d"
2700
+ "sha256": "f84a2fba932e6e634773c3245f5dbe12f370f42ed6285a2e74546227024b07b5"
2701
2701
  },
2702
2702
  {
2703
2703
  "path": "docs/modules-contract.md",
@@ -4899,6 +4899,16 @@
4899
4899
  "mode": "0000644",
4900
4900
  "sha256": "6cfd20a7dbf5fe4a20872d465eaeb401ead659523e4f2ae64565fd5ddf3aec7e"
4901
4901
  },
4902
+ {
4903
+ "path": "modules/hall-ui/public/goal-forest-lib.js",
4904
+ "mode": "0000644",
4905
+ "sha256": "ea6258a241922dbf8e3782e01c60a3b03a8c7d9213cdfeddb7bcb91be3227d14"
4906
+ },
4907
+ {
4908
+ "path": "modules/hall-ui/public/goal-forest-render.js",
4909
+ "mode": "0000644",
4910
+ "sha256": "b39c5544c30fedfc0a42b7cc6c59b6d630b03fd543d5c5bcc34172c494a0addc"
4911
+ },
4902
4912
  {
4903
4913
  "path": "modules/hall-ui/public/goal-inbox.js",
4904
4914
  "mode": "0000644",
@@ -4937,22 +4947,22 @@
4937
4947
  {
4938
4948
  "path": "modules/hall-ui/public/goals-page.js",
4939
4949
  "mode": "0000644",
4940
- "sha256": "a8eed7fe8913f7b03e0a213a706ff203f1e439539dd18258499042e84cb8fc1f"
4950
+ "sha256": "db143d107e2a58f7d3056556202a0864f6595f723ff302441c092e670f3c4ea3"
4941
4951
  },
4942
4952
  {
4943
4953
  "path": "modules/hall-ui/public/goals-render.js",
4944
4954
  "mode": "0000644",
4945
- "sha256": "5954a301097fefc7dc7d89d3ecbd6cc3fe9a70d1319c8a2ea648fab057ca923a"
4955
+ "sha256": "8d296e2ba0cafdcdf575d8bee24a0aeae3b03173480f2b7b5f5efd33f66e18b6"
4946
4956
  },
4947
4957
  {
4948
4958
  "path": "modules/hall-ui/public/goals.css",
4949
4959
  "mode": "0000644",
4950
- "sha256": "70e3506cdb11d7b108c9bae383169ae4319e5be7fcbb10a656da966db942b33a"
4960
+ "sha256": "b0421607c705c577e680f6f61d926189ef3ea367bef052ad1fab87f4e45937c5"
4951
4961
  },
4952
4962
  {
4953
4963
  "path": "modules/hall-ui/public/goals.html",
4954
4964
  "mode": "0000644",
4955
- "sha256": "8900bae7318f5e124c8cad0e679326426db29ba12ab9f96694a711590c93718f"
4965
+ "sha256": "c1236c20825ed1c767b29d5b3bdfca502e0a40439cfeb84b58d65923c267705a"
4956
4966
  },
4957
4967
  {
4958
4968
  "path": "modules/hall-ui/public/goals.js",
@@ -5829,6 +5839,11 @@
5829
5839
  "mode": "0000644",
5830
5840
  "sha256": "c22a0782ddf7a93deb40f700a2afab15a313925647bbdf424db777da5aa29aa1"
5831
5841
  },
5842
+ {
5843
+ "path": "modules/lifecycle/goal-forest.js",
5844
+ "mode": "0000644",
5845
+ "sha256": "3127ce171e80a40d87b6df030169e094a627ea0cb72d2d055f58d95419c77a07"
5846
+ },
5832
5847
  {
5833
5848
  "path": "modules/lifecycle/goal-from-idea.js",
5834
5849
  "mode": "0000644",
@@ -6022,7 +6037,7 @@
6022
6037
  {
6023
6038
  "path": "modules/lifecycle/routes/goals.js",
6024
6039
  "mode": "0000644",
6025
- "sha256": "1c97d9ba0c9f0ec0b6c1211e85e8f3c1bc3cc56719b5d86082715621af5c61a6"
6040
+ "sha256": "856f1ab40127186fa22c10fd37fbe57eb2d856cbdf75faee0e21209579ad887c"
6026
6041
  },
6027
6042
  {
6028
6043
  "path": "modules/lifecycle/routes/help-requests.js",
@@ -6772,7 +6787,7 @@
6772
6787
  {
6773
6788
  "path": "modules/provisioning/starter-bundles.js",
6774
6789
  "mode": "0000644",
6775
- "sha256": "cb2ca3bdf6cd99ee66d5242709fed3c167840a90cff9efe83ddb193c00c9719e"
6790
+ "sha256": "afb39d1642004ff98d3b722436e789542b87f0b4929f7d85826e8e07ebbd2570"
6776
6791
  },
6777
6792
  {
6778
6793
  "path": "modules/provisioning/tests/liveness-notify.mjs",
@@ -7287,7 +7302,7 @@
7287
7302
  {
7288
7303
  "path": "modules/public-landing/public/projects.html",
7289
7304
  "mode": "0000644",
7290
- "sha256": "3465b462b09b49a93b4e6dcd4a2ec23753bf270b5a1a9d8d92f2013c7d2e3bf8"
7305
+ "sha256": "180097d115d67efd4d5c08119529eb4e9cbafe6e8cae26d5e59c26f910a1434d"
7291
7306
  },
7292
7307
  {
7293
7308
  "path": "modules/public-landing/public/projects.probes.json",
@@ -7777,7 +7792,7 @@
7777
7792
  {
7778
7793
  "path": "modules/ui-design/kit/serve.js",
7779
7794
  "mode": "0000644",
7780
- "sha256": "8c2018cd6f8cfa90e3b73255d8da824162ca72f3963c4b685d81834e3db78029"
7795
+ "sha256": "871ff3a32396dda09f4aa9127f110821c45b6d616c0636d586cc683db01a111c"
7781
7796
  },
7782
7797
  {
7783
7798
  "path": "modules/ui-design/kit/tells.js",
@@ -8087,12 +8102,12 @@
8087
8102
  {
8088
8103
  "path": "package-lock.json",
8089
8104
  "mode": "0000644",
8090
- "sha256": "5453556ff16b658bfd7f82f6659d6b2e38abafecfd1c51e70c15d1e61ea5e6a9"
8105
+ "sha256": "1eb1f4122d6de764110bb2d783bd0c79d3727dff6663d23917edb602a081774f"
8091
8106
  },
8092
8107
  {
8093
8108
  "path": "package.json",
8094
8109
  "mode": "0000644",
8095
- "sha256": "b5c17e5a3b966a08c4b4f31b5361cc147dc3dcf7a794ccb0dc208ef16f4eef73"
8110
+ "sha256": "58625b2583bfd11c6972959c530409e6c63085cc2de47e9f6278bec7c767b9db"
8096
8111
  },
8097
8112
  {
8098
8113
  "path": "public-docs/index.html",
@@ -8437,7 +8452,7 @@
8437
8452
  {
8438
8453
  "path": "scripts/gds/claude-materialize.js",
8439
8454
  "mode": "0000644",
8440
- "sha256": "146844bd64dc10f5ae06dab42ff2f711c08af450edb58d765ba976b84d7fb6ca"
8455
+ "sha256": "3baec8c96ee223786af2124265e45b43db92a5836349836107ffc71cd421c860"
8441
8456
  },
8442
8457
  {
8443
8458
  "path": "scripts/gds/claude-md-template.js",
@@ -9692,7 +9707,7 @@
9692
9707
  {
9693
9708
  "path": "scripts/hall-preview/server.js",
9694
9709
  "mode": "0000644",
9695
- "sha256": "f66f32220b4090f4dd29e0446ac4c2b68e8ac8dbf46a0f64487cadc68fc1a1b1"
9710
+ "sha256": "9c2fc2f33ddd4f51e715e89cc84d615c55aa070ee2173cdaa8fb7559f2f5a14a"
9696
9711
  },
9697
9712
  {
9698
9713
  "path": "scripts/hall-preview/task-visual.js",
@@ -10097,7 +10112,7 @@
10097
10112
  {
10098
10113
  "path": "src/module-api.js",
10099
10114
  "mode": "0000644",
10100
- "sha256": "7a59066fd558222ba904ef314cea8f2145c85f00efbb6a0d6d811c0c4d527564"
10115
+ "sha256": "a71508cbe155a0041db078c9a66c9e78cc578163d0056ae2cf12c9aa2bcc2df0"
10101
10116
  },
10102
10117
  {
10103
10118
  "path": "src/module-loader/catalog.js",
@@ -10877,7 +10892,7 @@
10877
10892
  {
10878
10893
  "path": "tests/claude_materialize.mjs",
10879
10894
  "mode": "0000644",
10880
- "sha256": "0771634ad696014cb6307ca82c7d20fdb3ccad809a7ea463b5866913ec046812"
10895
+ "sha256": "23e00b61129b39e8b22696d22feba3810e276949e10ef81235468002a15ea855"
10881
10896
  },
10882
10897
  {
10883
10898
  "path": "tests/clawback_confirmed_backlog.mjs",
@@ -11517,7 +11532,7 @@
11517
11532
  {
11518
11533
  "path": "tests/goal_category_ui.mjs",
11519
11534
  "mode": "0000644",
11520
- "sha256": "9da4d0762b665e5e622a3a26d754e13206da52ef591b14bf35e73da5e8c39317"
11535
+ "sha256": "2993b7fdf8fc06682597ee3bc24888db18d163109b1476dbafff961edae9371e"
11521
11536
  },
11522
11537
  {
11523
11538
  "path": "tests/goal_cli.mjs",
@@ -11542,13 +11557,28 @@
11542
11557
  {
11543
11558
  "path": "tests/goal_criterion_confirm_ui.mjs",
11544
11559
  "mode": "0000644",
11545
- "sha256": "57397a645bbb488ab53bf380d9f75fc925039ced5e2740817e340e0fe25e4c34"
11560
+ "sha256": "431b3f3beea6d82d654f0c7ce3259975c47538456e17afa1428b39fa64037e9e"
11546
11561
  },
11547
11562
  {
11548
11563
  "path": "tests/goal_dependencies_read.mjs",
11549
11564
  "mode": "0000644",
11550
11565
  "sha256": "27e5e881185511e0562dcfe36b8d6926b3222a9443893456c5f3a04089e40db6"
11551
11566
  },
11567
+ {
11568
+ "path": "tests/goal_forest.mjs",
11569
+ "mode": "0000644",
11570
+ "sha256": "5aa1abc7faa95f51bc4b37f6b07ca033d503dfb4f589bd05b516d6578c55045b"
11571
+ },
11572
+ {
11573
+ "path": "tests/goal_forest_lib.mjs",
11574
+ "mode": "0000644",
11575
+ "sha256": "3b309358c195469bee0ab1ce95242914fbc0b1b3def0bb7de3d51dc09dd29c6a"
11576
+ },
11577
+ {
11578
+ "path": "tests/goal_forest_page.mjs",
11579
+ "mode": "0000644",
11580
+ "sha256": "5c173e79de212e014860f9e3e9b26099decbce4ab675c0f8614e6c1ebdf2a2da"
11581
+ },
11552
11582
  {
11553
11583
  "path": "tests/goal_graph.mjs",
11554
11584
  "mode": "0000644",
@@ -11557,7 +11587,7 @@
11557
11587
  {
11558
11588
  "path": "tests/goal_invite_consent.mjs",
11559
11589
  "mode": "0000644",
11560
- "sha256": "06795131276221ca2cc5792b1b6f2d102a3b16fbbd413ceb1a05600edf6ff68a"
11590
+ "sha256": "e46d8d02b4beb7d28b5da7fd360ff703b8b0a052231de8c0711bdf47558fa2df"
11561
11591
  },
11562
11592
  {
11563
11593
  "path": "tests/goal_map_lib.mjs",
@@ -12062,7 +12092,7 @@
12062
12092
  {
12063
12093
  "path": "tests/helpers.mjs",
12064
12094
  "mode": "0000644",
12065
- "sha256": "fe55acfa23dbc1fcd5e75893f88e4bcbff46d128aa99595511449be56299ddb3"
12095
+ "sha256": "e48846a413c484ad1eb2d3d1c110560d1b80572e25dfac2dcb56d6311acaa9d2"
12066
12096
  },
12067
12097
  {
12068
12098
  "path": "tests/hierarchy-config.mjs",
@@ -13107,7 +13137,7 @@
13107
13137
  {
13108
13138
  "path": "tests/projects_hub_module_picker.mjs",
13109
13139
  "mode": "0000644",
13110
- "sha256": "bae4d97b96d9aa747e70ee3a62f40bb7ef16c3702f4c2be362b12fe814a60aa6"
13140
+ "sha256": "1a7e888877c7dc7c020a89cd7fda436c373f40b6799866a711d5ce357a34176d"
13111
13141
  },
13112
13142
  {
13113
13143
  "path": "tests/projects_hub_pre_uat.mjs",
@@ -13292,7 +13322,7 @@
13292
13322
  {
13293
13323
  "path": "tests/provisioning_starter_bundles.mjs",
13294
13324
  "mode": "0000644",
13295
- "sha256": "5996c2dac6fbb427fba2eac98058ec9cea529fd2aa74d4b51e4085263af798ae"
13325
+ "sha256": "54c279c84cb5419fd60fa0ae693b617619a8e0a423adee342e01d257236a3d7b"
13296
13326
  },
13297
13327
  {
13298
13328
  "path": "tests/provisioning_teardown_intent.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
- - 409 operations across 61 resource groups
8
+ - 410 operations across 61 resource groups
9
9
 
10
10
  ## Use it from your project
11
11
 
@@ -403,6 +403,8 @@ function createClient(opts = {}) {
403
403
  getGoalsIdConflicts: (args) => request("GET", "/goals/{id}/conflicts", { hasBody: false }, args),
404
404
  // POST /goals/{id}/conflicts/resolve — rank: any-builder — POST /goals/:id/conflicts/resolve
405
405
  postGoalsIdConflictsResolve: (args) => request("POST", "/goals/{id}/conflicts/resolve", { hasBody: true }, args),
406
+ // GET /goals/{id}/dependency-forest — rank: any-builder — GET /goals/:id/dependency-forest
407
+ getGoalsIdDependencyForest: (args) => request("GET", "/goals/{id}/dependency-forest", { hasBody: false }, args),
406
408
  // POST /goals/{id}/invitations — rank: any-builder — POST /goals/:id/invitations
407
409
  postGoalsIdInvitations: (args) => request("POST", "/goals/{id}/invitations", { hasBody: true }, args),
408
410
  // POST /goals/{id}/invitations/{reqId}/rescind — rank: any-builder — POST /goals/:id/invitations/:reqId/rescind
@@ -402,6 +402,8 @@ function createClient(opts = {}) {
402
402
  getGoalsIdConflicts: (args) => request("GET", "/goals/{id}/conflicts", { hasBody: false }, args),
403
403
  // POST /goals/{id}/conflicts/resolve — rank: any-builder — POST /goals/:id/conflicts/resolve
404
404
  postGoalsIdConflictsResolve: (args) => request("POST", "/goals/{id}/conflicts/resolve", { hasBody: true }, args),
405
+ // GET /goals/{id}/dependency-forest — rank: any-builder — GET /goals/:id/dependency-forest
406
+ getGoalsIdDependencyForest: (args) => request("GET", "/goals/{id}/dependency-forest", { hasBody: false }, args),
405
407
  // POST /goals/{id}/invitations — rank: any-builder — POST /goals/:id/invitations
406
408
  postGoalsIdInvitations: (args) => request("POST", "/goals/{id}/invitations", { hasBody: true }, args),
407
409
  // POST /goals/{id}/invitations/{reqId}/rescind — rank: any-builder — POST /goals/:id/invitations/:reqId/rescind
@@ -784,6 +784,8 @@ export interface BongosClient {
784
784
  getGoalsIdConflicts(args?: RequestArgs): Promise<GetGoalsIdConflictsResponse>;
785
785
  /** POST /goals/{id}/conflicts/resolve — rank: any-builder */
786
786
  postGoalsIdConflictsResolve(args: RequestArgs & { body: PostGoalsIdConflictsResolveRequest }): Promise<PostGoalsIdConflictsResolveResponse>;
787
+ /** GET /goals/{id}/dependency-forest — rank: any-builder */
788
+ getGoalsIdDependencyForest(args?: RequestArgs): Promise<ApiResponse>;
787
789
  /** POST /goals/{id}/invitations — rank: any-builder */
788
790
  postGoalsIdInvitations(args?: RequestArgs & { body?: PostGoalsIdInvitationsRequest }): Promise<PostGoalsIdInvitationsResponse>;
789
791
  /** POST /goals/{id}/invitations/{reqId}/rescind — rank: any-builder */
@@ -399,6 +399,8 @@ export function createClient(opts = {}) {
399
399
  getGoalsIdConflicts: (args) => request("GET", "/goals/{id}/conflicts", { hasBody: false }, args),
400
400
  // POST /goals/{id}/conflicts/resolve — rank: any-builder — POST /goals/:id/conflicts/resolve
401
401
  postGoalsIdConflictsResolve: (args) => request("POST", "/goals/{id}/conflicts/resolve", { hasBody: true }, args),
402
+ // GET /goals/{id}/dependency-forest — rank: any-builder — GET /goals/:id/dependency-forest
403
+ getGoalsIdDependencyForest: (args) => request("GET", "/goals/{id}/dependency-forest", { hasBody: false }, args),
402
404
  // POST /goals/{id}/invitations — rank: any-builder — POST /goals/:id/invitations
403
405
  postGoalsIdInvitations: (args) => request("POST", "/goals/{id}/invitations", { hasBody: true }, args),
404
406
  // POST /goals/{id}/invitations/{reqId}/rescind — rank: any-builder — POST /goals/:id/invitations/:reqId/rescind
@@ -3,6 +3,10 @@
3
3
  - **Status:** Accepted
4
4
  - **Date:** 2026-09-04
5
5
  - **Tasks:** [#1002340](https://cloudbongos.com/builders#/task/1002340) (this decision — BV1.R21)
6
+ - **Amended:** 2026-09-22 by [task 1003500](https://cloudbongos.com/builders#/task/1003500) — §3's "no
7
+ synonyms" is reversed (rows carry declared search `aliases`, so typing `chat` finds Discord); "not
8
+ semantic" stands, the sentence case still finds nothing, and the embeddings layer §3 deferred to
9
+ was declined. See the amendment note in §3.
6
10
  - **Goal:** [#1000046](https://cloudbongos.com/builders#/goal/1000046) — Project creation, criterion
7
11
  `proj-module-select` ("during setup a default set of modules is pre-selected…")
8
12
  - **Related:** [ADR 0237](0237-a-starter-bundle-is-a-preset-over-the-always-on-core-keyed-by-declared-type.md)
@@ -78,6 +82,43 @@ Concretely, in `modules/public-landing/public/projects.html`'s picker block:
78
82
  AI layer for project creation. A lexical filter that admits it is lexical is
79
83
  honest; one that half-guesses is not.
80
84
 
85
+ > **AMENDED 2026-09-22 — "no synonyms" is reversed; "not semantic" stands**
86
+ > ([task 1003500](https://cloudbongos.com/builders#/task/1003500)). The haystack now also
87
+ > carries a row's declared `aliases` (`starter-bundles.js`), so typing **`chat`** finds
88
+ > Discord. Everything else here is unchanged: still AND across terms, still substring,
89
+ > still no ranking or fuzziness, and still nothing that understands a query.
90
+ >
91
+ > **What this does not fix, stated plainly because the example above invites the
92
+ > misreading.** The sentence "somewhere for people to chat" *still finds nothing*, and no
93
+ > alias list can change that: the matcher ANDs every whitespace-separated term, so
94
+ > `somewhere` and `people` must appear in the haystack too. Making a sentence work means
95
+ > OR-then-rank, which §3 and §5 reject on their own merits. Aliases raise the odds that an
96
+ > owner's **one word** lands on the right row; they do not teach the filter to read a
97
+ > phrase. `tests/projects_hub_module_picker.mjs` pins both halves — the word that now
98
+ > works, and the sentence that still does not — so this boundary is proven rather than
99
+ > promised.
100
+ >
101
+ > **Why this instead of the embeddings layer this section deferred to.** Task 1003500 was
102
+ > that layer, and it was declined on measurement rather than on principle. The catalog is
103
+ > 22 modules whose only free text is a title and one sentence — there is no `keywords` or
104
+ > `category` field in the manifest schema to embed. Against that, the semantic path costs a
105
+ > paid provider account, a key route, a `pgvector` migration and an index, on a project
106
+ > whose retrieval budget ([ADR 0060](0060-gds-retrieval-layer.md)) rules out exactly that
107
+ > kind of addition "unless lexical-in-Postgres demonstrably can't do the job". It was never
108
+ > shown that it can't: the go/no-go that was supposed to decide this (GDS-V4 R24, with
109
+ > pgvector as R32) was **abandoned in July 2026 by [ADR 0103](0103-gdsv4-bongos-consolidation.md)**
110
+ > and never run, so the "deferred" above had quietly been pointing at dead tasks.
111
+ >
112
+ > A declared word list is also the better artifact at this size: it is deterministic, it costs
113
+ > nothing to run, and when it gets something wrong the person who read the complaint can fix
114
+ > it in one line. `tests/provisioning_starter_bundles.mjs` requires at least three aliases per
115
+ > opt-in module and rejects one the row's own copy already contains, so the list cannot rot
116
+ > into decoration and a new opt-in module cannot ship unfindable.
117
+ >
118
+ > Aliases are **never rendered** — the same standing §3 already gives the key. The swap §4
119
+ > anticipated ("a future by-meaning search replaces `moduleMatches` and nothing else") is
120
+ > still available if the catalog ever outgrows this.
121
+
81
122
  The whole matcher is a named function on the page precisely so the vm harness in
82
123
  `tests/projects_hub_module_picker.mjs` can run it directly — the same reason
83
124
  `bundleForAnswers` is a function and not an inline expression.
@@ -4,6 +4,7 @@
4
4
  - **Date:** 2026-09-22
5
5
  - **Task:** [task 1004156](https://cloudbongos.com/builders#/task/1004156) (goal 1000090)
6
6
  - **Deciders:** the owner, asked directly. The question is theirs because it narrows a rule the project drew on purpose; the implementation follows from the answer.
7
+ - **Amended:** 2026-09-22 by [task 1004158](https://cloudbongos.com/builders#/task/1004158) — the two shelf rules became starter-only; the top-up asks for the credential half alone. See the amendment note in *Decision* below.
7
8
  - **Amends:** [ADR 0121](0121-greenfield-vs-brownfield-onboarding-adopt-existing-repo.md) §2 — which already licensed "merge **or** leave-and-report" for a pre-existing file; this names the one merge that is provably safe, and extends it from `--adopt` to every materialize
8
9
  - **Related:** task 1003962 (the allow-only starter) · task 1003966 (the deny half) · [task 1004155](https://cloudbongos.com/builders#/task/1004155) (the core's own list) · task 1004023 (an unparseable settings.json is copied through, not mangled)
9
10
 
@@ -31,6 +32,10 @@ The credential rules are pure safety: nothing an instance legitimately does requ
31
32
 
32
33
  The consequence is worth stating plainly rather than discovering: an instance whose builders used the repo-global shelf as a set-aside fallback will find it blocked after its next upgrade, and — no provenance, deny beating allow — cannot re-enable it from `settings.json`. Every added rule is named in the run output, so this is visible when it happens. If it ever bites, excluding the two shelf rules from the top-up set (keeping them starter-only) is a one-line change.
33
34
 
35
+ > **AMENDED 2026-09-22 — the exclusion above was taken** ([task 1004158](https://cloudbongos.com/builders#/task/1004158)). Not because it bit, but because the owner, shown the trade-off, judged the asymmetry the wrong way round: the core should not silently withdraw a workflow an instance cannot give itself back, when what it buys is a guard against a *shared-state* hazard the instance may not even have (the stack is repo-global across worktrees, so a single-worktree instance is not exposed the way this repo is). `starterDeny` is now `credentialDeny` plus the shelf rules; the top-up asks for `credentialDeny` alone.
36
+ >
37
+ > **Two things this does not change.** A *fresh* instance still gets both shelf rules — there they are a visible default, present from the first commit and editable before any workflow has grown around it, which is the difference in kind the section above is really about. And because the top-up only ever appends, **an instance that already took them keeps them**: live `cloudbongos.com` gained both in its 0→20 run and its builders stay blocked until someone hand-edits that file. This amendment is forward-looking only, and deliberately so — an upgrade that started *removing* rules would give up the directionality the whole decision rests on.
38
+
34
39
  ### What is deliberately NOT done
35
40
 
36
41
  - **No provenance, and so no memory of a deletion.** An owner who deliberately removes a rule we add will get it back on the next upgrade, and because deny beats allow in Claude Code's permission order they cannot override it with an allow rule either. We considered recording what we added so a later deletion could be read as intent. It was rejected as speculative machinery for a case nobody has hit: the rules in question are "do not read credentials" and "do not `git stash`", and an instance that genuinely needs one lifted is a conversation, not a config toggle. Revisit if it is ever a real complaint.
@@ -332,7 +332,7 @@ This keeps the decision history honest and traceable.
332
332
  | 0242 | [**A cross-role cascade is a declaration table on the event that already exists** ([task 1003549](https://cloudbongos.com/builders#/task/1003549) · goal 1000074, criterion `artist-review-task-on-ship` — the split R04 invited in its own brief and [ADR 0241](0241-the-artist-gate-is-a-per-project-deploy-gate-that-reads-a-state.md)'s dependency). R04 needs "every ship with copy or visuals GENERATES one", and its brief said the infrastructure did not exist, naming the spark queue as the prototype. Reading both surfaces shows the prototype is the wrong half: the spark queue ([task 1002921](https://cloudbongos.com/builders#/task/1002921)) and the copy-desk flag queue ([task 1003113](https://cloudbongos.com/builders#/task/1003113)) are both **PULL** — filtered listings of rows another role already created — and nothing anywhere generated work on a state transition, which is the **PUSH** R04 asks for. (`cascade-dispatch.js` is model-cost routing; named only because its name has misled more than one reader.) **Decision: no new bus.** The kernel seam registry's own header uses this exact scenario as its worked example, `task.shipped` is already emitted post-commit by both ship paths and already has two consumers (discord's broadcast, sessions' deferred reward) — so a cascade is ONE MORE LISTENER on it, registered in `routes/lifecycle.js` beside the port because that mount is the one boot hook tests also run. A cascade is a frozen entry `{ key, discipline, when(task), title(task), body(task) }` — **a declaration table, not a rules engine**: no condition language, no stored rules, no per-project config, no priority ordering; adding one appends an entry and the runner does not change (the [ADR 0237](0237-a-starter-bundle-is-a-preset-over-the-always-on-core-keyed-by-declared-type.md)/[0239](0239-a-recommendation-is-a-table-that-carries-its-own-reason.md) shape, for their reason — the generalisations worth building are the ones two or three real entries have shown they share). **`CASCADES` ships EMPTY and R04 writes the first rule**, asserted so the emptiness is a decision on the record; the mechanism is proved against the tests' own declarations rather than against its first consumer's shape. Four load-bearing properties, all pinned: (1) **it can never fail the ship it reacts to** — post-commit event, isolated listeners, and a runner that catches everything and returns a report, because ADR 0241 settled that an artist may hold a DEPLOY and never a builder's SHIP and a generator that could fail a ship would smuggle that gate back in; verified against a real failure (no database reachable → one log line, resolves); (2) **idempotent on machinery that already exists** — `source: 'cascade'` + `source_ref: cascade-<key>-<taskId>` through `createTask`'s `(source, source_ref)` read, the same one a copy proposal uses ([ADR 0233](0233-a-copy-proposal-is-a-task-carrying-a-patch.md)), so a reconciler re-sweep or a double emit is a no-op; (3) **it says where it came from** — inherits `goal_id`/`version_id`, carries the origin id in the ref and the prose, but NOT `parent_task_id`, which models a SUBTASK, and cascaded work is not part of the ship that triggered it; (4) **files at `backlog`, never `ready`** — generated work is a request, and auto-filing claimable work would let a rule nobody re-read mint claims. A rule key is PERMANENT (half the source_ref; renaming re-files everything it ever generated), pinned by a duplicate-key check. The mechanism is discipline-agnostic — an ideator or engineer cascade is the same entry with a different `discipline`, which is the generalisation that made this its own task. Accepted limit: cascades are declared in code, not configured per project. Rejected: extending the spark queue (a read-side filter that generates nothing), a new event or poller, a rules engine or per-project config, filing at `ready`, `parent_task_id` as the link, and running the cascade inside the ship transaction (which would make a generation failure a ship failure — the exact coupling ADR 0241 rules out).](0242-a-cascade-is-a-declaration-table-on-the-event-that-already-exists.md) | goal 1000074 / the artist's loop |
333
333
  | 0243 | [**A bundle adjustment belongs to the bundle, not to the advice about it** ([task 1002341](https://cloudbongos.com/builders#/task/1002341) · goal 1000046, criterion `proj-module-select`). R17 ([ADR 0239](0239-a-recommendation-is-a-table-that-carries-its-own-reason.md)) built a rule engine and R20 ([ADR 0240](0240-a-skipped-module-picker-stores-nothing-and-resolves-the-bundle-on-read.md)) built a picker, and nothing connected them: the picker preselected the type's RAW preset while `GET /provisioning/recommendations` separately applied `TEAM_SHAPE_RULES` to that same preset and returned a better answer no surface rendered. Joining them the obvious way — let the picker apply the rules — breaks the property R20 was built on ([ADR 0240](0240-a-skipped-module-picker-stores-nothing-and-resolves-the-bundle-on-read.md) §2: the preselect and the fallback are "one function, called twice"), because an owner who answers the interview, watches the panel preselect `discord`, and then SKIPS the picker would get a project without it. **Decision: `TEAM_SHAPE_RULES` moves from `recommendations.js` down to `starter-bundles.js`** (re-exported, so the public surface is unchanged), `effectiveModules` resolves through the new `bundleForAnswers(type, teamShape)`, and the picker reads the same table the fallback does. The distinction is not a preference: a bundle adjustment is not advice ABOUT the bundle — it IS the bundle once the owner has answered, and it is the only one of R17's three tables that is ever APPLIED (governance is a preview carrying `applied: false`, invites are a seam for R28), so it is the only one that has to live where the resolution happens. `effectiveModules` reads `detail.team_shape` straight off the row rather than through `normalizeProjectDetail` — importing it would point the bundle layer UP at the module that requires it ([ADR 0083](0083-modular-architecture-strangler-build-plan.md)) to buy an answer it already has, since an off-vocabulary shape fires no rule and adjusts nothing. `GET /provisioning/starter-bundles` gains `byTeamShape`: every bundle re-resolved for every ruled answer, with the `adjustments` that got it there — twenty small rows, the whole preselect table in ONE read, for the reason `optional` rides the same payload (the owner flips type and team-shape on two EARLIER panels, and a refetch could land after they moved on or paint a key the panel has no row for). `PATCH /provisioning/instances/:id/detail` returns `modules: effectiveModules(updated)` beside its `publish` verdict and for the same reason — changing `team_shape` MOVES the modules of a `source: 'bundle'` project, so the effect comes back with the cause. The panel renders one reason line per adjustment that ACTUALLY fired, and only while the owner has not answered for themselves (a reason handed back for a toggle they flipped reads as the panel arguing with them). An adjusted bundle returns in CATALOG order, never append order, or the same set of modules would read as a different list depending on whether the picker was answered or skipped; a payload without `byTeamShape` degrades to the R20 answer, which is never wrong, only untailored. No new storage — the adjustment is resolved, never written. Rejected: leaving the rules up in `recommendations.js` and having the picker call `/recommendations` (two lists kept in agreement by hand, plus a round trip per keystroke); importing `normalizeProjectDetail`; storing the adjusted bundle on a skip (it writes down an answer nobody gave); moving governance and invites down too (they are never applied, so there is nothing for them to stay in agreement with); and a reason line per matched condition rather than per actual change.](0243-a-bundle-adjustment-belongs-to-the-bundle-not-the-advice-about-it.md) | provisioning / project creation |
334
334
  | 0245 | [**The way back into an optional step is that step’s own answer route** ([task 1002342](https://cloudbongos.com/builders#/task/1002342) · goal 1000046, criterion `proj-module-select`). R20 ([ADR 0240](0240-a-skipped-module-picker-stores-nothing-and-resolves-the-bundle-on-read.md)) made the create wizard’s module picker skippable and left a project’s module set write-once — the route file said so out loud (“there is no mutation route yet”). R23 closes it WITHOUT a new endpoint: `modules` joins `description` and `detail` on `PATCH /provisioning/instances/:id/detail`, because the wizard’s two optional steps are the same kind of thing — asked optionally at creation, stored on the platform row, reaching no running instance — and that route already carried the ownership gate, the `api:self`/`api:archon:<id>` actor rule, the 404-no-existence-leak shape, and (since [ADR 0243](0243-a-bundle-adjustment-belongs-to-the-bundle-not-the-advice-about-it.md)) an `effectiveModules` answer it could report but not set. Deliberately NOT the settings channel: a setting is a policy the running instance obeys and would queue a restart to deliver a value the instance does not read. Only an ARRAY is a change — an omitted or null key leaves the column alone — so the three-valued contract’s `NULL` (“never asked”) stays unreachable by a later edit, and `modules` REPLACES where `detail` merges, because a merging write would make removal impossible. The surface is the manage view’s extras band (`/projects?view=manage&id=N`), where a toggle IS the save and the band repaints from the platform’s answer rather than from what was sent.](0245-the-way-back-into-an-optional-step-is-that-step-s-own-answer-route.md) | provisioning / project creation |
335
- | 0244 | [**A catalog search is a view over the rows, never an edit to the selection** ([task 1002340](https://cloudbongos.com/builders#/task/1002340) · goal 1000046, criterion `proj-module-select`). The creation picker ([ADR 0240](0240-a-skipped-module-picker-stores-nothing-and-resolves-the-bundle-on-read.md)) paints one toggle per optional module, and the catalog is meant to grow — so R21 adds a keyword search over it. A search over a list of links is trivial; a search over a list of TOGGLES is not, because the list is also an answer. Two failure modes follow from letting a row be hidden: the filter **edits** the answer (narrow the rendered array and the read-back array together, and typing `discord` then pressing Next files one module instead of three — silently, with every screen looking right), or the filter **lies** about it (a switched-on module off screen reads as switched off, which is [ADR 0237](0237-a-starter-bundle-is-a-preset-over-the-always-on-core-keyed-by-declared-type.md)’s "a preselect they have to audit" in its harsher form — one they cannot see at all). **Decision: the search is a VIEW and `state.modules` is the answer; the view may not write to it.** `moduleQuery` is a plain variable, never part of the draft, so `saveDraft` is not called from the input handler and a search can never turn an untouched picker into an answered one (ADR 0240’s `null` survives); `visibleModules()` derives the shown rows per render while `pickedModules()`, `modulesForCreate()` and `extrasRow()` keep reading the whole catalog; and the panel STATES what the filter is hiding — one line under the field counting what is **on**, not what is visible. Matching is `moduleMatches(mod, terms)`, a pure function: lower-cased **label + summary + key** as one haystack, every whitespace-separated term an AND’d substring. AND because adding a word means the list is still too long; substring because a filter that shows nothing until you finish typing reads as broken; the key because that is what a returning owner names a module by. No ranking (reordering toggles under a typing hand is worse than not), and deliberately **not semantic** — by-meaning search is deferred with the rest of the creation AI layer (idea 1000552). Rejected: filtering the read-back array (failure mode 1), CSS-hiding non-matches (leaves invisible `aria-pressed` toggles in the a11y tree), pinning switched-on rows above the query (a filter that lies the other way), persisting the query in the draft, and debouncing a pass over already-loaded rows.](0244-a-catalog-search-is-a-view-over-the-rows-never-an-edit-to-the-selection.md) |
335
+ | 0244 | [**A catalog search is a view over the rows, never an edit to the selection** ([task 1002340](https://cloudbongos.com/builders#/task/1002340) · goal 1000046, criterion `proj-module-select`). The creation picker ([ADR 0240](0240-a-skipped-module-picker-stores-nothing-and-resolves-the-bundle-on-read.md)) paints one toggle per optional module, and the catalog is meant to grow — so R21 adds a keyword search over it. A search over a list of links is trivial; a search over a list of TOGGLES is not, because the list is also an answer. Two failure modes follow from letting a row be hidden: the filter **edits** the answer (narrow the rendered array and the read-back array together, and typing `discord` then pressing Next files one module instead of three — silently, with every screen looking right), or the filter **lies** about it (a switched-on module off screen reads as switched off, which is [ADR 0237](0237-a-starter-bundle-is-a-preset-over-the-always-on-core-keyed-by-declared-type.md)’s "a preselect they have to audit" in its harsher form — one they cannot see at all). **Decision: the search is a VIEW and `state.modules` is the answer; the view may not write to it.** `moduleQuery` is a plain variable, never part of the draft, so `saveDraft` is not called from the input handler and a search can never turn an untouched picker into an answered one (ADR 0240’s `null` survives); `visibleModules()` derives the shown rows per render while `pickedModules()`, `modulesForCreate()` and `extrasRow()` keep reading the whole catalog; and the panel STATES what the filter is hiding — one line under the field counting what is **on**, not what is visible. Matching is `moduleMatches(mod, terms)`, a pure function: lower-cased **label + summary + key** as one haystack, every whitespace-separated term an AND’d substring. AND because adding a word means the list is still too long; substring because a filter that shows nothing until you finish typing reads as broken; the key because that is what a returning owner names a module by. No ranking (reordering toggles under a typing hand is worse than not), and deliberately **not semantic** — by-meaning search is deferred with the rest of the creation AI layer (idea 1000552). Rejected: filtering the read-back array (failure mode 1), CSS-hiding non-matches (leaves invisible `aria-pressed` toggles in the a11y tree), pinning switched-on rows above the query (a filter that lies the other way), persisting the query in the draft, and debouncing a pass over already-loaded rows. **AMENDED 2026-09-22 by [task 1003500](https://cloudbongos.com/builders#/task/1003500): "no synonyms" is reversed — rows carry declared search `aliases`, so typing `chat` finds Discord.** The SENTENCE "somewhere for people to chat" still finds nothing and no alias list can change that (the matcher ANDs every term, so `somewhere` and `people` must appear too); aliases raise the odds an owner's one word lands, they do not teach the filter to read a phrase, and both halves are pinned by tests. "Not semantic" stands: the embeddings layer this row deferred to was DECLINED, not built. Its go/no-go (GDS-V4 R24, pgvector R32) had been abandoned by ADR 0103 in July and never run, so the deferral was pointing at dead tasks; and the catalog is 22 modules whose only free text is a title and one sentence, against a provider account, key route, migration and index that ADR 0060's budget rules out absent proof lexical can't do the job. A declared word list is deterministic, free to run and fixable by whoever reads the complaint; `tests/provisioning_starter_bundles.mjs` requires ≥3 aliases per opt-in module and rejects one its own copy already contains.](0244-a-catalog-search-is-a-view-over-the-rows-never-an-edit-to-the-selection.md) |
336
336
  | 0247 | [**The join door is ONE composed answer, and dark matter has no live verdict to compose with yet** ([task 1002331](https://cloudbongos.com/builders#/task/1002331) · goal 1000046, criterion `proj-join-policy`). [ADR 0182](0182-project-visibility-two-axes.md) D6 wrote the rule — visibility is the ceiling, join policy may only narrow it — and nothing implemented it: the public `POST /access-requests` read `joinability` alone, so an owner could set their project private or stealth on the map and the application endpoint behaved exactly as before. R12 adds the third knob (`join_grant` = view | apply | full, one line of settings vocabulary, default `full` so nothing existing changes) and composes all three in one pure kernel file, `src/bongos/project-door.js`, published through the doorway so no module can re-open a door the others closed. Three things worth carrying: the door vocabulary is a list ordered widest→strictest, so “the stricter wins” is the data structure rather than a branch; the refusal never names WHICH knob closed the door, because a stealth project that could be identified by its 403 gives back exactly what R06’s server-side redaction protects; and `locked` (dark matter) is specified, tested and deliberately UNWIRED — the instance cannot derive publishability (no description in its pack, and a pushed verdict goes stale the moment the owner writes one), while the hub’s cached copy says every live project is dark matter today, so enforcing off it would refuse every join on the platform.](0247-the-join-door-is-one-composed-answer-and-dark-matter-has-no-live-verdict-yet.md) | onboarding / provisioning / project creation |
337
337
  | 0248 | [**A hub project’s owner is the provisioning row, carried on a port and bound to both halves of the catalog projection** ([task 1003580](https://cloudbongos.com/builders#/task/1003580) · goal 1000046, criterion `proj-invite-builders`). [ADR 0246](0246-catalog-membership-is-a-projection-not-a-federation-side-effect.md) made a wizard-made project resolvable by `POST /projects/invite` and named the remainder: the route is gated `project.curate` (floor metic) with no own-scope arm, so the project’s own owner — a `xenos` on the hub — got 403 on their own project, blocking BV1.R27–R29 and the C6 proof. The arm admits a `project.curate` holder **OR** the owner, where owner means `provisioning_instances.owner_builder_id` — never the hub’s `owner_github_id` columns, which [ADR 0238](0238-the-join-door-is-a-provisioning-settings-policy-key.md) fixes as attribution. Three things worth carrying: the fact travels on a NEW port `provisioning.projectOwnedBy` answering a **boolean**, so the comparison stays on the plane that holds the row and no consumer handles an ownership id it could mis-compare; the port is asked about **both halves of the ADR 0246 projection** (slug AND origin), because a hand-seeded catalog row’s name is otherwise a squattable claim on that project’s membership directory; and ownership is asked BEFORE the target is resolved, so the refusal stays the untouched `requirePermission` 403 and cannot become an existence oracle over stealth projects ([ADR 0182](0182-project-visibility-two-axes.md) D2a). The atom stays spelled in the route’s own middleware list ([ADR 0210](0210-scouting-graduates-on-an-instance-asserted-owner-claim.md)’s rule), so the route still classifies `metic+archon`.](0248-a-hub-project-s-owner-is-the-provisioning-row-carried-on-a-port.md) | onboarding / provisioning / project creation |
338
338
 
@@ -417,4 +417,4 @@ This keeps the decision history honest and traceable.
417
417
  | 0323 | [**Hosting is three shapes, and we are not the landlord** ([task 1004140](https://cloudbongos.com/builders#/task/1004140), goal 1000106 — BONGOS-V2). Five projects ran as one Node process each on the control-plane droplet, about $0.80/month apiece — the cheapest arrangement available, bought with ~4,600 lines of `provision*.js` that write systemd units, create unix accounts, render Caddy snippets, `createdb`, install backup timers and count memory against a ceiling. That is the cost nobody priced: **we are a landlord.** The owner reframed the question — build on what others maintain, and let an owner keep the deployment platform they already use. **Seven decisions.** The shapes become `byo-host` (the owner runs it), `cloud-host` (we stand it up for them) and `dedicated-cloud-host` (the same path, larger plan); `co-tenant` is **retired, not renamed**, because it means *a process on our box from our checkout* — the opposite of bring-your-own-host, so reusing the name would make every existing comment lie. **DigitalOcean hosts hubs only**, which closes [ADR 0145](0145-free-hosted-project-tier-isolation-and-domain-separation.md) Finding 1 by removing its precondition rather than hardening the cookie: with no customer code on a `cloudbongos.com` subdomain there is no tenant to receive the hub session. **Render is the default** — its Postgres carries PITR and automated backups on every paid tier; Fly's managed Postgres starts ~$38/mo and Railway's is explicitly unmanaged, which is the exact responsibility being shed. **Cloudflare is the front door, not the compute**: Workers is a V8-isolate runtime that would require rewriting Express and `pg`, and Containers is built for bursty workloads that sleep, which a hall with background sweeps and a live event stream does not survive. **No deployment-platform adapter is built at all** — their application already deploys itself from the repository, so the platform we do not talk to is the platform we do not have to add. **Secrets: the platform is the system of record and Bongos stores names, never values** — a manifest of name, purpose and environment, no application secret, no platform API token, no "is it set?" probe, because each of those makes us custodian of other companies' credentials for convenience the manifest already delivers; `byo-host` marks the manifest unverifiable rather than opening an exception. **The free tier becomes a capped trial**, since ~$14/project cannot be subsidised indefinitely. Per-project cost rises ~17×, which is the price of not being a landlord and the reason metering stops being deferred work. Mostly a DELETION: [ADR 0306](0306-instance-supervision-is-a-seam-with-systemd-as-the-default.md)'s supervision seam is not extended (Render supervises its own processes), [ADR 0285](0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md) stays true and stops being load-bearing with its queued resize held rather than spent, and the hub's offsite backup becomes the highest-consequence open item. Rejected: AWS/GCP (2–3× the cost for a bare instance, plus an IAM and billing surface to learn — maintenance added where the goal is to remove it); keeping the shared box and fixing only the cookie (cheaper, closes the finding, leaves the landlord runner and answers nothing else); a Vercel/Netlify/Render adapter layer (speculative pluggability); Bongos as the secrets vault pushing values through platform APIs.](0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md) | hosting / provisioning / cost |
418
418
  | 0324 | [**The public npm CLI channel is retired** ([task 1004144](https://cloudbongos.com/builders#/task/1004144), goal 1000091 — BONGOS-V2). The owner deleted `@cloudbongos/cli` from the registry on 2026-09-22 (`npm view` → E404; the `@cloudbongos` scope is empty). This supersedes [ADR 0258](0258-the-public-cli-is-a-generated-client-package-not-the-published-core.md) and [ADR 0292](0292-both-install-paths-stay-and-vendoring-is-the-production-one.md) §2 — the latter titled "`@cloudbongos/cli` STAYS, on a new justification" and written *because* task 1003872 proposed removing it once the public core flip dissolved its original reason. 0292's measured argument (~0.5 MB front door vs a 46 MB core pull to print a task list) is still true and is NOT what changed; the owner's decision about carrying a public channel at all is. `scripts/gds/build-cli-package.js` + `tests/cli_package.mjs` are deleted, no doc offers `npx @cloudbongos/cli`, and the way in is the hall at `<origin>/builders` or a checkout. The COMPILED binary channel (`build-bongos.js` → `/downloads/bongos/*` via `config/bongos-cli.json`) is a different artifact and is untouched — the same near-twin task 1003889 warned about. Three tests used the package's FILES manifest as a stand-in for "what a terminal user can run" and now read `bin/bongos.js`'s verb table, which is what that set always meant.](0324-the-public-npm-cli-channel-is-retired.md) | distribution / CLI |
419
419
  | 0325 | [**The managed fleet control plane is the hub we already run** ([task 1001948](https://cloudbongos.com/builders#/task/1001948), goal 1000106 — BONGOS-V2). The spike was written against [ADR 0111](0111-instance-hosting-provisioning-module.md) §5/§7, which deferred the managed offering as **"genuinely greenfield (no fleet registry exists)"** behind two gates — Mercury and a real `bongos upgrade`. Both gates are shipped and **the premise is not**: measuring the call sites first changed the answer, as it did for [task 1004141](https://cloudbongos.com/builders#/task/1004141). The registry is `provisioning_instances` (owner, shape, domain, database, port, lifecycle, cost estimate, module selection, the liveness overlay and the served/available core-version snapshot), with `provisioning_intents` as the queue and `provisioning_events` as the audit trail; the operator is `scripts/gds/provision.js`, the single token-holder draining that queue behind ADR 0111 §2's trust boundary; oversight is `GET /provisioning/fleet` and `/fleet/cost-ledger`; and the update channel is not one mechanism but two shipped ones — the owner's per-instance deploy door ([ADR 0293](0293-the-owners-deploy-door-is-an-intent-the-control-plane-drains.md), health-gated with auto-rollback) and the opt-in patch-only sweep ([ADR 0136](0136-update-channel-subscription-policy.md)). **Decision: there is no greenfield fleet control plane to design, and none will be built — the hub IS it.** It never needed to sit *above* the per-repo databases [ADR 0100](0100-two-repo-instance-model.md) §3 locks: the roster is a hub table and the operator has never read one. The genuinely missing leg is a **platform leg** — `grep -rl RENDER_API scripts modules src` returns nothing, and after [ADR 0323](0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md) a `cloud-host` is a Render service — and this ADR **deliberately specifies none of it**, because that is [task 1004142](https://cloudbongos.com/builders#/task/1004142)'s with those files open and a second spec for one implementation is how two drift. The fleet **driven** and the fleet **observed** are different populations: `byo-host` is in the registry and operated by nobody, its liveness the [ADR 0252](0252-the-self-hosted-visibility-report-is-a-pull-and-auto-appear-buys-a-retraction-lever.md) pull, so every operator surface must state which it counts — a roster that mixes them reports a fleet-wide upgrade as complete while an unknown number were never asked. The operator holds **exactly one** credential and it is **its own** (the platform account token, root-only on the control plane, never the web tier, never per-customer); ADR 0323 D6's "names, never values" rule is about the CUSTOMER's secrets and is unchanged. Two gaps are filed rather than fixed here: the subscription roster living in `config/update-subscriptions.json` is the one fleet fact the roster route cannot report and a restored backup can lose independently of the rows it describes ([task 1004150](https://cloudbongos.com/builders#/task/1004150), a recommendation and not a ruling), and **`costEstimateUsd` returns 0 for every shape but `dedicated`** — measured live, all 24 fleet rows report `$0`, confidently and with no error, for projects about to cost ~\$14/month each ([task 1004148](https://cloudbongos.com/builders#/task/1004148)). **No fleet-wide "upgrade everything" button**: both channels are per-instance by construction, and one action that moves the whole fleet is the shape most likely to take every customer down at once. AGPL posture unchanged — ADR 0111 §5's one-sentence rule survives its own table. Rejected: building the separate control plane §5 described (a second registry and a second operator holding the same rows with nothing keeping them equal); specifying Render here; reading per-instance databases for fleet reporting; and continuing to defer the whole layer, which is now the reason the ledger reads \$0 for a fleet about to cost real money.](0325-the-managed-fleet-control-plane-is-the-hub-we-already-run.md) | hosting / fleet / provisioning |
420
- | 0326 | [**An upgrade may ADD a deny rule to a file the instance owns** ([task 1004156](https://cloudbongos.com/builders#/task/1004156), goal 1000090 — BONGOS-V2). Task 1003966 gave the instance starter its deny rules — no reading `.env` files, keys or session tokens, no shelving with the repo-global stash — and they reached **nobody who was already here**. The starter is written only when `.claude/settings.json` is ABSENT, and `bongos upgrade` materializes from the published package, which excludes that file on purpose; so an upgrade always takes the starter branch, always finds a settings.json already there, and left it alone. Every instance on the allow-only starter from task 1003962 — the live one included — kept it forever. That was deliberate ("rewriting a file the instance owns is the line task 1003962 drew"), and the recorded remedy was manual: re-run `bongos init`, or delete the file. **A manual remedy for a security gap protects the instances whose owners read release notes, which is not the set that needs protecting.** The owner's call: ADD the missing deny rules and NAME each one in the run output. The argument is that a deny rule is **monotonic in the safe direction** — an allow rule widens what the local agent may do, a deny rule can only narrow, so appending one cannot make any instance less safe than it was a moment earlier. That is what makes this the one merge worth doing without asking per-instance, and it extends [ADR 0121](0121-greenfield-vs-brownfield-onboarding-adopt-existing-repo.md) §2's already-licensed "merge OR leave-and-report" from `--adopt` to every materialize. "Never clobber" survives in the sense that was load-bearing: **nothing the instance wrote is removed, reordered or rewritten** — their allow rules, own denies, `ask` list, hooks, env and every key outside `permissions` come back exactly as they were, with new rules appended after. Deliberately NOT done: **no provenance**, so an owner who deletes a rule we add gets it back next upgrade and (deny beating allow in Claude Code's order) cannot override it with an allow — rejected as speculative machinery for a case nobody has hit, revisit on a real complaint; a file that will not parse, or whose `permissions`/`permissions.deny` is not the expected shape, is left BYTE-FOR-BYTE alone and reported, the posture task 1004023 took for the core's own file, because a half-understood rewrite of a permissions file is worse than the gap; and **the allow half is never topped up** — the safety argument is directionality and does not survive being applied to a rule that grants. `materializeClaude` gains `summary.settingsDenyAdded` + `settingsDenyUncheckable`, both printed through `log()` since `upgrade.js` does not iterate `summary.notes`. The test asserting an instance's settings.json comes back byte-identical is superseded by a strictly stronger one — every rule still there, unmoved, deny-only growth — the weaker spelling being what froze the gap in place.](0326-an-upgrade-may-add-a-deny-rule-to-a-file-the-instance-owns.md) | security / instance onboarding |
420
+ | 0326 | [**An upgrade may ADD a deny rule to a file the instance owns** ([task 1004156](https://cloudbongos.com/builders#/task/1004156), goal 1000090 — BONGOS-V2). Task 1003966 gave the instance starter its deny rules — no reading `.env` files, keys or session tokens, no shelving with the repo-global stash — and they reached **nobody who was already here**. The starter is written only when `.claude/settings.json` is ABSENT, and `bongos upgrade` materializes from the published package, which excludes that file on purpose; so an upgrade always takes the starter branch, always finds a settings.json already there, and left it alone. Every instance on the allow-only starter from task 1003962 — the live one included — kept it forever. That was deliberate ("rewriting a file the instance owns is the line task 1003962 drew"), and the recorded remedy was manual: re-run `bongos init`, or delete the file. **A manual remedy for a security gap protects the instances whose owners read release notes, which is not the set that needs protecting.** The owner's call: ADD the missing deny rules and NAME each one in the run output. The argument is that a deny rule is **monotonic in the safe direction** — an allow rule widens what the local agent may do, a deny rule can only narrow, so appending one cannot make any instance less safe than it was a moment earlier. That is what makes this the one merge worth doing without asking per-instance, and it extends [ADR 0121](0121-greenfield-vs-brownfield-onboarding-adopt-existing-repo.md) §2's already-licensed "merge OR leave-and-report" from `--adopt` to every materialize. "Never clobber" survives in the sense that was load-bearing: **nothing the instance wrote is removed, reordered or rewritten** — their allow rules, own denies, `ask` list, hooks, env and every key outside `permissions` come back exactly as they were, with new rules appended after. Deliberately NOT done: **no provenance**, so an owner who deletes a rule we add gets it back next upgrade and (deny beating allow in Claude Code's order) cannot override it with an allow — rejected as speculative machinery for a case nobody has hit, revisit on a real complaint; a file that will not parse, or whose `permissions`/`permissions.deny` is not the expected shape, is left BYTE-FOR-BYTE alone and reported, the posture task 1004023 took for the core's own file, because a half-understood rewrite of a permissions file is worse than the gap; and **the allow half is never topped up** — the safety argument is directionality and does not survive being applied to a rule that grants. `materializeClaude` gains `summary.settingsDenyAdded` + `settingsDenyUncheckable`, both printed through `log()` since `upgrade.js` does not iterate `summary.notes`. The test asserting an instance's settings.json comes back byte-identical is superseded by a strictly stronger one — every rule still there, unmoved, deny-only growth — the weaker spelling being what froze the gap in place. **AMENDED 2026-09-22 by [task 1004158](https://cloudbongos.com/builders#/task/1004158): the two shelf rules are now starter-only and the top-up asks for the CREDENTIAL half alone.** 0326 itself named this exclusion as the remedy if the rule ever bit; the owner took it ahead of that, because the monotonicity argument licenses a rule about reading a secret and does not reach one that guards a workflow — with no provenance and deny beating allow, an instance that loses the shelf cannot give it back. A fresh instance still gets both. The top-up only ever appends, so this is **forward-looking only**: an instance that already took them keeps them, live included.](0326-an-upgrade-may-add-a-deny-rule-to-a-file-the-instance-owns.md) | security / instance onboarding |
@@ -6238,7 +6238,7 @@
6238
6238
  "goals"
6239
6239
  ],
6240
6240
  "summary": "GET /goals/:id/conflicts",
6241
- "description": "GET /goals/:id/conflicts — the server-side task-conflict view (task 1736). Replaces the hall Goals page's client-side exact-string approximation (the page feature-detects on 404): glob-aware touches[] intersection via the canonical path-match machinery, claim-aware severity, and cross-goal overlaps against other OPEN goals sharing scope_modules. Detection only — resolution is the POST below. rank: any-builder — a read; the same audience as GET /goals/:id.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
6241
+ "description": "GET /goals/:id/conflicts — the server-side task-conflict view (task 1736). Replaces the hall Goals page's client-side exact-string approximation (the page feature-detects on 404): glob-aware touches[] intersection via the canonical path-match machinery, claim-aware severity, and cross-goal overlaps against other OPEN goals sharing scope_modules. Detection only — resolution is the POST below. rank: any-builder — a read, GET /goals/:id's audience.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
6242
6242
  "x-rank": "any-builder",
6243
6243
  "x-source": "modules/lifecycle/routes/goals.js",
6244
6244
  "parameters": [
@@ -6346,6 +6346,51 @@
6346
6346
  ]
6347
6347
  }
6348
6348
  },
6349
+ "/goals/{id}/dependency-forest": {
6350
+ "get": {
6351
+ "operationId": "get_goals_id_dependency_forest",
6352
+ "tags": [
6353
+ "goals"
6354
+ ],
6355
+ "summary": "GET /goals/:id/dependency-forest",
6356
+ "description": "GET /goals/:id/dependency-forest, the per-goal task dependency drill-down (task 1003480, ADR 0227 §6.4).\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
6357
+ "x-rank": "any-builder",
6358
+ "x-source": "modules/lifecycle/routes/goals.js",
6359
+ "parameters": [
6360
+ {
6361
+ "name": "id",
6362
+ "in": "path",
6363
+ "required": true,
6364
+ "schema": {
6365
+ "type": "string"
6366
+ },
6367
+ "description": "Path parameter `id`."
6368
+ }
6369
+ ],
6370
+ "responses": {
6371
+ "200": {
6372
+ "description": "Success."
6373
+ },
6374
+ "400": {
6375
+ "$ref": "#/components/responses/BadRequest"
6376
+ },
6377
+ "401": {
6378
+ "$ref": "#/components/responses/Unauthorized"
6379
+ },
6380
+ "403": {
6381
+ "$ref": "#/components/responses/Forbidden"
6382
+ },
6383
+ "404": {
6384
+ "$ref": "#/components/responses/NotFound"
6385
+ }
6386
+ },
6387
+ "security": [
6388
+ {
6389
+ "builderSession": []
6390
+ }
6391
+ ]
6392
+ }
6393
+ },
6349
6394
  "/goals/{id}/invitations": {
6350
6395
  "post": {
6351
6396
  "operationId": "post_goals_id_invitations",
@@ -26015,7 +26060,7 @@
26015
26060
  "description": "A required dependency/feature is not configured or is temporarily down."
26016
26061
  }
26017
26062
  },
26018
- "x-endpoint-count": 409,
26063
+ "x-endpoint-count": 410,
26019
26064
  "x-schema-count": 448,
26020
26065
  "x-undocumented-bodies": 9,
26021
26066
  "x-response-schemas": 304,