@bongos/core 1.19.373 → 1.19.375

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 (51) hide show
  1. package/.bongos-core.json +98 -48
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +6 -0
  4. package/clients/bongos-client/index.cjs +6 -0
  5. package/clients/bongos-client/index.d.ts +8 -0
  6. package/clients/bongos-client/index.mjs +6 -0
  7. package/docs/adr/0194-joinability-one-door-decision.md +42 -0
  8. package/docs/adr/README.md +1 -0
  9. package/docs/api/openapi.json +159 -4
  10. package/docs/api-reference.md +7 -4
  11. package/docs/copy-inventory.md +252 -233
  12. package/docs/copy-registry.json +503 -301
  13. package/modules/builder-settings/profile-nudge-prefs.js +125 -0
  14. package/modules/builder-settings/routes/settings.js +48 -0
  15. package/modules/dev-box/app/src/vendor/bongos-client.cjs +6 -0
  16. package/modules/government/catalog.js +1 -1
  17. package/modules/hall-ui/public/builders.js +5 -3
  18. package/modules/hall-ui/public/goal-inbox.js +10 -0
  19. package/modules/hall-ui/public/hall-render.js +63 -22
  20. package/modules/hall-ui/public/index.html +4 -0
  21. package/modules/hall-ui/public/my-projects.js +6 -1
  22. package/modules/hall-ui/public/profile-nudge.js +122 -0
  23. package/modules/hall-ui/public/watch.css +8 -0
  24. package/modules/hall-ui/public/watch.js +56 -3
  25. package/modules/onboarding/module.json +1 -1
  26. package/modules/onboarding/routes/access-requests.js +87 -1
  27. package/modules/platform-identity/routes/my-projects.js +35 -9
  28. package/package-lock.json +2 -2
  29. package/package.json +1 -1
  30. package/public-landing/projects.html +169 -3
  31. package/scripts/gds/fitness-checks-write-validation.js +1 -0
  32. package/scripts/gds/login.js +24 -3
  33. package/scripts/gds/ship-land.js +94 -3
  34. package/src/bongos/auth-admission.js +50 -5
  35. package/src/bongos/auth.js +2 -1
  36. package/src/bongos/routes/auth.js +15 -0
  37. package/src/branding.js +3 -2
  38. package/src/module-api.js +22 -1
  39. package/tests/access_requests_invite.mjs +165 -0
  40. package/tests/auth_admission_gate.mjs +42 -0
  41. package/tests/join_relay_outcome.mjs +61 -0
  42. package/tests/joinability_apply.mjs +72 -0
  43. package/tests/joinability_invite_only.mjs +192 -0
  44. package/tests/joinability_open.mjs +89 -0
  45. package/tests/module_api.mjs +1 -0
  46. package/tests/profile_nudge_prefs.mjs +97 -0
  47. package/tests/projects_hub.mjs +76 -2
  48. package/tests/projects_hub_pre_uat.mjs +4 -2
  49. package/tests/public_landing_projects.mjs +10 -3
  50. package/tests/ship_push_diagnosis.mjs +78 -0
  51. package/tests/sound_player.mjs +38 -0
package/.bongos-core.json CHANGED
@@ -2,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.373",
6
- "core_contract": "1.19.373",
7
- "source_commit": "fbe2ff6cdfb5154f956f0a473085ad2f55a9e7e2",
5
+ "core_version": "1.19.375",
6
+ "core_contract": "1.19.375",
7
+ "source_commit": "75b8de1c408ddd51e9e44d88a1fab7eee51cf017",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-25T22:26:30.155Z",
9
+ "built_at": "2026-08-25T23:12:43.870Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
- "docs_redacted": 334,
12
+ "docs_redacted": 335,
13
13
  "agent_docs_stubbed": 22,
14
- "functional_verbatim": 1545,
14
+ "functional_verbatim": 1554,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1901,
20
- "tree_sha256": "028f3591a335092742ce9ca6791ae17cc69d690bbe3895e09e10742a648eb04e",
19
+ "file_count": 1911,
20
+ "tree_sha256": "6f58b38b0e6751d8cbf0fbaecae632da6dd95061c28e22fd950e182471f7ea57",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -347,12 +347,12 @@
347
347
  {
348
348
  "path": "clients/bongos-client/README.md",
349
349
  "mode": "0000644",
350
- "sha256": "fc26e581e9ba06ec9fd798e982539f0bd8dc4c27a5f6c248b395f62214daaf78"
350
+ "sha256": "663c0e020d58d131bd1311d3cee1ee2b09b27287f59f0f301f89ecac3452a9dd"
351
351
  },
352
352
  {
353
353
  "path": "clients/bongos-client/bongos-client.global.js",
354
354
  "mode": "0000644",
355
- "sha256": "e50696e5b7b9c1abab280665dd57ab6f8f8883d3bfd0f2701a45ee7d9a6fb728"
355
+ "sha256": "e2454e2affc5a2f9203d7dacbe10423264e2c3ff1a45f4d8d5c3bf805c658484"
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": "b13e62181adbc47a9ba6d3349a4abcb63fca123f3b0202414a1f7a2ba5e7d088"
365
+ "sha256": "2c353c9b91dcb8c0afbc961e5b5f383aee69569c05cf7a03fabac02b34d06760"
366
366
  },
367
367
  {
368
368
  "path": "clients/bongos-client/index.d.ts",
369
369
  "mode": "0000644",
370
- "sha256": "bebe9282f7f60c62dc554c24bbec1fb7c9a8453e98015d5207c3b70260bcbb56"
370
+ "sha256": "b19f0435cf2a0124d9e568ad6daac87b54220ea3f170f65fda54ed391058f480"
371
371
  },
372
372
  {
373
373
  "path": "clients/bongos-client/index.mjs",
374
374
  "mode": "0000644",
375
- "sha256": "d4f2e6c3b376c35679416efcd30bbe54bae396cbaa8ee1e4078c56b6175da3d4"
375
+ "sha256": "f6d8b1a0ba6b2868204aabda309594e3c25f092611972dbd32753a257a095bd3"
376
376
  },
377
377
  {
378
378
  "path": "clients/bongos-client/package.json",
@@ -1464,20 +1464,25 @@
1464
1464
  "mode": "0000644",
1465
1465
  "sha256": "38af373e191ebf9d9788884315f90d439ec5a719f08afc596039d722153c8396"
1466
1466
  },
1467
+ {
1468
+ "path": "docs/adr/0194-joinability-one-door-decision.md",
1469
+ "mode": "0000644",
1470
+ "sha256": "65bd3a60b8d313af7ea69acf7fe9a522a15a144c4395b3788bd3f82cc20e8351"
1471
+ },
1467
1472
  {
1468
1473
  "path": "docs/adr/README.md",
1469
1474
  "mode": "0000644",
1470
- "sha256": "cf4a81346983c4247f711855d8eae3939b3cbeb37b12ad7cf2449d2e896b3ade"
1475
+ "sha256": "7f7354c2597bd982d92e0008595e05642c8ccc05c1e9446388c304e80b142626"
1471
1476
  },
1472
1477
  {
1473
1478
  "path": "docs/api-reference.md",
1474
1479
  "mode": "0000644",
1475
- "sha256": "544b25f700d89d7f51dd84cb4faa740bc1c9bb4bf52853470e7d645040215033"
1480
+ "sha256": "3fca3c9ed9fe092266ac1b4c721094cd0d50792e1b17a0821f7135d65fb89a57"
1476
1481
  },
