@bongos/core 1.19.510 → 1.19.512
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 +48 -38
- package/config/branding.neutral.json +3 -2
- package/docs/adr/0233-a-copy-proposal-is-a-task-carrying-a-patch.md +16 -2
- package/docs/adr/0238-the-join-door-is-a-provisioning-settings-policy-key.md +57 -0
- package/docs/adr/README.md +1 -0
- package/docs/copy-inventory.md +124 -118
- package/docs/copy-registry.json +223 -158
- package/docs/module-api-changelog.md +4 -0
- package/modules/copy-desk/proposals.js +13 -0
- package/modules/copy-desk/routes/copy-desk.js +9 -2
- package/modules/copy-desk/tests/copy_proposals.mjs +41 -0
- package/modules/lifecycle/conflict-resolve.js +59 -3
- package/modules/provisioning/provisioning.js +27 -1
- package/modules/provisioning/tests/provisioning.mjs +17 -14
- package/modules/public-landing/public/assets/cosmos.css +3 -1
- package/modules/public-landing/public/projects.html +125 -0
- package/modules/ui-design/kit/serve.js +4 -3
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/copy-apply.js +54 -4
- package/scripts/gds/git-merge-regen.js +18 -0
- package/src/branding.js +9 -0
- package/src/module-api.js +1 -1
- package/tests/conflict_resolve.mjs +118 -4
- package/tests/currency_label.mjs +5 -4
- package/tests/git_merge_regen.mjs +128 -2
- package/tests/project_door_ui.mjs +267 -0
- package/tests/projects_hub.mjs +86 -1
- package/tests/projects_hub_pre_uat.mjs +4 -1
- package/tests/provision_settings_apply.mjs +8 -7
- package/tests/provisioning_settings.mjs +65 -6
- package/tests/provisioning_settings_apply.mjs +3 -3
- package/tests/provisioning_settings_env.mjs +10 -8
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.512",
|
|
6
|
+
"core_contract": "1.19.512",
|
|
7
|
+
"source_commit": "b80dfcced279a5dc071290fdc22ea3aeec3dfd70",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-03T18:
|
|
9
|
+
"built_at": "2026-09-03T18:54:15.905Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 429,
|
|
13
13
|
"agent_docs_stubbed": 24,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1952,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2405,
|
|
20
|
+
"tree_sha256": "382fde42e266c00ccb3f8289a487a709a55e3e606c5fc80a395a49c667f42e96",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -397,7 +397,7 @@
|
|
|
397
397
|
{
|
|
398
398
|
"path": "config/branding.neutral.json",
|
|
399
399
|
"mode": "0000644",
|
|
400
|
-
"sha256": "
|
|
400
|
+
"sha256": "c33b285d6a88d38eb15e062e0da845d29ec2aa7e6bfc5a4d321a3a49e0b7b2f9"
|
|
401
401
|
},
|
|
402
402
|
{
|
|
403
403
|
"path": "config/design-tokens.neutral.json",
|
|
@@ -1672,7 +1672,7 @@
|
|
|
1672
1672
|
{
|
|
1673
1673
|
"path": "docs/adr/0233-a-copy-proposal-is-a-task-carrying-a-patch.md",
|
|
1674
1674
|
"mode": "0000644",
|
|
1675
|
-
"sha256": "
|
|
1675
|
+
"sha256": "ab2970017b4776ee7d0be1ee22933d86123f57b8935a1f1bb0844e5f367fae07"
|
|
1676
1676
|
},
|
|
1677
1677
|
{
|
|
1678
1678
|
"path": "docs/adr/0234-idea-routing-capture-time-promotion-landing-matrix-homeless-inbox.md",
|
|
@@ -1694,10 +1694,15 @@
|
|
|
1694
1694
|
"mode": "0000644",
|
|
1695
1695
|
"sha256": "10534e533c2c70baf670048fb38e18ddedb36925312806afbae9941a9c958667"
|
|
1696
1696
|
},
|
|
1697
|
+
{
|
|
1698
|
+
"path": "docs/adr/0238-the-join-door-is-a-provisioning-settings-policy-key.md",
|
|
1699
|
+
"mode": "0000644",
|
|
1700
|
+
"sha256": "fb05e36eb48861f2b32a38da04eba839f8141bd52ff9d913070bc589c115f9d5"
|
|
1701
|
+
},
|
|
1697
1702
|
{
|
|
1698
1703
|
"path": "docs/adr/README.md",
|
|
1699
1704
|
"mode": "0000644",
|
|
1700
|
-
"sha256": "
|
|
1705
|
+
"sha256": "1ec64297bc6f1858e1315b049d143de5390fb5b5843e0414f536d4fa2a42d7d5"
|
|
1701
1706
|
},
|
|
1702
1707
|
{
|
|
1703
1708
|
"path": "docs/api-reference.md",
|
|
@@ -1732,12 +1737,12 @@
|
|
|
1732
1737
|
{
|
|
1733
1738
|
"path": "docs/copy-inventory.md",
|
|
1734
1739
|
"mode": "0000644",
|
|
1735
|
-
"sha256": "
|
|
1740
|
+
"sha256": "519630f4bda79510d5bc3f131d28ad1915517b05ec4f8dadf3081d0845bb9440"
|
|
1736
1741
|
},
|
|
1737
1742
|
{
|
|
1738
1743
|
"path": "docs/copy-registry.json",
|
|
1739
1744
|
"mode": "0000644",
|
|
1740
|
-
"sha256": "
|
|
1745
|
+
"sha256": "b920c2349d109b9c75c22396095d011c90edc2614f844ac27a4b3f116c805278"
|
|
1741
1746
|
},
|
|
1742
1747
|
{
|
|
1743
1748
|
"path": "docs/design/apex-pass-2-direction.md",
|
|
@@ -2582,7 +2587,7 @@
|
|
|
2582
2587
|
{
|
|
2583
2588
|
"path": "docs/module-api-changelog.md",
|
|
2584
2589
|
"mode": "0000644",
|
|
2585
|
-
"sha256": "
|
|
2590
|
+
"sha256": "26d8b7f65d94927b0f382674b0421d8b5694856fd619b5c8fc57f1147f17b20d"
|
|
2586
2591
|
},
|
|
2587
2592
|
{
|
|
2588
2593
|
"path": "docs/modules-contract.md",
|
|
@@ -3697,7 +3702,7 @@
|
|
|
3697
3702
|
{
|
|
3698
3703
|
"path": "modules/copy-desk/proposals.js",
|
|
3699
3704
|
"mode": "0000644",
|
|
3700
|
-
"sha256": "
|
|
3705
|
+
"sha256": "de674674cf2f80fb36569cad67be4f1ba3a94e938b690b8f1d0c96446df346bf"
|
|
3701
3706
|
},
|
|
3702
3707
|
{
|
|
3703
3708
|
"path": "modules/copy-desk/registry.js",
|
|
@@ -3707,7 +3712,7 @@
|
|
|
3707
3712
|
{
|
|
3708
3713
|
"path": "modules/copy-desk/routes/copy-desk.js",
|
|
3709
3714
|
"mode": "0000644",
|
|
3710
|
-
"sha256": "
|
|
3715
|
+
"sha256": "7231f021bb49bd78cb247a6d8e8a0f6e9256ade52f2f9bb1438b54b6ddf400f8"
|
|
3711
3716
|
},
|
|
3712
3717
|
{
|
|
3713
3718
|
"path": "modules/copy-desk/tests/copy_flags.mjs",
|
|
@@ -3722,7 +3727,7 @@
|
|
|
3722
3727
|
{
|
|
3723
3728
|
"path": "modules/copy-desk/tests/copy_proposals.mjs",
|
|
3724
3729
|
"mode": "0000644",
|
|
3725
|
-
"sha256": "
|
|
3730
|
+
"sha256": "e435042dea0436e0f4fa7f2a95b16b8647ff0723290741e26776e76c9d1c6973"
|
|
3726
3731
|
},
|
|
3727
3732
|
{
|
|
3728
3733
|
"path": "modules/copy-desk/tests/copy_queue.mjs",
|
|
@@ -5237,7 +5242,7 @@
|
|
|
5237
5242
|
{
|
|
5238
5243
|
"path": "modules/lifecycle/conflict-resolve.js",
|
|
5239
5244
|
"mode": "0000644",
|
|
5240
|
-
"sha256": "
|
|
5245
|
+
"sha256": "1a281a4233b3805550a05ed6bd38b163ab18db658d48675f055b90d2ba722a16"
|
|
5241
5246
|
},
|
|
5242
5247
|
{
|
|
5243
5248
|
"path": "modules/lifecycle/criterion-suggest.js",
|
|
@@ -5972,7 +5977,7 @@
|
|
|
5972
5977
|
{
|
|
5973
5978
|
"path": "modules/provisioning/provisioning.js",
|
|
5974
5979
|
"mode": "0000644",
|
|
5975
|
-
"sha256": "
|
|
5980
|
+
"sha256": "67eb2963688cba73fe846b354070e1e43333971425f603b1e84b45552b5381f0"
|
|
5976
5981
|
},
|
|
5977
5982
|
{
|
|
5978
5983
|
"path": "modules/provisioning/rate-limit.js",
|
|
@@ -5997,7 +6002,7 @@
|
|
|
5997
6002
|
{
|
|
5998
6003
|
"path": "modules/provisioning/tests/provisioning.mjs",
|
|
5999
6004
|
"mode": "0000644",
|
|
6000
|
-
"sha256": "
|
|
6005
|
+
"sha256": "03e4ba2a5625ac321a95392c91b8e90cc6f7e235dc9a0c4bbcb742cbd459b083"
|
|
6001
6006
|
},
|
|
6002
6007
|
{
|
|
6003
6008
|
"path": "modules/provisioning/ui/.gitkeep",
|
|
@@ -6237,7 +6242,7 @@
|
|
|
6237
6242
|
{
|
|
6238
6243
|
"path": "modules/public-landing/public/assets/cosmos.css",
|
|
6239
6244
|
"mode": "0000644",
|
|
6240
|
-
"sha256": "
|
|
6245
|
+
"sha256": "bb2fa6d0a1c87aeb3e08038faf1d3ae0f06a1a41f9fe6d83f357146bc7e73045"
|
|
6241
6246
|
},
|
|
6242
6247
|
{
|
|
6243
6248
|
"path": "modules/public-landing/public/assets/verro-medallion.png",
|
|
@@ -6497,7 +6502,7 @@
|
|
|
6497
6502
|
{
|
|
6498
6503
|
"path": "modules/public-landing/public/projects.html",
|
|
6499
6504
|
"mode": "0000644",
|
|
6500
|
-
"sha256": "
|
|
6505
|
+
"sha256": "8af95785b813eb27e6356fa717a4f20cb73f1220dd9ce15de2d62a18631afdd3"
|
|
6501
6506
|
},
|
|
6502
6507
|
{
|
|
6503
6508
|
"path": "modules/public-landing/public/projects.probes.json",
|
|
@@ -6932,7 +6937,7 @@
|
|
|
6932
6937
|
{
|
|
6933
6938
|
"path": "modules/ui-design/kit/serve.js",
|
|
6934
6939
|
"mode": "0000644",
|
|
6935
|
-
"sha256": "
|
|
6940
|
+
"sha256": "efb50e76596a5ad009dab58d8e12b590382cc6bf1f819d0ab447cb14a4b4d6a3"
|
|
6936
6941
|
},
|
|
6937
6942
|
{
|
|
6938
6943
|
"path": "modules/ui-design/kit/tells.js",
|
|
@@ -7242,12 +7247,12 @@
|
|
|
7242
7247
|
{
|
|
7243
7248
|
"path": "package-lock.json",
|
|
7244
7249
|
"mode": "0000644",
|
|
7245
|
-
"sha256": "
|
|
7250
|
+
"sha256": "63c01590a251d6ec16dde2e0be5d843e12ff40955733268f6d322718f1e807be"
|
|
7246
7251
|
},
|
|
7247
7252
|
{
|
|
7248
7253
|
"path": "package.json",
|
|
7249
7254
|
"mode": "0000644",
|
|
7250
|
-
"sha256": "
|
|
7255
|
+
"sha256": "4bbced9436a337f2b2ea9fe5445bf6f9f94354ffeda4d1b1766cd12f4e9f74d9"
|
|
7251
7256
|
},
|
|
7252
7257
|
{
|
|
7253
7258
|
"path": "public-docs/index.html",
|
|
@@ -7532,7 +7537,7 @@
|
|
|
7532
7537
|
{
|
|
7533
7538
|
"path": "scripts/gds/copy-apply.js",
|
|
7534
7539
|
"mode": "0000644",
|
|
7535
|
-
"sha256": "
|
|
7540
|
+
"sha256": "05326c8016794742acc10a95ae0f1c7b630581231a7f0f86aef449a8c17c2c40"
|
|
7536
7541
|
},
|
|
7537
7542
|
{
|
|
7538
7543
|
"path": "scripts/gds/copy-inventory.js",
|
|
@@ -7762,7 +7767,7 @@
|
|
|
7762
7767
|
{
|
|
7763
7768
|
"path": "scripts/gds/git-merge-regen.js",
|
|
7764
7769
|
"mode": "0000644",
|
|
7765
|
-
"sha256": "
|
|
7770
|
+
"sha256": "f6f9c93faeb589d07105027655e42bd842d8d766119812b16bd534d58aa50816"
|
|
7766
7771
|
},
|
|
7767
7772
|
{
|
|
7768
7773
|
"path": "scripts/gds/go-live.js",
|
|
@@ -8897,7 +8902,7 @@
|
|
|
8897
8902
|
{
|
|
8898
8903
|
"path": "src/branding.js",
|
|
8899
8904
|
"mode": "0000644",
|
|
8900
|
-
"sha256": "
|
|
8905
|
+
"sha256": "e603fa0fabe63b7150f649db1ecaa50ef0908a803396e808cf212f4378cbb053"
|
|
8901
8906
|
},
|
|
8902
8907
|
{
|
|
8903
8908
|
"path": "src/build-info.js",
|
|
@@ -8917,7 +8922,7 @@
|
|
|
8917
8922
|
{
|
|
8918
8923
|
"path": "src/module-api.js",
|
|
8919
8924
|
"mode": "0000644",
|
|
8920
|
-
"sha256": "
|
|
8925
|
+
"sha256": "379afd7239016bc0e504bc3b73596c35cb71a074a6b743f32c76c5a24493a44e"
|
|
8921
8926
|
},
|
|
8922
8927
|
{
|
|
8923
8928
|
"path": "src/module-loader/catalog.js",
|
|
@@ -9512,7 +9517,7 @@
|
|
|
9512
9517
|
{
|
|
9513
9518
|
"path": "tests/conflict_resolve.mjs",
|
|
9514
9519
|
"mode": "0000644",
|
|
9515
|
-
"sha256": "
|
|
9520
|
+
"sha256": "7ad7be450b9350e7888f075944888ddf2a0e2d57a23568f410a9ff17bd340dec"
|
|
9516
9521
|
},
|
|
9517
9522
|
{
|
|
9518
9523
|
"path": "tests/connections_api.mjs",
|
|
@@ -9617,7 +9622,7 @@
|
|
|
9617
9622
|
{
|
|
9618
9623
|
"path": "tests/currency_label.mjs",
|
|
9619
9624
|
"mode": "0000644",
|
|
9620
|
-
"sha256": "
|
|
9625
|
+
"sha256": "09a937b9a9550b77659c0e60b3bc44c45378fdb3545c510d7579d4711518a733"
|
|
9621
9626
|
},
|
|
9622
9627
|
{
|
|
9623
9628
|
"path": "tests/cutover_codemod.mjs",
|
|
@@ -9907,7 +9912,7 @@
|
|
|
9907
9912
|
{
|
|
9908
9913
|
"path": "tests/git_merge_regen.mjs",
|
|
9909
9914
|
"mode": "0000644",
|
|
9910
|
-
"sha256": "
|
|
9915
|
+
"sha256": "f4e7ab5eec2d13404080c408c0a70819c4b398b38fa61474b9c4cc57069d119b"
|
|
9911
9916
|
},
|
|
9912
9917
|
{
|
|
9913
9918
|
"path": "tests/github_push_ancestry.mjs",
|
|
@@ -11099,6 +11104,11 @@
|
|
|
11099
11104
|
"mode": "0000644",
|
|
11100
11105
|
"sha256": "6a7a223a678b56333befaed017429e5b9d8a81e4405efae72a23355902517d9d"
|
|
11101
11106
|
},
|
|
11107
|
+
{
|
|
11108
|
+
"path": "tests/project_door_ui.mjs",
|
|
11109
|
+
"mode": "0000644",
|
|
11110
|
+
"sha256": "29f59ab64b231a7ec5d17a3e784d451f75d80c19efd4cb6a523d263e8f88b581"
|
|
11111
|
+
},
|
|
11102
11112
|
{
|
|
11103
11113
|
"path": "tests/project_stats.mjs",
|
|
11104
11114
|
"mode": "0000644",
|
|
@@ -11112,12 +11122,12 @@
|
|
|
11112
11122
|
{
|
|
11113
11123
|
"path": "tests/projects_hub.mjs",
|
|
11114
11124
|
"mode": "0000644",
|
|
11115
|
-
"sha256": "
|
|
11125
|
+
"sha256": "eeb54786e5cb1211f0036d7c72119082fe47c023502c017b0092c6b22f60f0c1"
|
|
11116
11126
|
},
|
|
11117
11127
|
{
|
|
11118
11128
|
"path": "tests/projects_hub_pre_uat.mjs",
|
|
11119
11129
|
"mode": "0000644",
|
|
11120
|
-
"sha256": "
|
|
11130
|
+
"sha256": "fef872ed80c7efdbf37e0672067f555c76aa17368a515afb99b1fa7e7ca3c4b4"
|
|
11121
11131
|
},
|
|
11122
11132
|
{
|
|
11123
11133
|
"path": "tests/promote_goal_id.mjs",
|
|
@@ -11142,7 +11152,7 @@
|
|
|
11142
11152
|
{
|
|
11143
11153
|
"path": "tests/provision_settings_apply.mjs",
|
|
11144
11154
|
"mode": "0000644",
|
|
11145
|
-
"sha256": "
|
|
11155
|
+
"sha256": "9a04386b3047177bb55b6d392db88764f06f92e5c0b20c16ed9e0f8ce520b07d"
|
|
11146
11156
|
},
|
|
11147
11157
|
{
|
|
11148
11158
|
"path": "tests/provisioning_default_domain.mjs",
|
|
@@ -11172,17 +11182,17 @@
|
|
|
11172
11182
|
{
|
|
11173
11183
|
"path": "tests/provisioning_settings.mjs",
|
|
11174
11184
|
"mode": "0000644",
|
|
11175
|
-
"sha256": "
|
|
11185
|
+
"sha256": "6f78a45d0c68a0da780d40c602f23bca3d48a80524218d4d7d311b2f8574dbee"
|
|
11176
11186
|
},
|
|
11177
11187
|
{
|
|
11178
11188
|
"path": "tests/provisioning_settings_apply.mjs",
|
|
11179
11189
|
"mode": "0000644",
|
|
11180
|
-
"sha256": "
|
|
11190
|
+
"sha256": "c453bab3768953257be481cb8df700530f964e167fdca073cf4ae69892930a79"
|
|
11181
11191
|
},
|
|
11182
11192
|
{
|
|
11183
11193
|
"path": "tests/provisioning_settings_env.mjs",
|
|
11184
11194
|
"mode": "0000644",
|
|
11185
|
-
"sha256": "
|
|
11195
|
+
"sha256": "4f51ea07fe3d80d52d6998547b028bec0a97267210faeffd75d9a47f7b5643e8"
|
|
11186
11196
|
},
|
|
11187
11197
|
{
|
|
11188
11198
|
"path": "tests/provisioning_slug_available.mjs",
|
|
@@ -27,9 +27,10 @@
|
|
|
27
27
|
"idp": false
|
|
28
28
|
},
|
|
29
29
|
"project": {
|
|
30
|
-
"_note": "Per-project policy the PLATFORM decides and this instance OBEYS (task 1003139, goal 1000058). Stored on the platform's provisioning_instances.settings; written here as <PREFIX>_PLATFORM_VISIBILITY / <PREFIX>_JOINABILITY by the provisioning runner, which re-writes web.env and restarts the service when the owner changes one. The defaults are TODAY's behavior, so an instance that never receives a push — a self-hosted one, or one provisioned before this channel — keeps behaving exactly as it does now. platformVisibility: public | gated. joinability: open | apply | invite_only.",
|
|
30
|
+
"_note": "Per-project policy the PLATFORM decides and this instance OBEYS (task 1003139, goal 1000058). Stored on the platform's provisioning_instances.settings; written here as <PREFIX>_PLATFORM_VISIBILITY / <PREFIX>_JOINABILITY / <PREFIX>_VISIBILITY by the provisioning runner, which re-writes web.env and restarts the service when the owner changes one. The defaults are TODAY's behavior, so an instance that never receives a push — a self-hosted one, or one provisioned before this channel — keeps behaving exactly as it does now. platformVisibility (who may READ this instance's own pages): public | gated. joinability (HOW someone becomes a member): open | apply | invite_only. visibility (the join door, ADR 0182 D1 — how this project stands on the platform's map): public | private | stealth; it does NOT decide whether the project is on the map, publishability does.",
|
|
31
31
|
"platformVisibility": "public",
|
|
32
|
-
"joinability": "apply"
|
|
32
|
+
"joinability": "apply",
|
|
33
|
+
"visibility": "public"
|
|
33
34
|
},
|
|
34
35
|
"repo": {
|
|
35
36
|
"owner": "",
|
|
@@ -114,7 +114,12 @@ afterwards buys a port widening and a failure mode for a string.
|
|
|
114
114
|
So the bare call, `node scripts/gds/copy-apply.js`, acts on **the task the caller is holding**.
|
|
115
115
|
Holding no claim is a named refusal that says to claim it first. The command may only run under a
|
|
116
116
|
claim — that is the whole feature — and a claim already names a task, so the constraint and the
|
|
117
|
-
ergonomics are the same fact. `--task <id>` remains for someone holding several
|
|
117
|
+
ergonomics are the same fact. `--task <id>` remains for someone holding several — and it is *checked*
|
|
118
|
+
against the same claim list the bare call reads, refusing `claim_not_held` for a task the caller does
|
|
119
|
+
not hold. It shipped unchecked (task 1003493 fixed it): a rule stated in this ADR, in the module's
|
|
120
|
+
`CLAUDE.md` and in the script's own header, enforced on one of its two paths, is the kind of claim
|
|
121
|
+
that rots. The one path with no claim to prove is `--patch-file`, a local JSON patch used by the
|
|
122
|
+
tests, which lands nothing a proposal task authorised.
|
|
118
123
|
|
|
119
124
|
### 7. Proposing is open to every rank, and lands in `backlog`
|
|
120
125
|
|
|
@@ -130,7 +135,16 @@ before anyone can claim it. Landing it `ready` would let anyone with an opinion
|
|
|
130
135
|
## Consequences
|
|
131
136
|
|
|
132
137
|
- **The write surface of `copy-desk` is now three verbs, not two**, and `tests/copy_no_cms.mjs` says so
|
|
133
|
-
in a diff a reviewer can see — which is exactly what R02 built that list for.
|
|
138
|
+
in a diff a reviewer can see — which is exactly what R02 built that list for. To be exact about what
|
|
139
|
+
that test proves (task 1003493): it pins that no route **in this module** can write a rendered
|
|
140
|
+
string, which is narrower than "no route can write a rendered string to production directly". That
|
|
141
|
+
is the right scope for the feature — the module is the write surface this ADR adds — but the wider
|
|
142
|
+
claim is not something a module-scoped test can carry, and reading it as if it were is how a
|
|
143
|
+
no-CMS guarantee quietly becomes an assumption.
|
|
144
|
+
- **A proposal lands only in the version being BUILT.** `proposals.resolveTargetVersion` returns the
|
|
145
|
+
`building` version or `null`, and the route refuses `no_open_version` — there is deliberately no
|
|
146
|
+
"first version in the list" fallback (task 1003493), because that row can be a shipped version, and
|
|
147
|
+
a proposal landing on sealed history is worse than a proposal that was not accepted.
|
|
134
148
|
- **`scripts/gds/copy-apply.js` is the only thing in this feature that writes a surface file**, it is a
|
|
135
149
|
command rather than a route, and the test asserts no file in the module's request path imports it.
|
|
136
150
|
Naming it is fine and required: the task tells its claimer the exact line to run.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# ADR 0238 — The join door is a provisioning SETTINGS key, and the hub column stays the cache
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-09-03
|
|
5
|
+
- **Task:** [task 1002324](https://cloudbongos.com/builders#/task/1002324) (BONGOS-V1, goal 1000046 — *Project creation — visibility and customization*, criterion `proj-visibility-states`)
|
|
6
|
+
- **Deciders:** jaxri (Archon), Claude
|
|
7
|
+
- **Related:** [ADR 0182](<redacted>.md) (the two axes — this ADR only says *where* R05's half lives) · [ADR 0141](<redacted>.md) §3 (authority stays local) · [ADR 0190](<redacted>.md) (a pushed setting takes effect by a restart) · [ADR 0192](<redacted>.md) (`platform_visibility`, the neighbouring axis)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Context
|
|
12
|
+
|
|
13
|
+
ADR 0182 D5 says R05 "**moves host** — an instance surface, not a hub one … The setting lives in the project's own hall settings, gated by the project's own ranks." It does not say through which channel, and on the tree as R05 was claimed those two sentences pointed at nothing that existed:
|
|
14
|
+
|
|
15
|
+
- **The hub catalog column that R04 shipped** — `platform_identity_projects.visibility` — is written by **no route at all**. Every write on `routes/projects.js` is `requirePermission('project.curate')`, an Archon/Metic *curation* gate with no ownership check, and `upsertProject` deliberately omits `visibility` from its column list (ADR 0182 D5: a client re-registration must not reset an owner's door). `owner_github_id` on that table is commented "advisory attribution (not an authority)". So the hub has, by design, no way to authorise an owner's visibility change.
|
|
16
|
+
- **"The project's own hall settings"** is a page that does not exist. `modules/hall-ui/public/settings.html` is per-**builder** (account / access / dev box / preferences); the hall's `my-projects.html` was deleted in task 1003308 ("the hub owns it"). Building a new project-settings page on the instance, plus the hub sync (R08, unshipped), to carry one radio group would ship a control that changes nothing anyone can see.
|
|
17
|
+
|
|
18
|
+
Meanwhile the surface where an owner **already manages a project** — `/projects?view=manage` on the hub — carries two adjacent settings that are the same shape as this one, on a channel built for exactly this: `provisioning_instances.settings`, a jsonb "dumb store" whose migration header names the keys the manage-page wave adds "one task at a time — *platform visibility, joinability, repo visibility, …*".
|
|
19
|
+
|
|
20
|
+
## Decision
|
|
21
|
+
|
|
22
|
+
**The owner's join door is a key in `provisioning.SETTINGS_VOCAB`** — `visibility ∈ (public, private, stealth)`, default `public` — set through the existing `PATCH /api/gds/provisioning/instances/:id/settings`, and rendered as one more section on the manage view.
|
|
23
|
+
|
|
24
|
+
That one line buys the whole of R05 from machinery already in place and already proven:
|
|
25
|
+
|
|
26
|
+
| What R05 needs | What the channel already does |
|
|
27
|
+
|---|---|
|
|
28
|
+
| only the owner may set it | `instanceAccessDenied` — own-scoped, Archon override, **404** (never 403) so there is no existence leak |
|
|
29
|
+
| the enum is enforced by the server | the route's schema **is** the vocabulary (`vocabSchema(SETTINGS_VOCAB)`), so a value off the list is a 400 and a key added to the vocabulary can never drift out of the validator |
|
|
30
|
+
| an audit trail | a `provisioning_events` row with the actor, `api:archon:<id>` on an override |
|
|
31
|
+
| the value reaches the instance | `settingsEnvVars` derives `<PREFIX>_VISIBILITY` from the vocabulary automatically; the runner patches `web.env` in place and restarts |
|
|
32
|
+
| the instance can read it | `src/branding.js` maps that env onto `project.visibility` and publishes it in the public manifest, beside its two neighbours |
|
|
33
|
+
|
|
34
|
+
### It is a POLICY key, not a platform-drawn one
|
|
35
|
+
|
|
36
|
+
The same store holds `PLANET_VOCAB` — keys the platform *draws* with, which have no default, ride no env and restart nothing. The door could have gone there (today only the map reads it, and the restart currently applies an env nothing consumes yet). It does not, for two reasons that outlive today:
|
|
37
|
+
|
|
38
|
+
1. **ADR 0182 D6 makes the door the ceiling `joinability` may only narrow**, and D1/D5 put admission authority on the instance. The instance is the thing that must refuse a self-join on `private` and refuse an application *at the door* on `stealth` (R11/R12). A door the instance cannot read cannot be a ceiling.
|
|
39
|
+
2. **A key with a default belongs in the defaulted vocabulary.** `effectiveSettings` gives `SETTINGS_VOCAB` keys today's behaviour for rows that predate them, which is exactly what ADR 0182's migration default (`'public'`, "existing rows keep today's behaviour") asks for. `PLANET_VOCAB` keys are nullable — null means *the platform picks* — and there is no such thing as "the platform picks your join door".
|
|
40
|
+
|
|
41
|
+
### The hub column is unchanged, and stays the cache
|
|
42
|
+
|
|
43
|
+
Nothing here writes `platform_identity_projects.visibility`. ADR 0182 D5 already calls that row a cache fed by the instance's report, and **R08 is that report** — this ADR only fixes where the truth it will carry is written. Until R08 ships, the map still reads the old `featured + active` predicate (R06's job), so an owner's pick is stored, validated, audited and delivered to their instance, and does not yet move the orb. That gap is R06/R08's, named here so the next builder does not go looking for a bug.
|
|
44
|
+
|
|
45
|
+
## Consequences
|
|
46
|
+
|
|
47
|
+
- Adding one key to `SETTINGS_VOCAB` propagates through every derived surface at once — the GET's `options`, the PATCH's validator, the env transport, the push receipt. The lockstep tests that had hardcoded the two-key world (`provisioning_settings*.mjs`, `provision_settings_apply.mjs`, the module's own unit file, the kit stub) all had to be told about the third key; that they broke is the guard working.
|
|
48
|
+
- The manage section deliberately has **no "what your project runs" read-back line**, unlike its two siblings. An already-deployed project reports no door until it takes a release carrying `project.visibility`, and a line that could only ever say *"this project's server doesn't report this setting yet"* is worse than no line. It arrives with the enforcement.
|
|
49
|
+
- `config/branding.neutral.json` gains `project.visibility: "public"`, so a self-hosted instance that never receives a push behaves exactly as it does now.
|
|
50
|
+
- The three settings on that page now answer three different questions and are easy to confuse. The mitigation is copy, not naming: each section's fold says what it is *not* ("separate from 'Who can see it' above — that one is the door on the project's own pages"), and the vocabulary comment in `provisioning.js` lays the three side by side.
|
|
51
|
+
|
|
52
|
+
## Alternatives rejected
|
|
53
|
+
|
|
54
|
+
- **A new owner-scoped write route on the hub catalog** (`PATCH /projects/:id/visibility`). Rejected: it would make the hub authorise a visibility change, which ADR 0182 D5 forbids in as many words, and it would have to invent per-project ownership authority on a table whose owner column is documented as advisory.
|
|
55
|
+
- **A new project-settings page on the instance's hall.** Rejected as the literal reading of D5: it is the right *long-run* home once a project has instance-side settings worth a page, but on its own today it is a control with no consumer — the map is fed by the hub, and the sync that would connect them (R08) is unshipped. The manage view is where an owner already goes, and the same value reaches the instance anyway through the push.
|
|
56
|
+
- **A `visibility` column on `provisioning_instances`, beside `description`.** Rejected: `description` earned a column because it is half the publish gate and is read by SQL; the door is a closed-vocabulary knob, which is exactly what the jsonb store plus a code-side vocabulary exists for (migration `provisioning_009`'s own reasoning: "a new key must not cost a migration each time").
|
|
57
|
+
- **Putting it in `PLANET_VOCAB` to avoid the restart.** Rejected above — it would buy a quieter save today and cost the ceiling rule tomorrow.
|
package/docs/adr/README.md
CHANGED
|
@@ -324,6 +324,7 @@ This keeps the decision history honest and traceable.
|
|
|
324
324
|
| 0234 | [**Idea routing: capture-time promotion, the landing matrix, and the homeless-only inbox** ([task 1003064](https://cloudbongos.com/builders#/task/1003064) · goal 1000071, "Idea routing"). Idea 1000771 named the failure: Discord `#bugs` created tasks DIRECTLY against a hardcoded, already-shipped version — no `captured_by`, no lineage, so the ideator-credit lane had nothing to pay against — while every other open idea sat waiting for a human triage pass that dropped its promotion into a version catch-all, because nothing in capture ever named a goal. Records what R01/R02/R05/R07/R13 shipped. `captureIdea` (`modules/ideas/inbox.js`) stays the ONE capture chokepoint — routing is one optional `goalId` argument, never a second creation door — and when the matrix routes, `routeIdeaToGoal` writes the idea row, the task (via the `lifecycle.createTask` port) and an `ideas_task_lineage` row (`origin='route'`) in ONE transaction, so a route can never mint attribution-free work the way `#bugs` did. `modules/ideas/routing.js`'s `decideLanding` is PURE and fail-closed: a bug from a Metic+ filer hard-lands `ready`, a sub-Metic bug or any other kind with a goal soft-lands `backlog`, a Full Idea or `criterion-proposal`/`decision` never routes, no goal named stays the pre-routing inbox path. `authorizeRoutedLanding` is the BINDING gate underneath the matrix's decision — a goal reaching protected scope raises the task's rank floor to Metic and REFUSES a sub-Metic filer outright, while membership is deliberately NOT required (reporting isn't authoring). `core_222`'s `suggested_goal_id` is an advisory hint only — the real home is `tasks.goal_id`, set by construction — and `/idea-triage` (`scripts/gds/triage.js`) now sends `goal_id` on promotion, so the inbox is homeless-only. Divergence recorded: R11 (Discord `#bugs`) hard-lands a linked Metic+ builder's bug immediately through the SAME shared matrix rather than the spec's "always soft-land, pending queue" design, because a local copy of the rank split would only drift from the one definition; and R12 (Discord `#ideas` hints) is NOT YET SHIPPED, so `#ideas` carries no goal hint today despite the interview decision that it should, hint-only, never route.](<redacted>.md) | ideas / lifecycle |
|
|
325
325
|
| 0235 | [**A fix category is `kind='bug'` under a goal, not a new column** ([task 1003215](https://cloudbongos.com/builders#/task/1003215) · goal 1000071, "Idea routing" — the structural half of idea 1000828; task 1003214 shipped the advisory's first quiet case, task 1003157 extended it). A fix for something that should already work needs a home under an existing goal WITHOUT a new goal being born and WITHOUT a stored column restating what `tasks.kind` already says. Not a global "Bugs" goal either: a goal spanning every module makes the [ADR 0086](<redacted>.md) scope wall either everything or nothing, and a goal that can never reach `achieved` (a bug container never finishes) pollutes the exact signal [ADR 0183](<redacted>.md)'s auto-satisfy depends on. Not [ADR 0192 "a category orients and authorises nothing"](<redacted>.md) either, despite the structural resemblance: that decision forbids any gate from branching on a goal's category, while THIS decision is two decision surfaces deliberately branching on `kind='bug'` — `adviseGoal()` (`modules/lifecycle/goal-advisory.js`) goes silent for a bug under all three of its reasons (`no_goal`, `catch_all`, `no_criterion_link` — "a fix is the goal's 'fix category', not a delivery against one of its stated outcomes," its own doc comment) and `countTasksWithoutCriterionLink()` (`modules/lifecycle/db-deps-criteria.js`, surfaced on the hall's goal detail view) excludes `kind='bug'` from its count for the same reason. No hall/status change was needed for GROUPING bugs under a goal: every task card already renders its `kind` as a visible tag and the goal detail view's Filters already offer a `Kind` lens with `bug` as a value — verified against the live code, not built new. Rejected: a `tasks.category`/`fix_category` column or enum, a global cross-module "Bugs" goal, a bug-specific grouping widget beside the general-purpose kind facility that already works.](<redacted>.md) | ideas / lifecycle |
|
|
326
326
|
| 0236 | [**Many ideas → one task: the lineage key is the pair** ([task 1003515](https://cloudbongos.com/builders#/task/1003515) · goal 1000071, "Idea routing" — promoted from idea 1000815; the table was built by [task 1002997](https://cloudbongos.com/builders#/task/1002997)). `ideas_task_lineage.task_id` was the PRIMARY KEY on the reasoning that "a task descends from at most one idea", and `linkIdeaTask` wrote `ON CONFLICT (task_id) DO UPDATE SET idea_id = EXCLUDED.idea_id` — so promoting a SECOND idea into a task that already had a link **silently replaced the first idea's lineage**: no error, no warning, no log line. Two ordinary triage promotes at one task were enough, and it happened on prod on 2026-08-21 (ideas 1000811 + 1000812 → task 1003150). Because this table is the rail [ADR 0172](<redacted>.md) stream 1 pays against, an overwritten row is an **ideator not paid** for work their idea produced, with nothing recording it — and it makes merging duplicate reports into one task a LOSSY move, which is the triage the inbox exists to encourage. The R01 grader named this shape and was told it was unreachable; it was unreachable on the ROUTING path examined and fully reachable on the ordinary PROMOTE path. **Decision: many ideas → one task is a real relation, so the key is the pair** — `ideas_006` re-adds `ideas_task_lineage_pkey` as `PRIMARY KEY (task_id, idea_id)` and both INSERT sites `DO UPDATE SET origin`, never `idea_id`. Refusing the second link would turn a legitimate merge into an error; logging the replacement keeps a lossy ledger. **Where one row is still required the EARLIEST link wins** (`ORDER BY created_at, idea_id`, stated in SQL rather than left to the planner): `sourceIdeaForTask`, `credits.awardIdeaTaskShipCredit`, and the nod queue's one-row-per-task predicate — one constant shared by the list and its count, so the pill can never disagree with the rows. Consequences: a task subsumed from N ideas **counts once toward each idea's** shipped-task credit (and cannot double-count within one — the pair is unique); the per-ship payout still names one idea, the earliest, because paying every co-source ideator is a decision about total worth, not a bug fix; the migration **backfills** from `idea_inbox.promoted_to_task_id`, the weaker pointer that survived each overwrite, labelling restored rows `origin='promote'`; and `updateIdea` still swallows a lineage failure but now reports it as a `LINEAGE GAP` through `api.logger` — a silent swallow on top of a silent overwrite was two layers of one mistake.](<redacted>.md) | ideas / economy |
|
|
327
|
+
| 0238 | [**The join door is a provisioning SETTINGS key, and the hub column stays the cache** ([task 1002324](https://cloudbongos.com/builders#/task/1002324) · goal 1000046, criterion `proj-visibility-states` — R05). [ADR 0182](<redacted>.md) D5 says R05 "moves host — an instance surface, not a hub one", and on the tree both halves of that sentence pointed at nothing: `platform_identity_projects.visibility` (which R04 shipped) is written by **no route** — every write on `routes/projects.js` is the `project.curate` CURATION gate with no ownership check, `upsertProject` deliberately omits the column, and `owner_github_id` there is documented "advisory attribution (not an authority)" — while "the project's own hall settings" is a page that does not exist (`settings.html` is per-BUILDER; the hall's my-projects page was deleted by task 1003308). **Decision: the door is a key in `provisioning.SETTINGS_VOCAB`** (`visibility ∈ public|private|stealth`, default `public`), set through the existing own-scoped `PATCH /provisioning/instances/:id/settings` and rendered as one more `.mSec` section on the manage view an owner already uses. One line in the vocabulary buys the whole task from machinery already proven: `instanceAccessDenied` (own-scoped, Archon override, **404** not 403 — no existence leak), a validator that IS the vocabulary (so an off-list value 400s and a key can never drift out of the schema), a `provisioning_events` audit row, the `<PREFIX>_VISIBILITY` env the runner patches into `web.env`, and `src/branding.js` mapping it onto `project.visibility` in the public manifest. It is a **POLICY** key, not a platform-drawn one like the planet, for two reasons that outlive today: ADR 0182 D6 makes the door the CEILING `joinability` may only narrow and D1/D5 put admission authority on the instance — a door the instance cannot read cannot be a ceiling — and a key with a DEFAULT belongs in the defaulted vocabulary (`PLANET_VOCAB` keys are nullable because null means "the platform picks", and there is no such thing as the platform picking your join door). Nothing here writes the hub column: it stays ADR 0182 D5's cache, and R08 is the report that will feed it — so today an owner's pick is stored, validated, audited and delivered to their instance, and does not yet move the orb (that gap is R06/R08's, named so the next builder does not hunt a bug). The section deliberately ships **no "what your project runs" read-back line**, unlike its two siblings: a deployed project reports no door until it takes a release carrying the field, and a line that could only ever say "this project's server doesn't report this yet" is worse than none. Rejected: a new owner-scoped write route on the hub catalog (D5 forbids the hub authorising the change); a new instance-side settings page on its own (right long-run home, but today a control with no consumer while R08 is unshipped); a `visibility` COLUMN beside `description` (that one earned a column as half the publish gate and is read by SQL — a closed-vocabulary knob is what the jsonb store exists for); and `PLANET_VOCAB` to dodge the restart (a quieter save today, the ceiling rule lost tomorrow).](<redacted>.md) | provisioning / public-landing |
|
|
327
328
|
| 0237 | [**A starter bundle is a preset over the always-on core, keyed by the declared project type** ([task 1002338](https://cloudbongos.com/builders#/task/1002338) · goal 1000046, criterion `proj-module-select`). "During setup a default set of modules is pre-selected" can only mean the OPT-IN modules, since every instance already boots with the same sixteen `default: true` core-domain ones — so a bundle is a short list of opt-in module keys layered on that floor, preselected and freely toggled off by R20's picker ([task 1002339](https://cloudbongos.com/builders#/task/1002339)). **The key is the DECLARED type, and the seeded vocabulary is stale:** the task text (2026-07-17) names *Game / SaaS-Tool / Community*, but task [1002772](https://cloudbongos.com/builders#/task/1002772)'s owner decision (2026-08-13) fixed the live closed set as `game · research · business · non-profit · not-sure` — two CHECK constraints, two `PROJECT_TYPES` constants and five wizard buttons — and "community" survives only as a `team_shape`. Two constraints ride the key: the arrow runs **type → modules, never back** (1002772 rejected inferring type from module flags, which flip all through setup), and the R15 interview must **not re-ask** what `type` declares, so R19 adds no `PROJECT_DETAIL_VOCAB` key and no migration. `not-sure` and *undeclared* resolve to the same modules but are deliberately different answers — `type` is nullable precisely so "nobody said" is never back-filled with a guess, and the resolver returns `type: null` with its own copy. **The declaration is pinned by a test, not a runtime catalog read:** a module may not import `src/module-loader/catalog.js` (the one-way rule, [ADR 0083](<redacted>.md)), so `modules/provisioning/starter-bundles.js` stays PURE like `onboard-plan.js` beside it and `tests/provisioning_starter_bundles.mjs` reads every `modules/*/module.json` off disk — failing the build if a bundle names a module that does not exist, if `ALWAYS_ON_CORE` drifts from the manifests' `default: true` set, if a bundle names an always-on module (a preselect that toggles nothing), or if it names one of the two hub-only modules. The bundles are `game → dev-box, discord`; `research → agents`; `business → agents, dev-box`; `non-profit → discord`; `not-sure → —`, and they are SHORT because today's opt-in catalog is three modules wide and a preselect an owner must audit is worse than none. Served whole at public `GET /provisioning/starter-bundles` (`{ core, bundles }`, same absent gate as `onboard-plan`) so the picker re-preselects without a refetch and R17's recommendations read the same shape. A bundle is **never a publish precondition** — [ADR 0182](<redacted>.md) D4 fixes that bar at name + description alone. Rejected: a runtime catalog read, a `bundle` key in the interview vocabulary, inferring the bundle from the description (the deferred AI layer, [task 1003500](https://cloudbongos.com/builders#/task/1003500)), and keeping the seeded Game/SaaS-Tool/Community triple.](<redacted>.md) | provisioning / project creation |
|
|
328
329
|
|
|
329
330
|
> ⚠️ **Numbering collisions are now machine-enforced, not narrated.** Twenty numbers are shared by two ADRs each — assigned in parallel sessions before anything checked. The files keep their filenames (renumbering would break every existing citation, and a number once assigned is never reused), and this table disambiguates each pair as `NNNN-a` / `NNNN-b`. The authoritative list is `LEGACY_DUPLICATE_ADRS` in [`scripts/gds/adr-namespace.js`](../../scripts/gds/adr-namespace.js), frozen by exact filename: a **new** collision — or a third file joining a legacy number, or a rename of either half — hard-fails the `unit` CI gate, as does an index row that points at no file or an ADR with no row. The list may only shrink. This paragraph used to narrate the collisions one by one and had fallen eight behind reality; see [ADR 0195](<redacted>.md) for why the check exists and why the pairs are grandfathered rather than renumbered.
|