@bongos/core 1.19.419 → 1.19.420
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 +58 -43
- package/.claude/skills/new-project/SKILL.md +22 -11
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +5 -1
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/adr/0209-one-budget-for-the-account-existence-oracle.md +79 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +103 -5
- package/docs/api-reference.md +4 -3
- package/docs/copy-inventory.md +186 -174
- package/docs/copy-registry.json +348 -238
- package/docs/file-map.md +1 -0
- package/docs/module-api-changelog.md +2 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +2 -0
- package/modules/onboarding/routes/access-requests.js +16 -3
- package/modules/platform-identity/platform-identity.js +5 -5
- package/modules/platform-identity/tests/platform-identity.mjs +20 -0
- package/modules/provisioning/migrations/provisioning_013_project_detail.sql +54 -0
- package/modules/provisioning/onboard-plan.js +13 -0
- package/modules/provisioning/provisioning.js +113 -5
- package/modules/provisioning/routes/provisioning.js +105 -0
- package/modules/provisioning/tests/provisioning.mjs +84 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/projects.html +161 -42
- package/scripts/gds/fitness.js +9 -0
- package/scripts/gds/onboard.js +41 -1
- package/scripts/gds/provision-net.js +7 -3
- package/src/bongos/middleware/rate-limit.js +68 -2
- package/src/bongos/routes/auth.js +19 -12
- package/src/module-api.js +13 -1
- package/tests/account_existence_rate_limit.mjs +375 -0
- package/tests/module_api.mjs +1 -0
- package/tests/onboard.mjs +56 -0
- package/tests/projects_hub.mjs +69 -0
- package/tests/projects_hub_pre_uat.mjs +27 -5
- package/tests/provision.mjs +20 -0
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.420",
|
|
6
|
+
"core_contract": "1.19.420",
|
|
7
|
+
"source_commit": "059167905bf23b4894c42e7e705ae7a4985e44b1",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-29T17:
|
|
9
|
+
"built_at": "2026-08-29T17:36:00.281Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 359,
|
|
13
13
|
"agent_docs_stubbed": 23,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1712,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2094,
|
|
20
|
+
"tree_sha256": "a7b3cb6487d5d1efdfb01330f81239e7e7000cbebf3ec2ca6d8bcd13d5e9dd87",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -192,7 +192,7 @@
|
|
|
192
192
|
{
|
|
193
193
|
"path": ".claude/skills/new-project/SKILL.md",
|
|
194
194
|
"mode": "0000644",
|
|
195
|
-
"sha256": "
|
|
195
|
+
"sha256": "21ae14d681f0b8a7056b320c764ffbaa88c9cf777afc67589049e4778bf98d3b"
|
|
196
196
|
},
|
|
197
197
|
{
|
|
198
198
|
"path": ".claude/skills/otb-character-review/SKILL.md",
|
|
@@ -352,12 +352,12 @@
|
|
|
352
352
|
{
|
|
353
353
|
"path": "clients/bongos-client/README.md",
|
|
354
354
|
"mode": "0000644",
|
|
355
|
-
"sha256": "
|
|
355
|
+
"sha256": "f190d3844d06a676480e270b05f14c13bd797915a43619be0a18affa06117000"
|
|
356
356
|
},
|
|
357
357
|
{
|
|
358
358
|
"path": "clients/bongos-client/bongos-client.global.js",
|
|
359
359
|
"mode": "0000644",
|
|
360
|
-
"sha256": "
|
|
360
|
+
"sha256": "562e26ce56898840c4bacf89ab7b31f7f80b30fa393374a3f8dc9c7ef973e48f"
|
|
361
361
|
},
|
|
362
362
|
{
|
|
363
363
|
"path": "clients/bongos-client/examples/hello-world.mjs",
|
|
@@ -367,17 +367,17 @@
|
|
|
367
367
|
{
|
|
368
368
|
"path": "clients/bongos-client/index.cjs",
|
|
369
369
|
"mode": "0000644",
|
|
370
|
-
"sha256": "
|
|
370
|
+
"sha256": "a4b6049666626ab1a99c8c30b0e8fa0ce40e11ba3e4ae61e5763c6ce6338a5e5"
|
|
371
371
|
},
|
|
372
372
|
{
|
|
373
373
|
"path": "clients/bongos-client/index.d.ts",
|
|
374
374
|
"mode": "0000644",
|
|
375
|
-
"sha256": "
|
|
375
|
+
"sha256": "272b28e3c36e61ad0f7d578679a6c097238807fb284f130dbcd132d618c865fb"
|
|
376
376
|
},
|
|
377
377
|
{
|
|
378
378
|
"path": "clients/bongos-client/index.mjs",
|
|
379
379
|
"mode": "0000644",
|
|
380
|
-
"sha256": "
|
|
380
|
+
"sha256": "a8ea6b45b485350bb0190839cd89429706f9a046c9d2be2a7fe99555e691b852"
|
|
381
381
|
},
|
|
382
382
|
{
|
|
383
383
|
"path": "clients/bongos-client/package.json",
|
|
@@ -1539,20 +1539,25 @@
|
|
|
1539
1539
|
"mode": "0000644",
|
|
1540
1540
|
"sha256": "72c859bdfc70fd4fc0d104439985a43eaf60cce281bbaae8b8faaffb40546973"
|
|
1541
1541
|
},
|
|
1542
|
+
{
|
|
1543
|
+
"path": "docs/adr/0209-one-budget-for-the-account-existence-oracle.md",
|
|
1544
|
+
"mode": "0000644",
|
|
1545
|
+
"sha256": "bdf26860d1f10413a30a770ab420d49fdf58f922d75e23fb1cf9f174f7e469e4"
|
|
1546
|
+
},
|
|
1542
1547
|
{
|
|
1543
1548
|
"path": "docs/adr/README.md",
|
|
1544
1549
|
"mode": "0000644",
|
|
1545
|
-
"sha256": "
|
|
1550
|
+
"sha256": "19b8ba0e720f3df34e710c074dfeeabb64729acb4bd42f08c0378470dbfc3c99"
|
|
1546
1551
|
},
|
|
1547
1552
|
{
|
|
1548
1553
|
"path": "docs/api-reference.md",
|
|
1549
1554
|
"mode": "0000644",
|
|
1550
|
-
"sha256": "
|
|
1555
|
+
"sha256": "d4295ad9e1a6e0003f804fd1e9541414b2337d61469b58844e2f8008a893413c"
|
|
1551
1556
|
},
|
|
1552
1557
|
{
|
|
1553
1558
|
"path": "docs/api/openapi.json",
|
|
1554
1559
|
"mode": "0000644",
|
|
1555
|
-
"sha256": "
|
|
1560
|
+
"sha256": "1fff6c12d94b54f129023b50fb469652e2d558b816c7de6d8906f3ae772be47b"
|
|
1556
1561
|
},
|
|
1557
1562
|
{
|
|
1558
1563
|
"path": "docs/architecture.md",
|
|
@@ -1577,12 +1582,12 @@
|
|
|
1577
1582
|
{
|
|
1578
1583
|
"path": "docs/copy-inventory.md",
|
|
1579
1584
|
"mode": "0000644",
|
|
1580
|
-
"sha256": "
|
|
1585
|
+
"sha256": "09df23ba8d777ddaab052ef0362f2ab3039818915101c3d7360893ba3516ee3a"
|
|
1581
1586
|
},
|
|
1582
1587
|
{
|
|
1583
1588
|
"path": "docs/copy-registry.json",
|
|
1584
1589
|
"mode": "0000644",
|
|
1585
|
-
"sha256": "
|
|
1590
|
+
"sha256": "d250537ce6978a0611104b5ece649905204beaf56fc63a923cf87410ab8c5a33"
|
|
1586
1591
|
},
|
|
1587
1592
|
{
|
|
1588
1593
|
"path": "docs/design/apex-pass-2-direction.md",
|
|
@@ -1917,7 +1922,7 @@
|
|
|
1917
1922
|
{
|
|
1918
1923
|
"path": "docs/file-map.md",
|
|
1919
1924
|
"mode": "0000644",
|
|
1920
|
-
"sha256": "
|
|
1925
|
+
"sha256": "3d5b793c64f79c7c867f63440fbb949022426b0c849e3536f0988ebaa38e396b"
|
|
1921
1926
|
},
|
|
1922
1927
|
{
|
|
1923
1928
|
"path": "docs/handoff-template.md",
|
|
@@ -1927,7 +1932,7 @@
|
|
|
1927
1932
|
{
|
|
1928
1933
|
"path": "docs/module-api-changelog.md",
|
|
1929
1934
|
"mode": "0000644",
|
|
1930
|
-
"sha256": "
|
|
1935
|
+
"sha256": "c59a19d11ab8ac21bdad72060f87d43b2e8774173dd7465ad45c30187ce44016"
|
|
1931
1936
|
},
|
|
1932
1937
|
{
|
|
1933
1938
|
"path": "docs/modules-contract.md",
|
|
@@ -3237,7 +3242,7 @@
|
|
|
3237
3242
|
{
|
|
3238
3243
|
"path": "modules/dev-box/app/src/vendor/bongos-client.cjs",
|
|
3239
3244
|
"mode": "0000644",
|
|
3240
|
-
"sha256": "
|
|
3245
|
+
"sha256": "a4b6049666626ab1a99c8c30b0e8fa0ce40e11ba3e4ae61e5763c6ce6338a5e5"
|
|
3241
3246
|
},
|
|
3242
3247
|
{
|
|
3243
3248
|
"path": "modules/dev-box/app/test/unit.mjs",
|
|
@@ -4757,7 +4762,7 @@
|
|
|
4757
4762
|
{
|
|
4758
4763
|
"path": "modules/onboarding/routes/access-requests.js",
|
|
4759
4764
|
"mode": "0000644",
|
|
4760
|
-
"sha256": "
|
|
4765
|
+
"sha256": "254eaab3d13d796fb19a029ddc00d429ee5e28827003c407cfdd969d9d9aab28"
|
|
4761
4766
|
},
|
|
4762
4767
|
{
|
|
4763
4768
|
"path": "modules/onboarding/routes/onboarding.js",
|
|
@@ -4882,7 +4887,7 @@
|
|
|
4882
4887
|
{
|
|
4883
4888
|
"path": "modules/platform-identity/platform-identity.js",
|
|
4884
4889
|
"mode": "0000644",
|
|
4885
|
-
"sha256": "
|
|
4890
|
+
"sha256": "cd9aabc9c78964de0a93387b9edd46ee03a0f7c5e6d3f1153b6274204c27a2b6"
|
|
4886
4891
|
},
|
|
4887
4892
|
{
|
|
4888
4893
|
"path": "modules/platform-identity/pollers/code-sweep.js",
|
|
@@ -4932,7 +4937,7 @@
|
|
|
4932
4937
|
{
|
|
4933
4938
|
"path": "modules/platform-identity/tests/platform-identity.mjs",
|
|
4934
4939
|
"mode": "0000644",
|
|
4935
|
-
"sha256": "
|
|
4940
|
+
"sha256": "6b523f1fbecaaa29310d3439cc1e4164f7dba3d01057812ccb4ac71bca861b68"
|
|
4936
4941
|
},
|
|
4937
4942
|
{
|
|
4938
4943
|
"path": "modules/platform-identity/ui/.gitkeep",
|
|
@@ -5004,6 +5009,11 @@
|
|
|
5004
5009
|
"mode": "0000644",
|
|
5005
5010
|
"sha256": "74c46df38eab2b8c0f59b2da423691b170e3e85a7ec9857fad239b912bca99f4"
|
|
5006
5011
|
},
|
|
5012
|
+
{
|
|
5013
|
+
"path": "modules/provisioning/migrations/provisioning_013_project_detail.sql",
|
|
5014
|
+
"mode": "0000644",
|
|
5015
|
+
"sha256": "7abe41633cd7f98712c39960fba7175f0bb3340c4568bfbfdc640af911a31e15"
|
|
5016
|
+
},
|
|
5007
5017
|
{
|
|
5008
5018
|
"path": "modules/provisioning/module.json",
|
|
5009
5019
|
"mode": "0000644",
|
|
@@ -5012,7 +5022,7 @@
|
|
|
5012
5022
|
{
|
|
5013
5023
|
"path": "modules/provisioning/onboard-plan.js",
|
|
5014
5024
|
"mode": "0000644",
|
|
5015
|
-
"sha256": "
|
|
5025
|
+
"sha256": "31f14cbf3d649c25f1f17c508ae19be2f39d9e1991fde17cdce08cb15837ffa1"
|
|
5016
5026
|
},
|
|
5017
5027
|
{
|
|
5018
5028
|
"path": "modules/provisioning/pollers/liveness-sweep.js",
|
|
@@ -5022,7 +5032,7 @@
|
|
|
5022
5032
|
{
|
|
5023
5033
|
"path": "modules/provisioning/provisioning.js",
|
|
5024
5034
|
"mode": "0000644",
|
|
5025
|
-
"sha256": "
|
|
5035
|
+
"sha256": "b643caae4ce9e8cfa8d5f3dccbfe41e709b89ce6820ca0422aee3ac184f45a12"
|
|
5026
5036
|
},
|
|
5027
5037
|
{
|
|
5028
5038
|
"path": "modules/provisioning/rate-limit.js",
|
|
@@ -5032,7 +5042,7 @@
|
|
|
5032
5042
|
{
|
|
5033
5043
|
"path": "modules/provisioning/routes/provisioning.js",
|
|
5034
5044
|
"mode": "0000644",
|
|
5035
|
-
"sha256": "
|
|
5045
|
+
"sha256": "100c0f73c26c1c4858009d8ce324b99e632c30725f32df84542ff7862bd339c2"
|
|
5036
5046
|
},
|
|
5037
5047
|
{
|
|
5038
5048
|
"path": "modules/provisioning/tests/liveness.mjs",
|
|
@@ -5042,7 +5052,7 @@
|
|
|
5042
5052
|
{
|
|
5043
5053
|
"path": "modules/provisioning/tests/provisioning.mjs",
|
|
5044
5054
|
"mode": "0000644",
|
|
5045
|
-
"sha256": "
|
|
5055
|
+
"sha256": "38bc7eb2f1dfd3c03f2c48f42683821ebefbe68fafec95dec491bb3df926b7e0"
|
|
5046
5056
|
},
|
|
5047
5057
|
{
|
|
5048
5058
|
"path": "modules/provisioning/ui/.gitkeep",
|
|
@@ -5467,12 +5477,12 @@
|
|
|
5467
5477
|
{
|
|
5468
5478
|
"path": "package-lock.json",
|
|
5469
5479
|
"mode": "0000644",
|
|
5470
|
-
"sha256": "
|
|
5480
|
+
"sha256": "4e7b2bce0bc55c3eba541e39d4d28348353108eb837b9469b02267bdb42e9628"
|
|
5471
5481
|
},
|
|
5472
5482
|
{
|
|
5473
5483
|
"path": "package.json",
|
|
5474
5484
|
"mode": "0000644",
|
|
5475
|
-
"sha256": "
|
|
5485
|
+
"sha256": "173eb6f8f5be1226bd5193296aa3f0e8700b185ee850f250d8a6357cf5f3b6e6"
|
|
5476
5486
|
},
|
|
5477
5487
|
{
|
|
5478
5488
|
"path": "public-docs/index.html",
|
|
@@ -5922,7 +5932,7 @@
|
|
|
5922
5932
|
{
|
|
5923
5933
|
"path": "public-landing/projects.html",
|
|
5924
5934
|
"mode": "0000644",
|
|
5925
|
-
"sha256": "
|
|
5935
|
+
"sha256": "274377ffb14a84b47867728bfa04a8198b57f9b13bcb3e16cadcd1a6ae2810d9"
|
|
5926
5936
|
},
|
|
5927
5937
|
{
|
|
5928
5938
|
"path": "public-landing/projects.probes.json",
|
|
@@ -6377,7 +6387,7 @@
|
|
|
6377
6387
|
{
|
|
6378
6388
|
"path": "scripts/gds/fitness.js",
|
|
6379
6389
|
"mode": "0000644",
|
|
6380
|
-
"sha256": "
|
|
6390
|
+
"sha256": "ec823ee34e55318a1bee8008658fe44c24158e43a3736065312ae931d0bab796"
|
|
6381
6391
|
},
|
|
6382
6392
|
{
|
|
6383
6393
|
"path": "scripts/gds/gate-review.js",
|
|
@@ -6647,7 +6657,7 @@
|
|
|
6647
6657
|
{
|
|
6648
6658
|
"path": "scripts/gds/onboard.js",
|
|
6649
6659
|
"mode": "0000644",
|
|
6650
|
-
"sha256": "
|
|
6660
|
+
"sha256": "bc169c26bfcddb6789e5fd7f8c2fc53bdaccadf75b7ea4dc2a96ea81d3f92c4d"
|
|
6651
6661
|
},
|
|
6652
6662
|
{
|
|
6653
6663
|
"path": "scripts/gds/onboarding-analyze.js",
|
|
@@ -6697,7 +6707,7 @@
|
|
|
6697
6707
|
{
|
|
6698
6708
|
"path": "scripts/gds/provision-net.js",
|
|
6699
6709
|
"mode": "0000644",
|
|
6700
|
-
"sha256": "
|
|
6710
|
+
"sha256": "01b6ef89cbef4d898934c1802dec416c6085f0dde5ae49012bd2fa7a14bb9589"
|
|
6701
6711
|
},
|
|
6702
6712
|
{
|
|
6703
6713
|
"path": "scripts/gds/provision-repo.js",
|
|
@@ -7407,7 +7417,7 @@
|
|
|
7407
7417
|
{
|
|
7408
7418
|
"path": "src/bongos/middleware/rate-limit.js",
|
|
7409
7419
|
"mode": "0000644",
|
|
7410
|
-
"sha256": "
|
|
7420
|
+
"sha256": "cd28f4517614b4b812aba3c051674ebaff8e78fd741a2e464966b687c459a654"
|
|
7411
7421
|
},
|
|
7412
7422
|
{
|
|
7413
7423
|
"path": "src/bongos/module-scope-map.js",
|
|
@@ -7477,7 +7487,7 @@
|
|
|
7477
7487
|
{
|
|
7478
7488
|
"path": "src/bongos/routes/auth.js",
|
|
7479
7489
|
"mode": "0000644",
|
|
7480
|
-
"sha256": "
|
|
7490
|
+
"sha256": "4c26db2cae7070a44523053d369da83a41ed38677b46292da927b061a398a4eb"
|
|
7481
7491
|
},
|
|
7482
7492
|
{
|
|
7483
7493
|
"path": "src/bongos/routes/backup.js",
|
|
@@ -7592,7 +7602,7 @@
|
|
|
7592
7602
|
{
|
|
7593
7603
|
"path": "src/module-api.js",
|
|
7594
7604
|
"mode": "0000644",
|
|
7595
|
-
"sha256": "
|
|
7605
|
+
"sha256": "f2d4f01228f6c10d90a87717756c696e1a0c37d52872f735f2e26da19ea4cb66"
|
|
7596
7606
|
},
|
|
7597
7607
|
{
|
|
7598
7608
|
"path": "src/module-loader/catalog.js",
|
|
@@ -7644,6 +7654,11 @@
|
|
|
7644
7654
|
"mode": "0000644",
|
|
7645
7655
|
"sha256": "4d8435e7f8b949715ee40843beddc1868105edd9c67b269cfa8f510a192e9212"
|
|
7646
7656
|
},
|
|
7657
|
+
{
|
|
7658
|
+
"path": "tests/account_existence_rate_limit.mjs",
|
|
7659
|
+
"mode": "0000644",
|
|
7660
|
+
"sha256": "41170e6fa3bc063770a39f4fe5b21e049ae91c8c4115b3b2152a7e464420fb6f"
|
|
7661
|
+
},
|
|
7647
7662
|
{
|
|
7648
7663
|
"path": "tests/account_privacy_flags.mjs",
|
|
7649
7664
|
"mode": "0000644",
|
|
@@ -9367,7 +9382,7 @@
|
|
|
9367
9382
|
{
|
|
9368
9383
|
"path": "tests/module_api.mjs",
|
|
9369
9384
|
"mode": "0000644",
|
|
9370
|
-
"sha256": "
|
|
9385
|
+
"sha256": "9b2394ce72e74a96460c6d9ef1cc2ee280dded85523fae2979d56a49fb514b85"
|
|
9371
9386
|
},
|
|
9372
9387
|
{
|
|
9373
9388
|
"path": "tests/module_catalog.mjs",
|
|
@@ -9467,7 +9482,7 @@
|
|
|
9467
9482
|
{
|
|
9468
9483
|
"path": "tests/onboard.mjs",
|
|
9469
9484
|
"mode": "0000644",
|
|
9470
|
-
"sha256": "
|
|
9485
|
+
"sha256": "3065c1ef3a57e3cd53b4e18bf7193cc3fe93efa012958628e8c99d6b9bfd8eae"
|
|
9471
9486
|
},
|
|
9472
9487
|
{
|
|
9473
9488
|
"path": "tests/onboarding_gate.mjs",
|
|
@@ -9632,12 +9647,12 @@
|
|
|
9632
9647
|
{
|
|
9633
9648
|
"path": "tests/projects_hub.mjs",
|
|
9634
9649
|
"mode": "0000644",
|
|
9635
|
-
"sha256": "
|
|
9650
|
+
"sha256": "b4090f624c1887dede854f67dfcb5c5dd851fdd90b048f6a649fcf651f70bc27"
|
|
9636
9651
|
},
|
|
9637
9652
|
{
|
|
9638
9653
|
"path": "tests/projects_hub_pre_uat.mjs",
|
|
9639
9654
|
"mode": "0000644",
|
|
9640
|
-
"sha256": "
|
|
9655
|
+
"sha256": "ccf476dbf012724c8b5de2080decd322eed3febb97d4079bfda7f0e1ec2ed733"
|
|
9641
9656
|
},
|
|
9642
9657
|
{
|
|
9643
9658
|
"path": "tests/promote_goal_id.mjs",
|
|
@@ -9652,7 +9667,7 @@
|
|
|
9652
9667
|
{
|
|
9653
9668
|
"path": "tests/provision.mjs",
|
|
9654
9669
|
"mode": "0000644",
|
|
9655
|
-
"sha256": "
|
|
9670
|
+
"sha256": "9744b866060ae83fc560f65e579fc30e73957ff21329994ca3d581dbe1726053"
|
|
9656
9671
|
},
|
|
9657
9672
|
{
|
|
9658
9673
|
"path": "tests/provision_restart.mjs",
|
|
@@ -19,7 +19,18 @@ Interview the owner for: product name, world name, company; the **GitHub repo**
|
|
|
19
19
|
|
|
20
20
|
**Human-only (UI-first):** have them create an **empty** GitHub repository — github.com → **New repository** → name it, leave it empty (no README), Create. That repo is where the instance's own code will live.
|
|
21
21
|
|
|
22
|
-
### 2.
|
|
22
|
+
### 2. Tell us about the project — OPTIONAL, and skipping it is fine (task 1002334)
|
|
23
|
+
|
|
24
|
+
Ask two things, and offer the skip in the same breath — a "you can leave this blank" that arrives *after* the question reads as a test:
|
|
25
|
+
|
|
26
|
+
- **What are you building?** A sentence or two. This is the project's **description**, and it is half of the publish gate ([ADR 0182](../../../docs/adr/<redacted>.md) D4: a project is on the public map iff it has a name AND a description).
|
|
27
|
+
- **Who's building it?** `solo` / `small-team` / `community`. This is what the platform's setup recommendations read.
|
|
28
|
+
|
|
29
|
+
Both ride the provisioning request as `description` and `detail: { team_shape }`. **Skipping is a supported default, not a failure:** the project stands up exactly the same, it just stays *dark matter* — off the public map and un-joinable — until a description exists. Say that plainly when the owner skips, and say the way back with it: `PATCH /provisioning/instances/:id/detail` fills either answer in later, and publishes the project the moment the description lands.
|
|
30
|
+
|
|
31
|
+
Never gate the standup on this step, and never invent an answer the owner didn't give — a recommendation built on a guess is worse than no recommendation.
|
|
32
|
+
|
|
33
|
+
### 3. DNS token preflight — BEFORE you commit the address (task 1980)
|
|
23
34
|
|
|
24
35
|
Before anything is provisioned, verify the control-plane's Cloudflare token can actually manage the domain's zone (a mismatched-zone token is exactly what broke the Emersonian standup mid-flow):
|
|
25
36
|
|
|
@@ -30,12 +41,12 @@ node scripts/gds/provision.js dns-check <domain>
|
|
|
30
41
|
- ✓ reachable → continue.
|
|
31
42
|
- ✗ not reachable → **STOP and resolve it now**, don't push past it. The command prints the fix; usually: widen the Cloudflare API token to include the domain's zone (CF dashboard → My Profile → API Tokens → edit → Zone Resources → Include → the zone), then re-run `dns-check` until green. Only then proceed.
|
|
32
43
|
|
|
33
|
-
###
|
|
44
|
+
### 4. `bongos init` — scaffold (greenfield) **or** `--adopt` (brownfield)
|
|
34
45
|
|
|
35
46
|
**This is the one leg that branches** (ADR 0121 §Decision 1). Pick the mode that matches the repo:
|
|
36
47
|
|
|
37
48
|
- **Greenfield** — a NEW, empty repo. The default below.
|
|
38
|
-
- **Brownfield / adopt** — an EXISTING repo (code, history, a backlog). Run `node bin/bongos.js init --adopt` instead. It DETECTS the repo's stack, runs a conflict **pre-flight** (migration-namespace / fitness-boundary / protected-path / CI-check collisions) and writes **nothing until you accept** (interactive y/N, or `--accept`); a genuine BLOCK (e.g. a `core_`-prefixed migration) is a hard stop, not accept-able. On accept it LAYERS the lean config + the pinned `@cloudbongos/core` dep + `.claude/` **additively** (merges-or-leaves-and-reports; never clobbers, never the `--force` path), captures the repo's **REAL** current version/goal, and imports its existing backlog (open GitHub issues via `GITHUB_TOKEN`/`GH_TOKEN`, else a `TODO`/`ROADMAP`/`BACKLOG` file). Then **skip step
|
|
49
|
+
- **Brownfield / adopt** — an EXISTING repo (code, history, a backlog). Run `node bin/bongos.js init --adopt` instead. It DETECTS the repo's stack, runs a conflict **pre-flight** (migration-namespace / fitness-boundary / protected-path / CI-check collisions) and writes **nothing until you accept** (interactive y/N, or `--accept`); a genuine BLOCK (e.g. a `core_`-prefixed migration) is a hard stop, not accept-able. On accept it LAYERS the lean config + the pinned `@cloudbongos/core` dep + `.claude/` **additively** (merges-or-leaves-and-reports; never clobbers, never the `--force` path), captures the repo's **REAL** current version/goal, and imports its existing backlog (open GitHub issues via `GITHUB_TOKEN`/`GH_TOKEN`, else a `TODO`/`ROADMAP`/`BACKLOG` file). Then **skip step 5** — adopt layered onto the existing repo, there is no empty repo to scaffold — and continue from step 6 (provision). Every other leg is identical.
|
|
39
50
|
|
|
40
51
|
Greenfield:
|
|
41
52
|
|
|
@@ -45,11 +56,11 @@ node bin/bongos.js init # interview (or: init --from spec.json for non-
|
|
|
45
56
|
|
|
46
57
|
It writes lean `config/branding.json` / `config/modules.json` / `config/hierarchy.json`, seeds the first version + its done-when goal, files the human-only kickoff tasks, and — when you opt into hosting — files a **provisioning request** (`POST /provisioning/instances`). Choose **hosting shape `standalone`**. Enable the `provisioning` module on the target so the request is honored (`config/modules.json` → `"provisioning": true`).
|
|
47
58
|
|
|
48
|
-
###
|
|
59
|
+
### 5. Scaffold the standalone repo
|
|
49
60
|
|
|
50
|
-
Materialize the instance's own repo (ADR 0108): clone the empty GitHub repo to the standalone root (`${PROVISION_STANDALONE_BASE:-/srv/cloudbongos}/<slug>` on the control plane), drop in the `config/` + `.claude/` from step
|
|
61
|
+
Materialize the instance's own repo (ADR 0108): clone the empty GitHub repo to the standalone root (`${PROVISION_STANDALONE_BASE:-/srv/cloudbongos}/<slug>` on the control plane), drop in the `config/` + `.claude/` from step 4, and pin the core as a dependency (`@cloudbongos/core` in the instance `package.json`). Commit + push. This checkout is what the standalone service will run from.
|
|
51
62
|
|
|
52
|
-
###
|
|
63
|
+
### 6. Provision — core + DB + address + service (task 1978)
|
|
53
64
|
|
|
54
65
|
The web tier only **enqueued** the request; the **control-plane runner** does the real work (it alone holds the DO/Cloudflare tokens):
|
|
55
66
|
|
|
@@ -60,7 +71,7 @@ node scripts/gds/provision.js run-intents --apply # drains the queue
|
|
|
60
71
|
|
|
61
72
|
For `standalone` this composes: allocate a port → `git pull` + `npm ci --omit=dev` (installs the pinned core) → `createdb` + **instance-root** migrate (`node_modules/@cloudbongos/core/scripts/migrate.sh`) → per-instance `/etc/<slug>/web.env` → a **standalone systemd unit** (runs the core package's `platform-server.js` from the instance repo) → DNS A-record UPSERT → per-instance Caddy snippet (on-demand TLS) → `/healthz`. It is idempotent + dry-run-by-default (omit `--apply` to preview). It **fail-fasts on the step-2 DNS preflight** if step 2's token check was skipped.
|
|
62
73
|
|
|
63
|
-
###
|
|
74
|
+
### 7. GitHub sign-in — ONE click (App Manifest flow), or a manual OAuth app + first sign-in
|
|
64
75
|
|
|
65
76
|
**Recommended — one click (task 2080, ADR 0133).** GitHub has no create-an-OAuth-App API, but it *does* let us pre-fill a **GitHub App Manifest**: the owner clicks one link, approves on GitHub, and GitHub hands the control plane the credentials automatically — **no OAuth app to hand-create, no secret to copy-paste ever.** From the control-plane hall, **Start a project → “Set up GitHub sign-in”** (the button appears once the instance is requested; it needs the instance's **domain** set). Under the hood it:
|
|
66
77
|
|
|
@@ -89,7 +100,7 @@ node scripts/gds/oauth-secret.js place <slug> --client-id <id> --client-secret <
|
|
|
89
100
|
|
|
90
101
|
**Human-only — first sign-in.** The owner opens `https://<domain>` and signs in with GitHub **once**. This seats the **founding Archon** — config alone seats no one, so this step is inherent and cannot be automated.
|
|
91
102
|
|
|
92
|
-
###
|
|
103
|
+
### 8. Verify
|
|
93
104
|
|
|
94
105
|
```bash
|
|
95
106
|
curl -s https://<domain>/healthz # → {"ok":true,"auth_configured":true}
|
|
@@ -97,11 +108,11 @@ curl -s https://<domain>/healthz # → {"ok":true,"auth_configured":tru
|
|
|
97
108
|
|
|
98
109
|
Confirm the owner is **Archon** (their profile / the hall roster). If `auth_configured` is `false`, re-run the `oauth-secret place` step and recheck the OAuth app values.
|
|
99
110
|
|
|
100
|
-
###
|
|
111
|
+
### 9. Brand
|
|
101
112
|
|
|
102
113
|
Fill the instance **look** — theme / palette / fonts in `config/branding.json` (see `docs/branding-fill-prompt.md`). The identity strings were set at init; this is the visual layer. "Brand with your own LLM."
|
|
103
114
|
|
|
104
|
-
###
|
|
115
|
+
### 10. Enter your builders hall
|
|
105
116
|
|
|
106
117
|
The hall is the point of everything above — end there, not at brand (task 1002703):
|
|
107
118
|
|
|
@@ -118,4 +129,4 @@ A new owner has gone zero → live: their own standalone repo on a pinned core,
|
|
|
118
129
|
- **Standalone only** (ADR 0108) — do not fall back to editing this monorepo's checkout for a new project.
|
|
119
130
|
- **Never weaken the trust boundary** — the web tier enqueues; only the control-plane runner (`provision.js`) touches cloud tokens (ADR 0016 / 0111 §2).
|
|
120
131
|
- **Live docs are automatic** — a standalone instance's session-log index regenerates on deploy (`provision.js` runs `regen-instance-docs.js` after migrate; `bongos init`/`upgrade` seed+refresh it), and the whole-file nav docs are gitignored so `git reset --hard` never wipes them ([ADR 0147](../../../docs/adr/<redacted>.md)). If you wire a **bespoke** deploy script for an instance, it MUST call `node node_modules/@bongos/core/scripts/gds/regen-instance-docs.js` after `git reset` + migrate — see [`docs/recipes/standalone-live-docs.md`](../../../docs/recipes/standalone-live-docs.md).
|
|
121
|
-
- **Adopting an EXISTING repo** (brownfield) is the other branch of the scaffold leg (step
|
|
132
|
+
- **Adopting an EXISTING repo** (brownfield) is the other branch of the scaffold leg (step 4) — `bongos init --adopt` ([ADR 0121](../../../docs/adr/<redacted>.md)): detect → accept-gated conflict pre-flight → additive layering (never clobbers) → real version/goal + backlog import. Built; the `bongos onboard` CLI and the hall "start a project" wizard offer the same greenfield-vs-adopt choice.
|
|
@@ -5,7 +5,7 @@ A **generated**, zero-dependency typed client for the Bongos API — produced fr
|
|
|
5
5
|
by hand; it regenerates when the spec changes, so it can never drift from the routes.
|
|
6
6
|
|
|
7
7
|
- API version: **v1** (served at `/api/bongos/v1`)
|
|
8
|
-
-
|
|
8
|
+
- 338 operations across 53 resource groups
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
@@ -641,6 +641,8 @@ function createClient(opts = {}) {
|
|
|
641
641
|
postProvisioningInstances: (args) => request("POST", "/provisioning/instances", { hasBody: true }, args),
|
|
642
642
|
// GET /provisioning/instances/{id} — rank: any-builder — GET /provisioning/instances/:id
|
|
643
643
|
getProvisioningInstancesId: (args) => request("GET", "/provisioning/instances/{id}", { hasBody: false }, args),
|
|
644
|
+
// PATCH /provisioning/instances/{id}/detail — rank: any-builder — PATCH /provisioning/instances/:id/detail
|
|
645
|
+
patchProvisioningInstancesIdDetail: (args) => request("PATCH", "/provisioning/instances/{id}/detail", { hasBody: true }, args),
|
|
644
646
|
// POST /provisioning/instances/{id}/domain — rank: any-builder — POST /provisioning/instances/:id/domain
|
|
645
647
|
postProvisioningInstancesIdDomain: (args) => request("POST", "/provisioning/instances/{id}/domain", { hasBody: true }, args),
|
|
646
648
|
// POST /provisioning/instances/{id}/force-teardown — rank: metic+archon — POST /provisioning/instances/:id/force-teardown
|
|
@@ -640,6 +640,8 @@ function createClient(opts = {}) {
|
|
|
640
640
|
postProvisioningInstances: (args) => request("POST", "/provisioning/instances", { hasBody: true }, args),
|
|
641
641
|
// GET /provisioning/instances/{id} — rank: any-builder — GET /provisioning/instances/:id
|
|
642
642
|
getProvisioningInstancesId: (args) => request("GET", "/provisioning/instances/{id}", { hasBody: false }, args),
|
|
643
|
+
// PATCH /provisioning/instances/{id}/detail — rank: any-builder — PATCH /provisioning/instances/:id/detail
|
|
644
|
+
patchProvisioningInstancesIdDetail: (args) => request("PATCH", "/provisioning/instances/{id}/detail", { hasBody: true }, args),
|
|
643
645
|
// POST /provisioning/instances/{id}/domain — rank: any-builder — POST /provisioning/instances/:id/domain
|
|
644
646
|
postProvisioningInstancesIdDomain: (args) => request("POST", "/provisioning/instances/{id}/domain", { hasBody: true }, args),
|
|
645
647
|
// POST /provisioning/instances/{id}/force-teardown — rank: metic+archon — POST /provisioning/instances/:id/force-teardown
|
|
@@ -166,6 +166,8 @@ export interface PatchMeSkillPrefsResponse { prefs: unknown; effective: unknown;
|
|
|
166
166
|
export interface PatchMeSoundPrefsResponse { prefs: unknown; effective: unknown; effective_map: unknown; volume: unknown; defaults: unknown; known_sounds: unknown }
|
|
167
167
|
export interface PatchProjectsIdFeaturedRequest { featured: boolean }
|
|
168
168
|
export interface PatchProjectsIdFeaturedResponse { project: unknown }
|
|
169
|
+
export interface PatchProvisioningInstancesIdDetailRequest { description?: string; detail?: Record<string, unknown> }
|
|
170
|
+
export interface PatchProvisioningInstancesIdDetailResponse { ok: boolean; description: unknown; detail: unknown; options: unknown }
|
|
169
171
|
export interface PatchProvisioningInstancesIdSettingsRequest { platform_visibility?: string; joinability?: string }
|
|
170
172
|
export interface PatchProvisioningInstancesIdSettingsResponse { ok: boolean; saved: unknown; push: unknown }
|
|
171
173
|
export interface PatchTaskRecommendationsIdRequest { status: "declined" | "withdrawn" }
|
|
@@ -317,7 +319,7 @@ export interface PostProvisioningInstancesIdGithubAppRequest { account?: string
|
|
|
317
319
|
export interface PostProvisioningInstancesIdGithubAppResponse { state: unknown; account: unknown; post_url: unknown; personal_url: unknown; org_url: unknown; org_login: unknown; manifest: unknown; redirect_url: unknown; expires_at: unknown }
|
|
318
320
|
export interface PostProvisioningInstancesIdRestartResponse { ok: boolean; queued: unknown; action: unknown; status: unknown; message: unknown }
|
|
319
321
|
export interface PostProvisioningInstancesIdTeardownResponse { ok: boolean; queued: unknown; action: unknown; status: unknown; superseded: unknown; message: unknown }
|
|
320
|
-
export interface PostProvisioningInstancesRequest { slug: string; target_ref?: string; hosting_shape?: string; domain?: string; no_address?: boolean; tier?: string; onboard_mode?: string; type?: string }
|
|
322
|
+
export interface PostProvisioningInstancesRequest { slug: string; target_ref?: string; hosting_shape?: string; domain?: string; no_address?: boolean; tier?: string; onboard_mode?: string; type?: string; description?: string; detail?: Record<string, unknown> }
|
|
321
323
|
export interface PostSearchMarkRequest { chunk_id?: StringifiableId; useful?: unknown }
|
|
322
324
|
export interface PostSearchRequest { expand?: unknown; q?: string; source_kind?: string; limit?: unknown }
|
|
323
325
|
export interface PostSearchResponse { expand?: unknown; chunk?: unknown; query?: unknown; count?: unknown; total_full_tokens?: unknown; results?: unknown; corpus?: unknown }
|
|
@@ -946,6 +948,8 @@ export interface BongosClient {
|
|
|
946
948
|
postProvisioningInstances(args: RequestArgs & { body: PostProvisioningInstancesRequest }): Promise<ApiResponse>;
|
|
947
949
|
/** GET /provisioning/instances/{id} — rank: any-builder */
|
|
948
950
|
getProvisioningInstancesId(args?: RequestArgs): Promise<GetProvisioningInstancesIdResponse>;
|
|
951
|
+
/** PATCH /provisioning/instances/{id}/detail — rank: any-builder */
|
|
952
|
+
patchProvisioningInstancesIdDetail(args?: RequestArgs & { body?: PatchProvisioningInstancesIdDetailRequest }): Promise<PatchProvisioningInstancesIdDetailResponse>;
|
|
949
953
|
/** POST /provisioning/instances/{id}/domain — rank: any-builder */
|
|
950
954
|
postProvisioningInstancesIdDomain(args: RequestArgs & { body: PostProvisioningInstancesIdDomainRequest }): Promise<PostProvisioningInstancesIdDomainResponse>;
|
|
951
955
|
/** POST /provisioning/instances/{id}/force-teardown — rank: metic+archon */
|
|
@@ -637,6 +637,8 @@ export function createClient(opts = {}) {
|
|
|
637
637
|
postProvisioningInstances: (args) => request("POST", "/provisioning/instances", { hasBody: true }, args),
|
|
638
638
|
// GET /provisioning/instances/{id} — rank: any-builder — GET /provisioning/instances/:id
|
|
639
639
|
getProvisioningInstancesId: (args) => request("GET", "/provisioning/instances/{id}", { hasBody: false }, args),
|
|
640
|
+
// PATCH /provisioning/instances/{id}/detail — rank: any-builder — PATCH /provisioning/instances/:id/detail
|
|
641
|
+
patchProvisioningInstancesIdDetail: (args) => request("PATCH", "/provisioning/instances/{id}/detail", { hasBody: true }, args),
|
|
640
642
|
// POST /provisioning/instances/{id}/domain — rank: any-builder — POST /provisioning/instances/:id/domain
|
|
641
643
|
postProvisioningInstancesIdDomain: (args) => request("POST", "/provisioning/instances/{id}/domain", { hasBody: true }, args),
|
|
642
644
|
// POST /provisioning/instances/{id}/force-teardown — rank: metic+archon — POST /provisioning/instances/:id/force-teardown
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# 0209 — Two routes answering one question spend one budget; whether the answer itself collapses is the owner's call
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted (the ceiling). The response-collapse half is **Deferred — owner decision**, filed as a blocker against the task.
|
|
4
|
+
- **Date:** 2026-08-29
|
|
5
|
+
- **Deciders:** Claude, building the throttle half of the account-existence task. The task body named the collapse question as an owner/Archon decision and it is left as one.
|
|
6
|
+
- **Task:** task 1003339 (goal 1000045 — public builder profiles + account security, criterion `i-control-my-own-access-and-exposure`)
|
|
7
|
+
- **Interacts with:** [ADR 0016](<redacted>.md) (the trust boundary — these two routes are deliberately outside it), [ADR 0083](<redacted>.md) (the published doorway a module reaches core through), [ADR 0091](<redacted>.md) §1 (what belongs in the bounded kernel), [ADR 0116](<redacted>.md) (the 429's envelope), [ADR 0194](<redacted>.md) (the membership door these routes report on), [ADR 0206](<redacted>.md) (the sibling account-security decision in this goal)
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
Two PUBLIC routes answer the same question — *does this GitHub login have an account here?*
|
|
12
|
+
|
|
13
|
+
- `GET /access-requests/status?github_login=X` (`modules/onboarding/routes/access-requests.js`) returns `member | invited | pending | dismissed | none` plus `admitted`.
|
|
14
|
+
- `GET /auth/web/admission-status?login=X` (`src/bongos/routes/auth.js`) returns a bare `{ admitted }`. Strictly weaker; strictly derivable from the first.
|
|
15
|
+
|
|
16
|
+
Neither was throttled, and the reason is structural rather than an oversight anyone could see locally: `rateLimitMiddleware` returns early for every non-write method, so the global limiter meters writes and nothing else. The one read limiter that exists (`publicReadRateLimitMiddleware`) is mounted only under `/public/*`. Neither of these routes is under that prefix, so both were reachable at line rate by anyone.
|
|
17
|
+
|
|
18
|
+
Scripted against a list of GitHub usernames, either route becomes a login→membership roster: who builds here, and confirmation that a specific named person is present. That is targeting information, and it is exactly what you would want before a phishing attempt aimed at a named builder. The `auth.js` comment had already stated the exposure plainly ("It is also NOT throttled") and deferred the remedy, correctly, on the grounds that hardening one route while its richer twin stayed open would accomplish nothing.
|
|
19
|
+
|
|
20
|
+
Two separable questions sit inside that, and conflating them is what kept the task open:
|
|
21
|
+
|
|
22
|
+
1. **Is unauthenticated probing throttled?** A ceiling is right under every possible answer to (2) — an enumerator is unwelcome whether or not the reply is detailed.
|
|
23
|
+
2. **Should the reply itself collapse**, so a caller who is not the subject cannot tell `pending` / `dismissed` / `none` apart from a bare "not admitted"? That changes what a real applicant sees during onboarding.
|
|
24
|
+
|
|
25
|
+
## Decision
|
|
26
|
+
|
|
27
|
+
**Ship (1) now, as ONE budget. Do not decide (2) here — file it.**
|
|
28
|
+
|
|
29
|
+
### The ceiling is one budget, not two identical ones
|
|
30
|
+
|
|
31
|
+
`accountExistenceReadRateLimit` joins `src/bongos/middleware/rate-limit.js` as limiter #4: 120 reads / 60s per IP over a single bucket map, keyed on `req.ip` (the same real-client-IP resolution the write limiter uses). It is exported as **one middleware instance, not a factory**, and the same instance is mounted on both routes.
|
|
32
|
+
|
|
33
|
+
That is the load-bearing part of this ADR. Two limiters configured `120/60s` each are indistinguishable from one shared limiter in a diff, in a code review, and in any test that only counts to the limit on each route separately — and they hand an enumerator 240/min, because alternating between the twins costs nothing. The routes are one oracle read two ways, so they get one budget. `tests/account_existence_rate_limit.mjs` proves the sharing behaviourally in both directions *and* by object identity, and the mutation that replaces the shared instance with per-route copies leaves the counting tests green while the sharing tests go red — which is precisely the regression worth a test.
|
|
34
|
+
|
|
35
|
+
120/min is the `/public/*` posture, and it clears the honest callers with wide margin: `renderAccessPendingPage` polls the boolean probe every 3s (20/min) and `bongos login` polls the rich one every 5s (12/min).
|
|
36
|
+
|
|
37
|
+
### The module reaches it through the doorway, and rate-limit.js becomes kernel
|
|
38
|
+
|
|
39
|
+
The onboarding module may require exactly one core file — the published doorway (ADR 0083). So `src/module-api.js` exports the shared instance, and `src/bongos/middleware/rate-limit.js` joins `KERNEL_FILES` in `fitness.js`. The argument is `logger.js`'s, verbatim: the abuse ceiling is a trust primitive in the same sense the authz gates are — **authz decides who may ask, this decides how often**, and an unauthenticated read route has nothing else holding it. The file requires nothing at all, so joining the roster costs no boundary and buys the import-direction guarantee on a file two surfaces now trust.
|
|
40
|
+
|
|
41
|
+
The alternative — the module vendoring its own copy, as `modules/platform-identity/routes/public-profile.js` had to before there was a doorway export — is the failure this export exists to prevent. It is also how you get two budgets by accident.
|
|
42
|
+
|
|
43
|
+
### The response collapse is deferred, with the evidence gathered
|
|
44
|
+
|
|
45
|
+
Not deferred vaguely. The consumer sweep was run first, and it is short enough to state in full:
|
|
46
|
+
|
|
47
|
+
- **Route 1 has exactly one consumer in the repo**: `scripts/gds/login.js`, the `bongos login` CLI. Its `pollDecision()` maps `member`/`invited` → admitted, `dismissed` → a hard stop, and **everything else → wait**. So `pending` and `none` are *already* indistinguishable to the only thing that reads them.
|
|
48
|
+
- **Route 2's only consumer reads `admitted` and nothing else** — the inline poller in `renderAccessPendingPage`. The pending-vs-stranger distinction that page needs comes from `webAdmissionStatus`'s `expectedSoon`, computed server-side from a direct query, never from route 1.
|
|
49
|
+
- **The generated typed clients define wrappers for both routes with zero call sites.**
|
|
50
|
+
- Therefore collapsing costs **exactly one thing**: `login.js` is the only place in the entire codebase that tells a declined applicant they were declined. Collapse the answer and that branch is unreachable — a dismissed applicant's CLI prints dots for thirty minutes and then emits an actively wrong instruction ("re-run `bongos login` once an Archon has approved you") for an approval that is never coming.
|
|
51
|
+
|
|
52
|
+
That is a real, if narrow, product regression, and it lands on a person at their worst moment with this project. It is a judgement about what an applicant is owed, not an engineering detail, so it goes to the owner rather than being settled by whoever happened to hold the task. The blocker carries this evidence and a recommendation.
|
|
53
|
+
|
|
54
|
+
One thing the collapse would **not** buy, and the blocker says so: it does not close the oracle. `admitted: true` still means "member or invited" on both routes. Collapsing `pending`/`dismissed`/`none` narrows what leaks about *applicants*; only removing the routes or putting them behind an authenticated identity would close membership enumeration itself, and neither is possible while a stranger with no account has to poll them.
|
|
55
|
+
|
|
56
|
+
### Rejected
|
|
57
|
+
|
|
58
|
+
- **A separate limiter per route, both 120/60s.** Looks identical in review, hands an enumerator double the rate, and is the exact regression the sharing tests exist to catch.
|
|
59
|
+
- **A `createReadRateLimit({ scope, limit, windowMs })` factory on the doorway.** More general and more wrong: two call sites with matching config get two buckets. The generality is what loses the property.
|
|
60
|
+
- **Vendoring the `public-profile.js` sliding window into the onboarding module.** A fourth copy, and copies drift. That file's own comment records that it vendored only because the core limiter was not on the doorway — this ADR removes that reason.
|
|
61
|
+
- **Hardening only `admission-status`** (the smaller diff). Accomplishes nothing while its richer twin stays open; the `auth.js` comment said so before this task existed.
|
|
62
|
+
- **Refactoring `publicReadRateLimitMiddleware` onto the new shared gate.** A live limiter that is not broken, on a surface this task does not own.
|
|
63
|
+
- **Collapsing the response on my own judgement because "nothing much" reads it.** "Nothing much" is not "nothing" — `dismissed` has one real consumer with real user-visible copy, and the task named this an owner decision.
|
|
64
|
+
- **Deciding it the other way — annotating both routes "acceptable for this community model" and closing.** Equally the owner's call, and equally not mine.
|
|
65
|
+
|
|
66
|
+
## Consequences
|
|
67
|
+
|
|
68
|
+
- Both account-existence probes now refuse past 120/min from one address, with the ADR 0116 envelope, `Retry-After`, and `X-RateLimit-Scope: account-existence` — the scope name is deliberately the *question*, not the route, so a caller can see the two share a budget.
|
|
69
|
+
- The limiter runs ahead of the lookups, so a refused probe issues no query. The oracle is not also a free amplifier.
|
|
70
|
+
- The doorway gains one export (a MINOR addition; `tests/module_api.mjs` records it). `src/bongos/middleware/rate-limit.js` is now held to the kernel import rule — it currently imports nothing, and must keep importing nothing but the kernel.
|
|
71
|
+
- Behind a shared NAT, ~6 simultaneous sign-ins polling at 3s would reach the ceiling. The refusal is a 429 with `Retry-After` and both pollers already retry, so this degrades to a slower poll rather than a failed sign-in — but it is the number to raise first if a real instance reports slow admissions.
|
|
72
|
+
- A leaked or exhausted budget is per-IP and in-memory: a process restart forgets it, and one abusive address cannot close the door on anybody else. Both are asserted.
|
|
73
|
+
|
|
74
|
+
## When to revisit
|
|
75
|
+
|
|
76
|
+
- **The owner answers the collapse question** (the blocker). If the answer is "collapse it", `login.js`'s declined branch needs a replacement — the honest option is a timeout message that stops promising an approval is coming.
|
|
77
|
+
- **A real instance reports admission polling hitting the ceiling** behind a shared address. Raise the limit; do not split the budget.
|
|
78
|
+
- **A third surface starts answering the same question.** It mounts this same instance. A new bucket for it would re-open exactly the hole this closed.
|
|
79
|
+
- **The routes stop needing to be public.** If a would-be builder ever arrives already holding an identity, the whole oracle can go behind `requireBuilder` and the ceiling becomes belt-and-braces.
|