1477
1482
  {
1478
1483
  "path": "docs/api/openapi.json",
1479
1484
  "mode": "0000644",
1480
- "sha256": "dbc90270a1754caf816ce9fde9cde35af21e977da08309f56c4d96dbf7cad9bf"
1485
+ "sha256": "72f2236c4123dd96be29c778df0b6b2b8531923cd394f193cf80b221b9bb8bdb"
1481
1486
  },
1482
1487
  {
1483
1488
  "path": "docs/architecture.md",
@@ -1502,12 +1507,12 @@
1502
1507
  {
1503
1508
  "path": "docs/copy-inventory.md",
1504
1509
  "mode": "0000644",
1505
- "sha256": "23502315ff7f5e758ad3171a7bd28f8861ccb6640be93bd6a3a3f681c1ff0606"
1510
+ "sha256": "fd5277bcf4ea80591a81d0b321d67f51380e0db35d0c08e237d95477f02d93b3"
1506
1511
  },
1507
1512
  {
1508
1513
  "path": "docs/copy-registry.json",
1509
1514
  "mode": "0000644",
1510
- "sha256": "85ead9ad5f556a449495e0e7f7af09a29fb9e54e663ef3fbf9f1fe1c390bc8d6"
1515
+ "sha256": "24955cb19ef7ef3b24783f58e9a8b403bdd43c8845b4a800b992191d6872dc01"
1511
1516
  },
1512
1517
  {
1513
1518
  "path": "docs/design-contract.md",
@@ -2609,6 +2614,11 @@
2609
2614
  "mode": "0000644",
2610
2615
  "sha256": "5d79d4f6f0e24b0ae9731a31ebabadc43faa3b6a423d31800b664960dc16145c"
2611
2616
  },
2617
+ {
2618
+ "path": "modules/builder-settings/profile-nudge-prefs.js",
2619
+ "mode": "0000644",
2620
+ "sha256": "26935726b1b388fb90793145d22f95641b2222a7b8ee29b2ed418bb2725f9fba"
2621
+ },
2612
2622
  {
2613
2623
  "path": "modules/builder-settings/render-prefs.js",
2614
2624
  "mode": "0000644",
@@ -2617,7 +2627,7 @@
2617
2627
  {
2618
2628
  "path": "modules/builder-settings/routes/settings.js",
2619
2629
  "mode": "0000644",
2620
- "sha256": "c5198a5fb915be1015eec09ca0b614c96d820fdf491cb56d1c735b8839a441c0"
2630
+ "sha256": "e1de49abd66d5dece54d5a794bb1af28469028903b022f3d7b1e2c453ea0b851"
2621
2631
  },
2622
2632
  {
2623
2633
  "path": "modules/builder-settings/settings.js",
@@ -2907,7 +2917,7 @@
2907
2917
  {
2908
2918
  "path": "modules/dev-box/app/src/vendor/bongos-client.cjs",
2909
2919
  "mode": "0000644",
2910
- "sha256": "b13e62181adbc47a9ba6d3349a4abcb63fca123f3b0202414a1f7a2ba5e7d088"
2920
+ "sha256": "2c353c9b91dcb8c0afbc961e5b5f383aee69569c05cf7a03fabac02b34d06760"
2911
2921
  },
2912
2922
  {
2913
2923
  "path": "modules/dev-box/app/test/unit.mjs",
@@ -3142,7 +3152,7 @@
3142
3152
  {
3143
3153
  "path": "modules/government/catalog.js",
3144
3154
  "mode": "0000644",
3145
- "sha256": "ed853e83a9765e0a5de90195a15dea6b0bf1f6d07be0b6ad740d54a7c79c2c7d"
3155
+ "sha256": "89c03f4af6b1a2bfef8c6820621b977d4872cd7fd4432e4f5ab506063c9acf96"
3146
3156
  },
3147
3157
  {
3148
3158
  "path": "modules/government/config.js",
@@ -3437,7 +3447,7 @@
3437
3447
  {
3438
3448
  "path": "modules/hall-ui/public/builders.js",
3439
3449
  "mode": "0000644",
3440
- "sha256": "df08dadd4a184a595319209e761d5de3aaa217623048972db7161a29b3db19ac"
3450
+ "sha256": "22f1d7714524e07397354507df61ccd4bcaf7c1779317945d1ba4cb45fca7042"
3441
3451
  },
3442
3452
  {
3443
3453
  "path": "modules/hall-ui/public/chime.wav",
@@ -3557,7 +3567,7 @@
3557
3567
  {
3558
3568
  "path": "modules/hall-ui/public/goal-inbox.js",
3559
3569
  "mode": "0000644",
3560
- "sha256": "ede756a5c11c8c6a9eca4c841f2110a5e05293b0fbd6d34a5c90a05dc02943c4"
3570
+ "sha256": "71836d70e751ea8c7363c3217931a2cdfaeea38b770b90e564baf69cfe75f40f"
3561
3571
  },
3562
3572
  {
3563
3573
  "path": "modules/hall-ui/public/goals-lib.js",
@@ -3612,7 +3622,7 @@
3612
3622
  {
3613
3623
  "path": "modules/hall-ui/public/hall-render.js",
3614
3624
  "mode": "0000644",
3615
- "sha256": "f9be7f42df75e5cac2ef917ddac0f22c294ab0eba687c0dbffc15a5fe31a36dd"
3625
+ "sha256": "5a09364d61b41b2cf7e970370ee993e41bbe85d0dbf5be89e979d436ac1e48a1"
3616
3626
  },
3617
3627
  {
3618
3628
  "path": "modules/hall-ui/public/hall-widgets.js",
@@ -3667,7 +3677,7 @@
3667
3677
  {
3668
3678
  "path": "modules/hall-ui/public/index.html",
3669
3679
  "mode": "0000644",
3670
- "sha256": "6564fe67e5987fbc25a4dd84d0288302ddcd81050e6afa1e5ab1b1dea873c0d8"
3680
+ "sha256": "78cb8c70e92e70d996a8f1694b7b01bf893f6aa3f210bc297e48cdd961b971ae"
3671
3681
  },
3672
3682
  {
3673
3683
  "path": "modules/hall-ui/public/md-reader.js",
@@ -3702,7 +3712,7 @@
3702
3712
  {
3703
3713
  "path": "modules/hall-ui/public/my-projects.js",
3704
3714
  "mode": "0000644",
3705
- "sha256": "9b5604fd1d071bcb61c1ce77a8356fbce8060db09440ba5621646aa18b6f5b5f"
3715
+ "sha256": "25195cabf71890f93f065e2aa15e899d51c251807e59ba14a147150c5b6228a3"
3706
3716
  },
3707
3717
  {
3708
3718
  "path": "modules/hall-ui/public/not-ready.html",
@@ -3754,6 +3764,11 @@
3754
3764
  "mode": "0000644",
3755
3765
  "sha256": "40f6f44283aa7dba25a2c983d77b6e6f53f40eefef3394122d34f7f42fc5f676"
3756
3766
  },
3767
+ {
3768
+ "path": "modules/hall-ui/public/profile-nudge.js",
3769
+ "mode": "0000644",
3770
+ "sha256": "b86a8a2b12e30671cc00a4495fbc9789967d38622ce803f1ffc2d95ae45b7d03"
3771
+ },
3757
3772
  {
3758
3773
  "path": "modules/hall-ui/public/profile.html",
3759
3774
  "mode": "0000644",
@@ -3872,7 +3887,7 @@
3872
3887
  {
3873
3888
  "path": "modules/hall-ui/public/watch.css",
3874
3889
  "mode": "0000644",
3875
- "sha256": "d3620f7db28969f817d377c9291fac05af40ef77e9c9f545e6a3c9b5ddcff816"
3890
+ "sha256": "bc89cba0af3e580f56425b5ecb83389ead0246e7240b08c2280671f2184025ad"
3876
3891
  },
3877
3892
  {
3878
3893
  "path": "modules/hall-ui/public/watch.html",
@@ -3882,7 +3897,7 @@
3882
3897
  {
3883
3898
  "path": "modules/hall-ui/public/watch.js",
3884
3899
  "mode": "0000644",
3885
- "sha256": "9fbaee78276658ca61631e2849d8c8b34e708d3074c2739f1c0ae0cd8a048c78"
3900
+ "sha256": "122f76452956180e3c3aff6b5d5bcddd5866131e37b244bb36b0efda25b35bd8"
3886
3901
  },
3887
3902
  {
3888
3903
  "path": "modules/hall-ui/public/work.css",
@@ -4362,7 +4377,7 @@
4362
4377
  {
4363
4378
  "path": "modules/onboarding/module.json",
4364
4379
  "mode": "0000644",
4365
- "sha256": "d121973873e2a38cdb2e6ad1096397a667bbd11b851a079c299d3fb6dbdd21be"
4380
+ "sha256": "36800847c9cc9d239bb4d0ec910b35b547f8c67e5db5cb1cd5aa63d801e4bf02"
4366
4381
  },
4367
4382
  {
4368
4383
  "path": "modules/onboarding/newcomer-restock.js",
@@ -4387,7 +4402,7 @@
4387
4402
  {
4388
4403
  "path": "modules/onboarding/routes/access-requests.js",
4389
4404
  "mode": "0000644",
4390
- "sha256": "96cc25d7f8c343333cc4393258ad931c2cb9df9b25b564d77d0fcf2257455a03"
4405
+ "sha256": "060e8c33c5182c3d11b2b39b721f485deecc07b51f735b98f97aafbcc7e7657a"
4391
4406
  },
4392
4407
  {
4393
4408
  "path": "modules/onboarding/routes/onboarding.js",
@@ -4482,7 +4497,7 @@
4482
4497
  {
4483
4498
  "path": "modules/platform-identity/routes/my-projects.js",
4484
4499
  "mode": "0000644",
4485
- "sha256": "de5aa85aedf31834009ec0e1a3b4a7ceea2ff60bbb6586a3f231b59eb9549aaf"
4500
+ "sha256": "4fadf91c0d40f3e5dd5d53f9ebe3ec825fd16a13aab41a76e8af25c911e11edf"
4486
4501
  },
4487
4502
  {
4488
4503
  "path": "modules/platform-identity/routes/profile.js",
@@ -4902,12 +4917,12 @@
4902
4917
  {
4903
4918
  "path": "package-lock.json",
4904
4919
  "mode": "0000644",
4905
- "sha256": "01e0ec5418014e61e5a0bdf1781ea84ee46c4c8bfc1b63b0e55bb6397ec06498"
4920
+ "sha256": "63450349ce8f5c64c960a3f83c7f217324c1a7ee61f28e5ff06e8972021b53d3"
4906
4921
  },
4907
4922
  {
4908
4923
  "path": "package.json",
4909
4924
  "mode": "0000644",
4910
- "sha256": "f38add7db0b96f5b469c5127f7e06336ef5626edc93d61f797d3439e0ca51760"
4925
+ "sha256": "e7deabfda7d0840b92653973c68e280bbe64ded769d1dd8fa94db95a21cc0ba6"
4911
4926
  },
4912
4927
  {
4913
4928
  "path": "public-docs/index.html",
@@ -5167,7 +5182,7 @@
5167
5182
  {
5168
5183
  "path": "public-landing/projects.html",
5169
5184
  "mode": "0000644",
5170
- "sha256": "38df756a4d5bac76ea0369f81392d32d906f5f16a634c97311457779c15b667f"
5185
+ "sha256": "f4818358fb126ccef093b6193b743aab73fe7598c13c685485651f6d6ad63921"
5171
5186
  },
5172
5187
  {
5173
5188
  "path": "public-landing/world/assets/clouds/cloud-island-large.png",
@@ -5587,7 +5602,7 @@
5587
5602
  {
5588
5603
  "path": "scripts/gds/fitness-checks-write-validation.js",
5589
5604
  "mode": "0000644",
5590
- "sha256": "7ee041817c06a56d4801f4367a618384ad856235a5034d3abdafa9f9344bff0b"
5605
+ "sha256": "7501680e8b657a1002c185750951f16af1b2e7e30c04c72a3f03a94d286e02c4"
5591
5606
  },
5592
5607
  {
5593
5608
  "path": "scripts/gds/fitness-lib.js",
@@ -5792,7 +5807,7 @@
5792
5807
  {
5793
5808
  "path": "scripts/gds/login.js",
5794
5809
  "mode": "0000644",
5795
- "sha256": "91bd5c85c79e7367a8a3486074a544e24916048beb7fd62365cae8e224f5f609"
5810
+ "sha256": "f16497d7eae23657e83f631b420f82a55ed8216480f3cd5488609d3b93274dee"
5796
5811
  },
5797
5812
  {
5798
5813
  "path": "scripts/gds/main-audit.js",
@@ -6257,7 +6272,7 @@
6257
6272
  {
6258
6273
  "path": "scripts/gds/ship-land.js",
6259
6274
  "mode": "0000644",
6260
- "sha256": "e091557b4cc29c081cb82f9cd650b4c110f1dede4e72b704893f8397cd7bbaf2"
6275
+ "sha256": "72efd4268060fb5ddf8a176d91baf01353dc1e82594d70feef029c45f748d8f9"
6261
6276
  },
6262
6277
  {
6263
6278
  "path": "scripts/gds/ship-memory-sync.js",
@@ -6517,7 +6532,7 @@
6517
6532
  {
6518
6533
  "path": "src/bongos/auth-admission.js",
6519
6534
  "mode": "0000644",
6520
- "sha256": "74830f017c5472e2420095e158c905dccfd046d1442263b374ae87f2ceebde86"
6535
+ "sha256": "0d2e4aaf4b99ac4b1122c182f279ae622f53d8896baa31d796c5bb0f4ef6532c"
6521
6536
  },
6522
6537
  {
6523
6538
  "path": "src/bongos/auth-config.js",
@@ -6542,7 +6557,7 @@
6542
6557
  {
6543
6558
  "path": "src/bongos/auth.js",
6544
6559
  "mode": "0000644",
6545
- "sha256": "c7f087b10ffc9288ec61e8b4b4c17e089b9e137da03961a1c71a62a4099c2ae7"
6560
+ "sha256": "6e9c3e0116fa0e2a62b6fc67db1c4a37020491e7f659f1193a4e71fae1be8b26"
6546
6561
  },
6547
6562
  {
6548
6563
  "path": "src/bongos/cascade-dispatch.js",
@@ -6682,7 +6697,7 @@
6682
6697
  {
6683
6698
  "path": "src/bongos/routes/auth.js",
6684
6699
  "mode": "0000644",
6685
- "sha256": "39392e56faf8fb7efb52686a006d3dd5dbb4b965dd81402d6a4d7a7c173923bb"
6700
+ "sha256": "d4dc490ccf82f56d66f4d17e64f514632171480f22175bfa682dea9eb0e2e44a"
6686
6701
  },
6687
6702
  {
6688
6703
  "path": "src/bongos/routes/backup.js",
@@ -6772,7 +6787,7 @@
6772
6787
  {
6773
6788
  "path": "src/branding.js",
6774
6789
  "mode": "0000644",
6775
- "sha256": "239e2af1c637d0f72e8a61dc2ef5450ca0821477617941399253330e9e4afa25"
6790
+ "sha256": "51d70cad3ef6955a0648f4467dd3e0ffa54d747d93b7a30301dce3c8afdbe1cb"
6776
6791
  },
6777
6792
  {
6778
6793
  "path": "src/build-info.js",
@@ -6792,7 +6807,7 @@
6792
6807
  {
6793
6808
  "path": "src/module-api.js",
6794
6809
  "mode": "0000644",
6795
- "sha256": "88ea06ed6759765c78ff0fc67ddd0a65041e7b8fda60e0f18b5809c44107ad7d"
6810
+ "sha256": "8d822b226b35e7226e3678e7e1e53e0c4da9d27be845bfe69d6832c976d2ffcb"
6796
6811
  },
6797
6812
  {
6798
6813
  "path": "src/module-loader/catalog.js",
@@ -6849,6 +6864,11 @@
6849
6864
  "mode": "0000644",
6850
6865
  "sha256": "031b75aeb83322055c4d69ab5481b220a7a41ccbe950129b84f8bc80d7c75a40"
6851
6866
  },
6867
+ {
6868
+ "path": "tests/access_requests_invite.mjs",
6869
+ "mode": "0000644",
6870
+ "sha256": "4d8435e7f8b949715ee40843beddc1868105edd9c67b269cfa8f510a192e9212"
6871
+ },
6852
6872
  {
6853
6873
  "path": "tests/achievements_reconcile.mjs",
6854
6874
  "mode": "0000644",
@@ -6967,7 +6987,7 @@
6967
6987
  {
6968
6988
  "path": "tests/auth_admission_gate.mjs",
6969
6989
  "mode": "0000644",
6970
- "sha256": "0ecd7b6bd81ddd65f2da7a2c4be38097ae22cc6a2f31a71cfc2204d7dce2a0ca"
6990
+ "sha256": "2acde0caeab2acba0e3d52a7605bf1017b95d2eab0358972a58d691a43f5dfc6"
6971
6991
  },
6972
6992
  {
6973
6993
  "path": "tests/auth_anthropic_identity.mjs",
@@ -8309,6 +8329,26 @@
8309
8329
  "mode": "0000644",
8310
8330
  "sha256": "d00e82184fbf7aab49c9b6efe5709b14f300ca0cea277a0eb140f073ded7ff50"
8311
8331
  },
8332
+ {
8333
+ "path": "tests/join_relay_outcome.mjs",
8334
+ "mode": "0000644",
8335
+ "sha256": "d3cb9fcd89fe88cfa5b1578cb9e2fc48d762227ee99b807006047e58ba9133b0"
8336
+ },
8337
+ {
8338
+ "path": "tests/joinability_apply.mjs",
8339
+ "mode": "0000644",
8340
+ "sha256": "18e67c48f3fb400ed69bcef5e7b706d19f1779c4751c091e72edb57040ca65e6"
8341
+ },
8342
+ {
8343
+ "path": "tests/joinability_invite_only.mjs",
8344
+ "mode": "0000644",
8345
+ "sha256": "082693b5e07c1c25cec17cde7f93a8716aafa94ead9b1cee76b1643f30edff79"
8346
+ },
8347
+ {
8348
+ "path": "tests/joinability_open.mjs",
8349
+ "mode": "0000644",
8350
+ "sha256": "c26fa3ccb97861370c08ebcc1ab4e7706f90b7cf427efd1a29db456ea82fbfa8"
8351
+ },
8312
8352
  {
8313
8353
  "path": "tests/key-card.mjs",
8314
8354
  "mode": "0000644",
@@ -8487,7 +8527,7 @@
8487
8527
  {
8488
8528
  "path": "tests/module_api.mjs",
8489
8529
  "mode": "0000644",
8490
- "sha256": "f9142ae695964882ef4d6c4b83fec12c9ab32d0e31b3135333c27d695afb3262"
8530
+ "sha256": "e5bb1ae80c818eec08f3f06999037b61d176afecb850fe65e4eab6c34e97b6b1"
8491
8531
  },
8492
8532
  {
8493
8533
  "path": "tests/module_catalog.mjs",
@@ -8704,6 +8744,11 @@
8704
8744
  "mode": "0000644",
8705
8745
  "sha256": "ff73579dd2416a81648d85dcd5d1a77cb776c32047bd0cc7897dff132cbae5b7"
8706
8746
  },
8747
+ {
8748
+ "path": "tests/profile_nudge_prefs.mjs",
8749
+ "mode": "0000644",
8750
+ "sha256": "a7f70fa612494593438565f4bd1e3d454a3dbca862c8396a7dcb252d38e3e489"
8751
+ },
8707
8752
  {
8708
8753
  "path": "tests/profile_rollup_consent.mjs",
8709
8754
  "mode": "0000644",
@@ -8727,12 +8772,12 @@
8727
8772
  {
8728
8773
  "path": "tests/projects_hub.mjs",
8729
8774
  "mode": "0000644",
8730
- "sha256": "7ced3851eb30960017693dfc0fd4711164448cfde17448ba92d528bdd3091041"
8775
+ "sha256": "9aa24895829edb4896c48258c974802f835b6b391db8b8e765cbdceb272738a9"
8731
8776
  },
8732
8777
  {
8733
8778
  "path": "tests/projects_hub_pre_uat.mjs",
8734
8779
  "mode": "0000644",
8735
- "sha256": "bb0da311738faba71a077557e60b5ab30086399ac767e677baf53334ca769c29"
8780
+ "sha256": "a6669ad211a98454a17330756ae8a36d6c853a3688d284c1487dc7b2f6a21dbc"
8736
8781
  },
8737
8782
  {
8738
8783
  "path": "tests/promote_goal_id.mjs",
@@ -8812,7 +8857,7 @@
8812
8857
  {
8813
8858
  "path": "tests/public_landing_projects.mjs",
8814
8859
  "mode": "0000644",
8815
- "sha256": "5c5fe7821194630d0dc765ea0ad34560049e2b88c5ac5cae19b9fbd770df3168"
8860
+ "sha256": "232ac3f8206850957852c5d855f72cfc94f272ef8e0493410376903e2ae06176"
8816
8861
  },
8817
8862
  {
8818
8863
  "path": "tests/public_mirror_export.mjs",
@@ -9219,6 +9264,11 @@
9219
9264
  "mode": "0000644",
9220
9265
  "sha256": "5c0d5961408ac202eb53fe648c4c8b00e5552b231b35f39a39e006b705cb34f1"
9221
9266
  },
9267
+ {
9268
+ "path": "tests/ship_push_diagnosis.mjs",
9269
+ "mode": "0000644",
9270
+ "sha256": "d69bad6c8b3a01434b4fd5230f2b781bbe7fd9938df3d96b2a4ef34906287e54"
9271
+ },
9222
9272
  {
9223
9273
  "path": "tests/ship_resume.mjs",
9224
9274
  "mode": "0000644",
@@ -9292,7 +9342,7 @@
9292
9342
  {
9293
9343
  "path": "tests/sound_player.mjs",
9294
9344
  "mode": "0000644",
9295
- "sha256": "42398e730349be5f10e07eb3ec42aa47c9a024371f2f95f96802b990ff0e6f80"
9345
+ "sha256": "5b42ae4236e203c69feb3b8885ebec3a5f2b0d7f6880128536dfe5b8088fd4e8"
9296
9346
  },
9297
9347
  {
9298
9348
  "path": "tests/sound_prefs.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
- - 318 operations across 52 resource groups
8
+ - 321 operations across 52 resource groups
9
9
 
10
10
  ## Install
11
11
 
@@ -91,6 +91,8 @@ function createClient(opts = {}) {
91
91
  postAccessRequests: (args) => request("POST", "/access-requests", { hasBody: true }, args),
92
92
  // PATCH /access-requests/{id} — rank: archon — PATCH /access-requests/:id
93
93
  patchAccessRequestsId: (args) => request("PATCH", "/access-requests/{id}", { hasBody: true }, args),
94
+ // POST /access-requests/invite — rank: archon — POST /access-requests/invite
95
+ postAccessRequestsInvite: (args) => request("POST", "/access-requests/invite", { hasBody: true }, args),
94
96
  // GET /access-requests/status — rank: public — GET /access-requests/status
95
97
  getAccessRequestsStatus: (args) => request("GET", "/access-requests/status", { hasBody: false }, args),
96
98
  },
@@ -501,6 +503,10 @@ function createClient(opts = {}) {
501
503
  patchMeModelAllocation: (args) => request("PATCH", "/me/model-allocation", { hasBody: true }, args),
502
504
  // PATCH /me/profile — rank: any-builder — PATCH /me/profile
503
505
  patchMeProfile: (args) => request("PATCH", "/me/profile", { hasBody: true }, args),
506
+ // GET /me/profile-nudge — rank: any-builder — GET /me/profile-nudge
507
+ getMeProfileNudge: (args) => request("GET", "/me/profile-nudge", { hasBody: false }, args),
508
+ // PATCH /me/profile-nudge — rank: any-builder — PATCH /me/profile-nudge
509
+ patchMeProfileNudge: (args) => request("PATCH", "/me/profile-nudge", { hasBody: true }, args),
504
510
  // GET /me/recent-ships — rank: any-builder — GET /me/recent-ships
505
511
  getMeRecentShips: (args) => request("GET", "/me/recent-ships", { hasBody: false }, args),
506
512
  // GET /me/render — rank: any-builder — GET /me/render
@@ -90,6 +90,8 @@ function createClient(opts = {}) {
90
90
  postAccessRequests: (args) => request("POST", "/access-requests", { hasBody: true }, args),
91
91
  // PATCH /access-requests/{id} — rank: archon — PATCH /access-requests/:id
92
92
  patchAccessRequestsId: (args) => request("PATCH", "/access-requests/{id}", { hasBody: true }, args),
93
+ // POST /access-requests/invite — rank: archon — POST /access-requests/invite
94
+ postAccessRequestsInvite: (args) => request("POST", "/access-requests/invite", { hasBody: true }, args),
93
95
  // GET /access-requests/status — rank: public — GET /access-requests/status
94
96
  getAccessRequestsStatus: (args) => request("GET", "/access-requests/status", { hasBody: false }, args),
95
97
  },
@@ -500,6 +502,10 @@ function createClient(opts = {}) {
500
502
  patchMeModelAllocation: (args) => request("PATCH", "/me/model-allocation", { hasBody: true }, args),
501
503
  // PATCH /me/profile — rank: any-builder — PATCH /me/profile
502
504
  patchMeProfile: (args) => request("PATCH", "/me/profile", { hasBody: true }, args),
505
+ // GET /me/profile-nudge — rank: any-builder — GET /me/profile-nudge
506
+ getMeProfileNudge: (args) => request("GET", "/me/profile-nudge", { hasBody: false }, args),
507
+ // PATCH /me/profile-nudge — rank: any-builder — PATCH /me/profile-nudge
508
+ patchMeProfileNudge: (args) => request("PATCH", "/me/profile-nudge", { hasBody: true }, args),
503
509
  // GET /me/recent-ships — rank: any-builder — GET /me/recent-ships
504
510
  getMeRecentShips: (args) => request("GET", "/me/recent-ships", { hasBody: false }, args),
505
511
  // GET /me/render — rank: any-builder — GET /me/render
@@ -163,6 +163,8 @@ export interface PatchProvisioningInstancesIdSettingsResponse { ok: boolean; sav
163
163
  export interface PatchTaskRecommendationsIdRequest { status: "declined" | "withdrawn" }
164
164
  export interface PatchTaskRecommendationsIdResponse { ok: boolean; recommendation: unknown }
165
165
  export interface PatchTasksIdRequest { kind?: string; discipline?: string; requires_rank?: string; module_key?: string; goal_id?: number; parent_task_id?: StringifiableId; parallel_safe?: unknown; newcomer_friendly?: boolean; xenos_claimable?: boolean; security_sensitive?: boolean; automation_tag?: string; needs_migration?: boolean; description?: string; status?: unknown }
166
+ export interface PostAccessRequestsInviteRequest { github_login: string }
167
+ export interface PostAccessRequestsInviteResponse { ok: boolean; already_invited?: boolean; github_login?: unknown; request?: unknown }
166
168
  export interface PostAccessRequestsRequest { github_login: string; display_name?: string; note?: string }
167
169
  export interface PostAccessRequestsResponse { ok: boolean; request?: unknown; already_pending?: boolean }
168
170
  export interface PostAuthAppPairApproveRequest { pair_code: string }
@@ -377,6 +379,8 @@ export interface BongosClient {
377
379
  postAccessRequests(args: RequestArgs & { body: PostAccessRequestsRequest }): Promise<PostAccessRequestsResponse>;
378
380
  /** PATCH /access-requests/{id} — rank: archon */
379
381
  patchAccessRequestsId(args: RequestArgs & { body: PatchAccessRequestsIdRequest }): Promise<PatchAccessRequestsIdResponse>;
382
+ /** POST /access-requests/invite — rank: archon */
383
+ postAccessRequestsInvite(args: RequestArgs & { body: PostAccessRequestsInviteRequest }): Promise<PostAccessRequestsInviteResponse>;
380
384
  /** GET /access-requests/status — rank: public */
381
385
  getAccessRequestsStatus(args?: RequestArgs): Promise<GetAccessRequestsStatusResponse>;
382
386
  };
@@ -787,6 +791,10 @@ export interface BongosClient {
787
791
  patchMeModelAllocation(args?: RequestArgs): Promise<ApiResponse>;
788
792
  /** PATCH /me/profile — rank: any-builder */
789
793
  patchMeProfile(args?: RequestArgs & { body?: PatchMeProfileRequest }): Promise<PatchMeProfileResponse>;
794
+ /** GET /me/profile-nudge — rank: any-builder */
795
+ getMeProfileNudge(args?: RequestArgs): Promise<ApiResponse>;
796
+ /** PATCH /me/profile-nudge — rank: any-builder */
797
+ patchMeProfileNudge(args?: RequestArgs): Promise<ApiResponse>;
790
798
  /** GET /me/recent-ships — rank: any-builder */
791
799
  getMeRecentShips(args?: RequestArgs): Promise<GetMeRecentShipsResponse>;
792
800
  /** GET /me/render — rank: any-builder */
@@ -87,6 +87,8 @@ export function createClient(opts = {}) {
87
87
  postAccessRequests: (args) => request("POST", "/access-requests", { hasBody: true }, args),
88
88
  // PATCH /access-requests/{id} — rank: archon — PATCH /access-requests/:id
89
89
  patchAccessRequestsId: (args) => request("PATCH", "/access-requests/{id}", { hasBody: true }, args),
90
+ // POST /access-requests/invite — rank: archon — POST /access-requests/invite
91
+ postAccessRequestsInvite: (args) => request("POST", "/access-requests/invite", { hasBody: true }, args),
90
92
  // GET /access-requests/status — rank: public — GET /access-requests/status
91
93
  getAccessRequestsStatus: (args) => request("GET", "/access-requests/status", { hasBody: false }, args),
92
94
  },
@@ -497,6 +499,10 @@ export function createClient(opts = {}) {
497
499
  patchMeModelAllocation: (args) => request("PATCH", "/me/model-allocation", { hasBody: true }, args),
498
500
  // PATCH /me/profile — rank: any-builder — PATCH /me/profile
499
501
  patchMeProfile: (args) => request("PATCH", "/me/profile", { hasBody: true }, args),
502
+ // GET /me/profile-nudge — rank: any-builder — GET /me/profile-nudge
503
+ getMeProfileNudge: (args) => request("GET", "/me/profile-nudge", { hasBody: false }, args),
504
+ // PATCH /me/profile-nudge — rank: any-builder — PATCH /me/profile-nudge
505
+ patchMeProfileNudge: (args) => request("PATCH", "/me/profile-nudge", { hasBody: true }, args),
500
506
  // GET /me/recent-ships — rank: any-builder — GET /me/recent-ships
501
507
  getMeRecentShips: (args) => request("GET", "/me/recent-ships", { hasBody: false }, args),
502
508
  // GET /me/render — rank: any-builder — GET /me/render
@@ -0,0 +1,42 @@
1
+ # ADR 0194 — A project's membership door is one decision, answered the same way everywhere a stranger meets it
2
+
3
+ - **Status:** accepted
4
+ - **Date:** 2026-08-25
5
+ - **Task:** [task 1003044](https://cloudbongos.com/builders#/task/1003044) (goal 1000058 — the non-cloudbongos project lifecycle)
6
+ - **Relates to:** ADR 0050 (invite-gated by default; the `OPEN_XENOS_ENROLLMENT` escape hatch), ADR 0190 (the settings channel: `brand.project` as the read-back), ADR 0192 (the platform-visibility member door — this setting's sibling), ADR 0116 (the error envelope), ADR 0083 (the module doorway), ADR 0161 (a MINOR is a human edit), task 1003040 (the settings row), task 1003043 (the manage-page section shape)
7
+
8
+ ## Context
9
+
10
+ The owner decided on 2026-08-16 that a project chooses how people join it — **open** (whoever signs in is in), **apply** (a stranger files an access request, an Archon invites; today's behaviour), or **invite only** (no applications; only an Archon's invite admits). Task 1003040 stored the choice on the platform's settings row, 1003139/1003140 pushed it into the instance's environment, and `branding().project.joinability` resolved it — enforced by nothing, and the manage page said so.
11
+
12
+ Admission on an instance is decided in one place already: `firstTimeAdmissionBlocked` (SR-19 — one pure decision both sign-in flows call, so they cannot drift), fed by `openEnrollmentEnabled()`, which read only the legacy env flag. Four sign-in paths call it (the GitHub web and device flows, and their federated twins). Beside it sit the onboarding module's public `POST /access-requests` (the one unauthenticated write), the Archon's `PATCH … invited`, the hall's landing page with its request form, the web sign-in's 403 page, the CLI's stranger path, and the hub's join relay — each of which tells a stranger what to do next.
13
+
14
+ ## Decision
15
+
16
+ 1. **One reader, and open enrolment follows it.** `joinabilityMode()` (`src/bongos/auth-admission.js`) is the project's effective door: `branding().project.joinability` when it says `open` or `invite_only`; otherwise `apply` — **unless** the legacy `OPEN_XENOS_ENROLLMENT` flag is set, which still turns an *apply* project into an open one. `openEnrollmentEnabled()` becomes `joinabilityMode() === 'open'`, so the four sign-in call sites, `webAdmissionStatus`, and the manifest's `admission.policy` follow without being touched. Why the flag keeps its power on `apply` only: a self-hosted instance that set it (ADR 0050) must keep its open door across the upgrade — the neutral pack defaults to `apply`, so "flag ignored" would have silently closed those doors; and an explicit `invite_only` is the stricter, newer choice, which the flag must not reopen. Why the flag is not folded into `branding()`: the pack is memoized for the process while the flag is read per call (and toggled by the existing tests), and the manifest's `brand.project.joinability` must stay the *pushed* value the hub reads back. The one case where the two disagree — a self-hosted instance carrying the flag — is never platform-managed: its hall landing offers the request form (the injected pack says `apply`), and the form collapses into "no request needed — sign in" on the door's own 409; an owner who wants the hall to say so up front sets `<PREFIX>_JOINABILITY=open`. The flag is kept only so nothing changes on upgrade.
17
+
18
+ 2. **An unreadable pack, or a value this core cannot read, fails closed: `invite_only`.** When the door's policy is unknown — the pack will not load, or it says `invite-only` with a hyphen, or a truncated write — the widest door is never the fallback, the same rule the visibility gate applies (ADR 0192 §2). Only an absent value or `apply` reaches the legacy-flag branch. Logged through the kernel logger, an unknown value once per process.
19
+
20
+ 3. **The public `POST /access-requests` answers the door first** — before validating the body and before any lookup. On `invite_only` it refuses `403 invite_only`; on `open` it refuses `409 open_enrollment` ("no request needed — sign in"). Both are plain sentences in the ADR 0116 envelope. Answering first costs no query, reveals nothing about who already exists (the account-existence oracle ADR 0192 §3 deferred does not widen), and keeps the refusal DB-free — which is what lets the real booted app prove it. A non-2xx for `open` rather than a friendly 200: an older `bongos login` would otherwise print "✓ Requested access" and wait thirty minutes for an approval that never comes; a refusal it does not recognise prints its status and stops, which is honest.
21
+
22
+ 4. **An Archon's invite is the same row the gate already reads.** `POST /access-requests/invite { github_login }` (rank archon — the gate that resolves a request) writes an `access_requests` row at `'invited'` — resolving a pending one in place, or inserting one. `hasInvitedAccessRequest` admits it at sign-in with nothing new consulted. No second table, no token, no expiry: an invite here is exactly what the `PATCH … invited` has meant since ADR 0050, created before the request instead of after. It reaches the hall as an "Invite by GitHub username" form in the Watch page's Access-requests section, whose one-line explanation names the door the project runs.
23
+
24
+ 5. **The door is said in every place a stranger meets it, in the project's own words.** The hall landing renders one door per mode from the pack the server injects (`window.__BRANDING__.project` — no fetch, no flash); its request form collapses into "sign in" on the door's own refusal codes. The web sign-in's 403 page names the invite-only door and the one way in instead of sending a stranger to a form the project refuses. The CLI says invite-only and stops instead of collecting a username. The hub's join relay names the door in the hub's own words (`joinRelayOutcome`, pure) — a registered project is trusted to build, not to write prose into the platform's UI — rather than painting a refusal as "requested", or as "could not be reached", which a 403 used to become; a 401 (the project's member-only door, ADR 0192, refusing the ask itself) is said as such, and the hub's join box keeps the typed address and does not repaint as if a request went out. Someone who applied before a project switched to invite-only stays in the queue — the Archon can still invite or dismiss them from the Watch page, and the manage fold says so.
25
+
26
+ 6. **The manifest's `admission.policy` stays binary.** It is a pinned public field derived from `openEnrollmentEnabled()`, so it agrees with the new decision for free (`open` → `open`, else `invite-only`, which is true of both `apply` and `invite_only`: you need an invite). The three-valued truth is `brand.project.joinability`, which the hub's "runs as" line already reads.
27
+
28
+ 7. **`joinabilityMode` joins the doorway — and rides the next CI patch, not a hand-bumped 1.20.0.** The onboarding module must refuse with the *same* decision the sign-in gate makes; re-deriving it from `api.branding()` would copy the flag rule and the fail-closed default into a second place — the drift SR-19 exists to prevent. An added export is a statement about the doorway surface, which ADR 0161 §2 reserves as a human MINOR (the 1.19.0 precedent). This task tried exactly that, and the publish lane refused it: `tests/bump_version.mjs`'s LIVE test asserts HEAD's version is one the lane's own carrier commits announced, so a branch declaring `1.20.0` fails the `unit` check by construction (1.19.0 was "bumped and never packaged" for the same reason). Landing hands-free on CI (ADR 0162) outranks expressing the MINOR in the number: the version stays the carrier's, the changelog says why, `modules/onboarding/module.json` floors at `^1.19.374`, and the contradiction between ADR 0161 §2's promise and the lane's test is filed as an idea for the owner.
29
+
30
+ 8. **The hub's section clones `#mVisibility`'s shape rather than extracting a factory.** Task 1003043's tests pin that block's text function by function; two settings sharing `visPushOpen()` is tolerable duplication. A third setting is the point at which a factory — and one manifest read serving every key — pays for itself.
31
+
32
+ ## What was deliberately not built
33
+
34
+ - No change to the Discord `#member-approvals` broadcast (an invite-only project simply never posts one, because nothing is filed).
35
+ - No widening of `admission.policy`; no projection of the manifest (idea 1000845); no throttling of the admission-status lookup (ADR 0192 §3).
36
+ - No invite expiry, revocation, or token. An Archon can dismiss nothing they created here — the row is the audit trail, as it is for a resolved request.
37
+
38
+ ## Consequences
39
+
40
+ - A project's door is enforced server-side wherever admission is decided, from one reader, and every surface that speaks to a stranger says which door it is.
41
+ - The hall's `postJSON` now reads the envelope (`error.code` / `error.message`); its already-a-member branch had silently never fired since the envelope landed, and the new refusal codes needed the same read.
42
+ - A self-hosted instance on the legacy flag is unchanged. A platform project defaults to `apply`, unchanged. Only a pushed `open` or `invite_only` changes behaviour, and only after the restart the runner performs (ADR 0190).
@@ -271,5 +271,6 @@ This keeps the decision history honest and traceable.
271
271
  | 0192 | [**A gated project refuses strangers at the app's front door, and the hub reads what the project runs from the project itself** ([task 1003043](https://cloudbongos.com/builders#/task/1003043), goal 1000058). Task 1003040 gave every project a `platform_visibility` setting and 1003139/1003140 carried it into the instance's `branding().project.platformVisibility` — resolved, enforced by nothing. Decision: ONE app-level middleware (`src/bongos/platform-visibility-gate.js`), mounted beside the pre-launch gate ahead of every human and public-read surface — the landing, the hub pages, the status host, the hall shell, `/public/*` and every module's `rank: public` route — rather than per-surface wiring, which misses the next surface nobody told it about. "Member" is a live, non-deactivated session on this instance, the same `resolveSession` lookup `requireBuilder` uses, per request, no cache; a lookup that throws fails CLOSED. The exempt list is the door, the manifest and the probes and nothing wider: `/api/*/auth/*` (a door that cannot be opened locks the members out too), `/api/*/instance` (what a stranger reads to learn how to sign in AND what the runner reads after a settings restart to learn whether the value was consumed — including the push that turns this gate on), `/healthz`, `/version`, `/metrics`; the pre-launch gate's blanket `/api/` exemption is deliberately not copied. A refused page gets a self-contained 401 door (no stylesheet, no branding bundle, no script) with one control — Sign in, returning (relatively) to the page knocked on; a refused API call gets `res.fail('unauthenticated', 401)`, exactly requireBuilder's envelope, which is why it mounts after `attachFail`. A member's answer leaves marked `private, no-store` whatever the route said — the public read API sends `public, max-age`, and a CDN edge would otherwise serve a member's copy to the next stranger. The exempt list covers every API prefix (the versioned contract too), `/downloads/*` and Caddy's TLS ask; a request's session is resolved once (`resolveSession` memoizes per request). The hub's manage page shows the CHOICE from the platform's row and what the project RUNS from the project's own public manifest (`brand.project`), and never confuses the two: `push.message` is rendered verbatim, `saved` moves only the radio, the "runs" line is written only from the manifest and says "doesn't report this setting yet" for a core that predates the channel. Rejected: per-surface wiring; a 302 into OAuth for a refused page (sends assets and crawlers to GitHub); copying the `/api/` exemption; painting the radio from the manifest or the runs line from `saved`; reusing the pre-launch gate.](<redacted>.md) | instance / project visibility |
272
272
  | 0192 | [**A work category orients a human and authorises nothing** ([task 1003275](https://cloudbongos.com/builders#/task/1003275), goal 1000072, criterion `legibility-categories`). A goal gains a human CATEGORY from an owner-named set of eight (Builders Hall, Context Management, Government (Decision Making), Modules, Security, Economics, Legal, User/ABI Experience) so a person scanning the board can tell at a glance which goals are about money, which about the walls, which about the hall — today each goal's own words say so, but only if you read all thirty-eight. The DECISION is the constraint, not the feature: the owner said twice that this organisation has no hierarchical purpose and no effect on the agentic management of the work, and the system ALREADY has a load-bearing hierarchy (ADR 0086 — the scope_modules wall, rank floors, goal membership). The goal's own prose predicts the failure: a second thing that looks like a hierarchy gets mistaken for one within a release, and the mistake arrives as ONE plausible line in a gate ('skip Legal goals in the feed'), after which work is refused because of a label nobody meant as a permission. So: no authority, claim-eligibility, feed or dependency surface may read a category — enforced by the `scripts/gds/category-advisory-guard.js` fitness check, which scans every decision surface with comments stripped (a gate may CITE the rule, never branch on it) AND asserts the feature still exists, so a renamed-away subject fails loudly instead of passing vacuously (the ADR 0022 assurance-of-assurance finding). Uncategorised is LEGAL — nullable, no default, required by no write — because the criterion is explicit that Claude failing to place a goal is a designed prompt, not an error. A task stores no category and INHERITS its goal's through `tasks.goal_id`; a stored copy would be a second answer to one question. The set is a table, not an enum (runtime-extensible without a migration per addition) and retiring is `active = false`, never a DELETE, so a goal keeps rendering a label someone tidied away. The three route-rank pins are a tripwire: if one ever needs a WIDER gate than its neighbours, the label has grown into a permission. Deliberately NOT built: a per-category page or filtered navigation view — a chip is a label, a drill-down destination is a tier. Making a category gate anything is an owner decision and needs its own ADR first.](<redacted>.md) | GDS / goals |
273
273
  | 0193 | [**Pause task-scoped box slices: a box serves its rank scope (the full repo for Metic+)** ([task 1003285](https://cloudbongos.com/builders#/task/1003285), owner decision from the 2026-08-25 dev-box efficiency review). Pauses, does not repeal, ADR 0148 / goal 1000051: `routes/box.js` skips the lifecycle-port `scopeKeys` resolution unless `BOX_TASK_SCOPE=on`, so `decideSourceAccess` takes its documented rank fallback — full repo for Metic+, starter surface below. WHY: the slice was defense-in-depth git cannot hard-enforce (ADR 0031 §6's honest limit) while its costs were real on the live fleet — sessions on a box that cannot see a module (or its nested CLAUDE.md) misdiagnose themselves as stuck, the scope machinery kept failing in the field (task 1002729's base-slice collapse), and every live box user is Metic+ anyway, so the slice narrowed trusted builders while newcomers were already gated by rank. UNCHANGED: live-rank allow/deny per request (ADR 0016), the provision floor and demotion clawback, the read-only per-request credential (ADR 0055 — push still 403s, re-verified on hardware in task 1003253), and sub-Metic starter boxes. `GET /box/scope` now reports `task_scoping_paused` so the hall/app can say why `task_scope` is false; the app task-picker's "scoped to `<modules>` and nothing else" copy overstates while paused (cosmetic). Revert = set the env flag; make-permanent = delete the resolution + a follow-up ADR.](<redacted>.md) | dev-box |
274
+ | 0194 | [**A project's membership door is one decision, answered the same way everywhere a stranger meets it** ([task 1003044](https://cloudbongos.com/builders#/task/1003044), goal 1000058). The owner's 2026-08-16 joinability setting — open / apply / invite_only — was stored (1003040), pushed (1003139/1003140) and resolved into `branding().project.joinability`, enforced by nothing. Decision: ONE reader, `joinabilityMode()` in `auth-admission.js`, and `openEnrollmentEnabled()` becomes `mode === 'open'`, so the four sign-in call sites, `webAdmissionStatus` and the manifest's `admission.policy` follow untouched (SR-19: one gate, no drift). The legacy `OPEN_XENOS_ENROLLMENT` flag still opens an APPLY project — a self-hosted instance that set it keeps its door across the upgrade — but never reopens an explicit invite_only; an unreadable pack or a value this core cannot read fails CLOSED (invite_only), the ADR 0192 rule. The public `POST /access-requests` answers the door FIRST — before validation, before any lookup — `403 invite_only` / `409 open_enrollment` with plain sentences: no query, nothing revealed about who exists, and DB-free enough for the real booted app to prove. An Archon's invite is the row the gate already reads: `POST /access-requests/invite` writes `access_requests` at 'invited' (resolving a pending one in place), so sign-in consults nothing new — surfaced as "Invite by GitHub username" on the Watch page. The door is SAID wherever a stranger meets it, in the project's words: the hall landing renders one door per mode from the injected pack, the web 403 names the invite-only door and the way in, the CLI stops instead of filing, the hub's join relay names the door in the hub's own words (a project is trusted to build, not to write prose into the platform's UI) instead of painting a refusal as "requested" or "unreachable". `admission.policy` stays binary (pinned; agrees for free); `joinabilityMode` joins the doorway — a human MINOR by ADR 0161 §2, which the publish lane's LIVE test refused (HEAD's version must be a CI carrier's), so it rides the next CI patch and the contradiction is filed as an idea. The hub's `#mJoinability` clones `#mVisibility` rather than a factory — a third setting is where a factory pays. Rejected: folding the flag into `branding()` (memoized vs per-call; the manifest must stay the pushed value); a 200 for the open door (an older CLI would wait thirty minutes for an approval); re-deriving the mode inside the module; a second invite table.](<redacted>.md) | instance / project joinability |
274
275
 
275
276
  > ⚠️ **Numbering collision (known):** two ADRs share `0008`. The numbers were assigned in parallel sessions before the lock could be enforced. The files are kept under their existing filenames to preserve git history, but the table disambiguates them as `0008-a` / `0008-b`. New ADRs should resume at `0040` to avoid further collisions. (`0024` is also a real on-disk duplicate: the cloneable-repo/local-first-memory ADR and the multi-agent-system-architecture ADR were both numbered `0024` in parallel; both keep their filenames, disambiguated as `0024-a` / `0024-b` like `0008`. Likewise `0025` ended up with two on-disk files — the offsite-backup ADR and the structured-criterion↔task-link ADR — both kept under their existing filenames and disambiguated as `0025-a` / `0025-b`; separately, the BFG ADR had also claimed `0025` on 2026-05-30 and yielded to `0026`. A third happened 2026-06-06: `0036` was claimed in parallel by the builders-subdomain ADR ([#726](https://example.com/builders#/task/726)) and this Discord-channels ADR ([#727](https://example.com/builders#/task/727)); the Discord-channels ADR yielded to `0037`. A fourth landed 2026-06-10: `0044` was claimed in parallel by the palette-replacement ADR and the per-box game-preview ADR ([#897](https://example.com/builders#/task/897)); both keep their filenames, disambiguated as `0044-a` / `0044-b` like `0008`. A fifth was caught pre-land 2026-06-13: `0050` was claimed in parallel by the device-flow-admission ADR ([#1000](https://example.com/builders#/task/1000)) and the sandbox-for-everyone ADR ([#1001](https://example.com/builders#/task/1001)); the latter yielded to `0052` before merging, so there is no `0050` duplicate. A sixth was caught pre-land 2026-06-21: the tool-agnostic-design-layer ADR ([#1375](https://example.com/builders#/task/1375)) first collided at `0079` with the value-gated-autonomous-engagement ADR, then at `0080` with the llm-cache-api-transport ADR (both part of the 0075–0080 LLM-cost-reduction series, landed on `main` in parallel); it yielded to `0081` before merging, so there is no `0079`/`0080` design-layer duplicate. A seventh landed 2026-06-23: `0084` was claimed in parallel by the auto-derived-per-task-rank-floor ADR and the remove-default-spend-cap ADR (task 1499); the rank-floor ADR landed on `main` first, so the spend-cap ADR yielded to `0085`, leaving no `0084` duplicate. An eighth landed 2026-06-27: `0095` was claimed in parallel by the borrowed-memory-and-retrieval-concepts ADR (PR #459, landed first) and the cross-agent-context-management ADR ([#1651](https://example.com/builders#/task/1651), PR #460); both keep their filenames, disambiguated as `0095-a` / `0095-b` like `0008`. These two are companions — `0095-a` adopts the memory/retrieval concepts and explicitly defers the grep-vs-semantic question to `0095-b`.)