@bongos/core 1.19.428 → 1.19.430
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 +69 -44
- package/clients/bongos-client/index.d.ts +4 -4
- package/docs/adr/0214-canonical-profile-url-one-resolver-dark-until-the-page-exists.md +90 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +13 -2
- package/docs/api-reference.md +1 -1
- package/docs/copy-inventory.md +137 -128
- package/docs/copy-registry.json +248 -166
- package/docs/design/mocks/hub/picker.html +421 -0
- package/docs/design/projects-hub-direction-v2.md +22 -0
- package/docs/file-map.md +2 -1
- package/docs/module-api-changelog.md +4 -0
- package/modules/hall-ui/public/profile-canonical.js +137 -0
- package/modules/hall-ui/public/profile-connections.js +20 -9
- package/modules/hall-ui/public/profile.html +9 -0
- package/modules/hall-ui/public/profile.js +5 -0
- package/modules/platform-identity/canonical-profile.js +143 -0
- package/modules/platform-identity/routes/projects.js +24 -0
- package/modules/platform-identity/routes/sso.js +7 -0
- package/modules/provisioning/module.json +1 -1
- package/modules/provisioning/provisioning.js +63 -2
- package/modules/provisioning/routes/provisioning.js +28 -6
- package/modules/provisioning/tests/provisioning.mjs +34 -0
- package/modules/ui-design/kit/fixtures/featured-hub.json +1 -1
- package/modules/ui-design/kit/fixtures/featured.json +1 -1
- package/modules/ui-design/kit/fixtures/provisioning-instances.json +1 -1
- package/modules/ui-design/kit/serve.js +28 -4
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/cosmos.css +37 -0
- package/public-landing/projects.html +164 -5
- package/public-landing/projects.probes.json +12 -0
- package/public-landing/projects.states.json +3 -2
- package/src/bongos/routes/builders.js +24 -2
- package/src/bongos/serve-internal.js +28 -2
- package/src/module-api.js +1 -1
- package/tests/canonical_profile_url.mjs +628 -0
- package/tests/profile_connections_ui.mjs +38 -0
- package/tests/profile_route.mjs +30 -5
- package/tests/projects_hub.mjs +122 -7
- package/tests/provisioning_settings.mjs +81 -2
- package/tests/provisioning_settings_env.mjs +6 -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.430",
|
|
6
|
+
"core_contract": "1.19.430",
|
|
7
|
+
"source_commit": "262b66056ab6867973959a2b1eacff9f625bca07",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-
|
|
9
|
+
"built_at": "2026-08-29T21:13:01.178Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 364,
|
|
13
13
|
"agent_docs_stubbed": 23,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1738,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2125,
|
|
20
|
+
"tree_sha256": "4781e80e202329cceb896cb95f60c931bd52807b92019cf69855068527aa6bc4",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -372,7 +372,7 @@
|
|
|
372
372
|
{
|
|
373
373
|
"path": "clients/bongos-client/index.d.ts",
|
|
374
374
|
"mode": "0000644",
|
|
375
|
-
"sha256": "
|
|
375
|
+
"sha256": "f9249af254590fbc92dd50837af339f6d8995db613ee7aeb2b19405262751dd0"
|
|
376
376
|
},
|
|
377
377
|
{
|
|
378
378
|
"path": "clients/bongos-client/index.mjs",
|
|
@@ -1564,20 +1564,25 @@
|
|
|
1564
1564
|
"mode": "0000644",
|
|
1565
1565
|
"sha256": "c3aba5879f639ea409d80bc8545085fc1892fe191868ac69c9be52507750f33a"
|
|
1566
1566
|
},
|
|
1567
|
+
{
|
|
1568
|
+
"path": "docs/adr/0214-canonical-profile-url-one-resolver-dark-until-the-page-exists.md",
|
|
1569
|
+
"mode": "0000644",
|
|
1570
|
+
"sha256": "b964e1674d615cb4525e94bb4cac63d4ac0813e3c1ea9236620bf030d7c53124"
|
|
1571
|
+
},
|
|
1567
1572
|
{
|
|
1568
1573
|
"path": "docs/adr/README.md",
|
|
1569
1574
|
"mode": "0000644",
|
|
1570
|
-
"sha256": "
|
|
1575
|
+
"sha256": "9b2d411decbc309773631064ed286ee0d134860ec688204ac45b827a28748d7a"
|
|
1571
1576
|
},
|
|
1572
1577
|
{
|
|
1573
1578
|
"path": "docs/api-reference.md",
|
|
1574
1579
|
"mode": "0000644",
|
|
1575
|
-
"sha256": "
|
|
1580
|
+
"sha256": "50d3d00e2183fb0e1065e36a0e61a0ef2dee5cf76e51042c1c284ffc539a7f03"
|
|
1576
1581
|
},
|
|
1577
1582
|
{
|
|
1578
1583
|
"path": "docs/api/openapi.json",
|
|
1579
1584
|
"mode": "0000644",
|
|
1580
|
-
"sha256": "
|
|
1585
|
+
"sha256": "85d13548f3c1d43bed1f157b4992eda7a2948386d851ed87d1d5971d21f4a8b1"
|
|
1581
1586
|
},
|
|
1582
1587
|
{
|
|
1583
1588
|
"path": "docs/architecture.md",
|
|
@@ -1602,12 +1607,12 @@
|
|
|
1602
1607
|
{
|
|
1603
1608
|
"path": "docs/copy-inventory.md",
|
|
1604
1609
|
"mode": "0000644",
|
|
1605
|
-
"sha256": "
|
|
1610
|
+
"sha256": "9fd0b4e8b21ebafbd172d990c35d0038b4a3d11cf3c582260395e943dace4b02"
|
|
1606
1611
|
},
|
|
1607
1612
|
{
|
|
1608
1613
|
"path": "docs/copy-registry.json",
|
|
1609
1614
|
"mode": "0000644",
|
|
1610
|
-
"sha256": "
|
|
1615
|
+
"sha256": "3f038ff873b92b85167ef9e2a91e6a82ddf51fd3dc6131d2a3eb847b38876a6e"
|
|
1611
1616
|
},
|
|
1612
1617
|
{
|
|
1613
1618
|
"path": "docs/design/apex-pass-2-direction.md",
|
|
@@ -1699,6 +1704,11 @@
|
|
|
1699
1704
|
"mode": "0000644",
|
|
1700
1705
|
"sha256": "5de484be1af673bdb91eaf9b9221f8dbca9a47f0c8dad098a0898cd10721b497"
|
|
1701
1706
|
},
|
|
1707
|
+
{
|
|
1708
|
+
"path": "docs/design/mocks/hub/picker.html",
|
|
1709
|
+
"mode": "0000644",
|
|
1710
|
+
"sha256": "357fedc874862a929590b7f7fb3177770ce0ad77f3683813b5e816181b67e88b"
|
|
1711
|
+
},
|
|
1702
1712
|
{
|
|
1703
1713
|
"path": "docs/design/mocks/hub/planets.html",
|
|
1704
1714
|
"mode": "0000644",
|
|
@@ -1902,7 +1912,7 @@
|
|
|
1902
1912
|
{
|
|
1903
1913
|
"path": "docs/design/projects-hub-direction-v2.md",
|
|
1904
1914
|
"mode": "0000644",
|
|
1905
|
-
"sha256": "
|
|
1915
|
+
"sha256": "b72f9c3989e6c66e275124b43ad0d36aa718bd2d670696d0053cb2e0653c0632"
|
|
1906
1916
|
},
|
|
1907
1917
|
{
|
|
1908
1918
|
"path": "docs/design/projects-hub-direction.md",
|
|
@@ -1952,7 +1962,7 @@
|
|
|
1952
1962
|
{
|
|
1953
1963
|
"path": "docs/file-map.md",
|
|
1954
1964
|
"mode": "0000644",
|
|
1955
|
-
"sha256": "
|
|
1965
|
+
"sha256": "23121260ec5061645027d50609712217a619ea9ae8ad539cdeeb18e1cd552755"
|
|
1956
1966
|
},
|
|
1957
1967
|
{
|
|
1958
1968
|
"path": "docs/handoff-template.md",
|
|
@@ -1962,7 +1972,7 @@
|
|
|
1962
1972
|
{
|
|
1963
1973
|
"path": "docs/module-api-changelog.md",
|
|
1964
1974
|
"mode": "0000644",
|
|
1965
|
-
"sha256": "
|
|
1975
|
+
"sha256": "b01ae85c05dc319d5baa59b256712d56bcf342c31e03c9823c12879efe3202d0"
|
|
1966
1976
|
},
|
|
1967
1977
|
{
|
|
1968
1978
|
"path": "docs/modules-contract.md",
|
|
@@ -4124,10 +4134,15 @@
|
|
|
4124
4134
|
"mode": "0000644",
|
|
4125
4135
|
"sha256": "40f6f44283aa7dba25a2c983d77b6e6f53f40eefef3394122d34f7f42fc5f676"
|
|
4126
4136
|
},
|
|
4137
|
+
{
|
|
4138
|
+
"path": "modules/hall-ui/public/profile-canonical.js",
|
|
4139
|
+
"mode": "0000644",
|
|
4140
|
+
"sha256": "4f010fd2a4e77447839eb77f5e294ea62c525fc47f392e916790f37078a92726"
|
|
4141
|
+
},
|
|
4127
4142
|
{
|
|
4128
4143
|
"path": "modules/hall-ui/public/profile-connections.js",
|
|
4129
4144
|
"mode": "0000644",
|
|
4130
|
-
"sha256": "
|
|
4145
|
+
"sha256": "1f52e92aea4583273d719538c453d4d11313f15d583536316c75ef4d625a8265"
|
|
4131
4146
|
},
|
|
4132
4147
|
{
|
|
4133
4148
|
"path": "modules/hall-ui/public/profile-nudge.js",
|
|
@@ -4137,12 +4152,12 @@
|
|
|
4137
4152
|
{
|
|
4138
4153
|
"path": "modules/hall-ui/public/profile.html",
|
|
4139
4154
|
"mode": "0000644",
|
|
4140
|
-
"sha256": "
|
|
4155
|
+
"sha256": "236e3da37f6e8b57525a7d0bc9cc07cea25db4a983770ed965409df8b9d5f655"
|
|
4141
4156
|
},
|
|
4142
4157
|
{
|
|
4143
4158
|
"path": "modules/hall-ui/public/profile.js",
|
|
4144
4159
|
"mode": "0000644",
|
|
4145
|
-
"sha256": "
|
|
4160
|
+
"sha256": "6433253093196f674142979c881ea8dc735d17841aeb99ddbc0e69b621b4b7e0"
|
|
4146
4161
|
},
|
|
4147
4162
|
{
|
|
4148
4163
|
"path": "modules/hall-ui/public/ranks.html",
|
|
@@ -4824,6 +4839,11 @@
|
|
|
4824
4839
|
"mode": "0000644",
|
|
4825
4840
|
"sha256": "3793f588d62eaa96a74da6bceb55043e8a9de35bc120dbe63f26bcc2b7e904da"
|
|
4826
4841
|
},
|
|
4842
|
+
{
|
|
4843
|
+
"path": "modules/platform-identity/canonical-profile.js",
|
|
4844
|
+
"mode": "0000644",
|
|
4845
|
+
"sha256": "2aa433ba293545a313972f88b1143d74551cb6ec4e06747906ff7e3c365f4e6a"
|
|
4846
|
+
},
|
|
4827
4847
|
{
|
|
4828
4848
|
"path": "modules/platform-identity/connections.js",
|
|
4829
4849
|
"mode": "0000644",
|
|
@@ -4972,7 +4992,7 @@
|
|
|
4972
4992
|
{
|
|
4973
4993
|
"path": "modules/platform-identity/routes/projects.js",
|
|
4974
4994
|
"mode": "0000644",
|
|
4975
|
-
"sha256": "
|
|
4995
|
+
"sha256": "7620a816ab376f77be6e46c8fe99c355ba323727fe7a6b2bfbf55b8b953626f3"
|
|
4976
4996
|
},
|
|
4977
4997
|
{
|
|
4978
4998
|
"path": "modules/platform-identity/routes/public-profile.js",
|
|
@@ -4987,7 +5007,7 @@
|
|
|
4987
5007
|
{
|
|
4988
5008
|
"path": "modules/platform-identity/routes/sso.js",
|
|
4989
5009
|
"mode": "0000644",
|
|
4990
|
-
"sha256": "
|
|
5010
|
+
"sha256": "dc3eaa8789a28a389ddeaac88f5cd514db8bf10705f430839f0bf164fc963674"
|
|
4991
5011
|
},
|
|
4992
5012
|
{
|
|
4993
5013
|
"path": "modules/platform-identity/scouting-authz.js",
|
|
@@ -5082,7 +5102,7 @@
|
|
|
5082
5102
|
{
|
|
5083
5103
|
"path": "modules/provisioning/module.json",
|
|
5084
5104
|
"mode": "0000644",
|
|
5085
|
-
"sha256": "
|
|
5105
|
+
"sha256": "26a6d752e610ac46f8306aa707f0229bf83ec9b0cb6c128ec723b7cc55b2b977"
|
|
5086
5106
|
},
|
|
5087
5107
|
{
|
|
5088
5108
|
"path": "modules/provisioning/onboard-plan.js",
|
|
@@ -5097,7 +5117,7 @@
|
|
|
5097
5117
|
{
|
|
5098
5118
|
"path": "modules/provisioning/provisioning.js",
|
|
5099
5119
|
"mode": "0000644",
|
|
5100
|
-
"sha256": "
|
|
5120
|
+
"sha256": "114210686cfe2f287d4d29ee603a443b2794e2065709ff60d2cd2c7530c60cad"
|
|
5101
5121
|
},
|
|
5102
5122
|
{
|
|
5103
5123
|
"path": "modules/provisioning/rate-limit.js",
|
|
@@ -5107,7 +5127,7 @@
|
|
|
5107
5127
|
{
|
|
5108
5128
|
"path": "modules/provisioning/routes/provisioning.js",
|
|
5109
5129
|
"mode": "0000644",
|
|
5110
|
-
"sha256": "
|
|
5130
|
+
"sha256": "f3ee76de08591eb957c1e533f8b96d4cb9c716eb5f89ceda1875b97d84bd2246"
|
|
5111
5131
|
},
|
|
5112
5132
|
{
|
|
5113
5133
|
"path": "modules/provisioning/tests/liveness.mjs",
|
|
@@ -5117,7 +5137,7 @@
|
|
|
5117
5137
|
{
|
|
5118
5138
|
"path": "modules/provisioning/tests/provisioning.mjs",
|
|
5119
5139
|
"mode": "0000644",
|
|
5120
|
-
"sha256": "
|
|
5140
|
+
"sha256": "559ae0d8025693a997a16012758236dbe895f28621de1a1a591d2a43fb21ff00"
|
|
5121
5141
|
},
|
|
5122
5142
|
{
|
|
5123
5143
|
"path": "modules/provisioning/ui/.gitkeep",
|
|
@@ -5447,12 +5467,12 @@
|
|
|
5447
5467
|
{
|
|
5448
5468
|
"path": "modules/ui-design/kit/fixtures/featured-hub.json",
|
|
5449
5469
|
"mode": "0000644",
|
|
5450
|
-
"sha256": "
|
|
5470
|
+
"sha256": "1c3ca6573b3e7b08269ca885bfcb79cb27bb623501066cace9e2575fefff9e1b"
|
|
5451
5471
|
},
|
|
5452
5472
|
{
|
|
5453
5473
|
"path": "modules/ui-design/kit/fixtures/featured.json",
|
|
5454
5474
|
"mode": "0000644",
|
|
5455
|
-
"sha256": "
|
|
5475
|
+
"sha256": "569038c7cbe3a1ff12d361111d062715f38aebd73317816a87c400fa96c12b04"
|
|
5456
5476
|
},
|
|
5457
5477
|
{
|
|
5458
5478
|
"path": "modules/ui-design/kit/fixtures/leaderboard.json",
|
|
@@ -5482,7 +5502,7 @@
|
|
|
5482
5502
|
{
|
|
5483
5503
|
"path": "modules/ui-design/kit/fixtures/provisioning-instances.json",
|
|
5484
5504
|
"mode": "0000644",
|
|
5485
|
-
"sha256": "
|
|
5505
|
+
"sha256": "35db35629d80420c5d816dff040934d69f5713dcf5ffbae2211d12172904dd8e"
|
|
5486
5506
|
},
|
|
5487
5507
|
{
|
|
5488
5508
|
"path": "modules/ui-design/kit/fixtures/recent-shipped.json",
|
|
@@ -5512,7 +5532,7 @@
|
|
|
5512
5532
|
{
|
|
5513
5533
|
"path": "modules/ui-design/kit/serve.js",
|
|
5514
5534
|
"mode": "0000644",
|
|
5515
|
-
"sha256": "
|
|
5535
|
+
"sha256": "f42b175e75905d1fdd854bd13a76899e7ee83e9d0b8683325fff138eee78b07c"
|
|
5516
5536
|
},
|
|
5517
5537
|
{
|
|
5518
5538
|
"path": "modules/ui-design/module.json",
|
|
@@ -5557,12 +5577,12 @@
|
|
|
5557
5577
|
{
|
|
5558
5578
|
"path": "package-lock.json",
|
|
5559
5579
|
"mode": "0000644",
|
|
5560
|
-
"sha256": "
|
|
5580
|
+
"sha256": "39bed51a410e133578366d4dd1c3b563b33053f266d0f77df36588a14094184d"
|
|
5561
5581
|
},
|
|
5562
5582
|
{
|
|
5563
5583
|
"path": "package.json",
|
|
5564
5584
|
"mode": "0000644",
|
|
5565
|
-
"sha256": "
|
|
5585
|
+
"sha256": "20414384b301f90cb64dd0349b014cdcc377f873dec08b80432f9d08f1ad004f"
|
|
5566
5586
|
},
|
|
5567
5587
|
{
|
|
5568
5588
|
"path": "public-docs/index.html",
|
|
@@ -5802,7 +5822,7 @@
|
|
|
5802
5822
|
{
|
|
5803
5823
|
"path": "public-landing/assets/cosmos.css",
|
|
5804
5824
|
"mode": "0000644",
|
|
5805
|
-
"sha256": "
|
|
5825
|
+
"sha256": "761b196a29b7d25d0ff1b2480e9462fd01ea2a4c57d889cd1689c6f4fbe9ca4d"
|
|
5806
5826
|
},
|
|
5807
5827
|
{
|
|
5808
5828
|
"path": "public-landing/assets/verro-medallion.png",
|
|
@@ -6022,17 +6042,17 @@
|
|
|
6022
6042
|
{
|
|
6023
6043
|
"path": "public-landing/projects.html",
|
|
6024
6044
|
"mode": "0000644",
|
|
6025
|
-
"sha256": "
|
|
6045
|
+
"sha256": "5cdf9caf7e376f89d0092f289cf8430211e3f1ac3bbbfdb1f0e516babc970851"
|
|
6026
6046
|
},
|
|
6027
6047
|
{
|
|
6028
6048
|
"path": "public-landing/projects.probes.json",
|
|
6029
6049
|
"mode": "0000644",
|
|
6030
|
-
"sha256": "
|
|
6050
|
+
"sha256": "3bc7cea614031e2c04246274adc8375b5f54d0a043e1bf4da0a564a10964db82"
|
|
6031
6051
|
},
|
|
6032
6052
|
{
|
|
6033
6053
|
"path": "public-landing/projects.states.json",
|
|
6034
6054
|
"mode": "0000644",
|
|
6035
|
-
"sha256": "
|
|
6055
|
+
"sha256": "3217f46d975bd4bed7c17ee6d87b5150e81130f75a780371cb4cffb558a07373"
|
|
6036
6056
|
},
|
|
6037
6057
|
{
|
|
6038
6058
|
"path": "public-landing/terms.html",
|
|
@@ -7587,7 +7607,7 @@
|
|
|
7587
7607
|
{
|
|
7588
7608
|
"path": "src/bongos/routes/builders.js",
|
|
7589
7609
|
"mode": "0000644",
|
|
7590
|
-
"sha256": "
|
|
7610
|
+
"sha256": "dc71324e4a1357f86b7e5a66c64ca5b38a2c79ef58c6d7a6f7337a23cfa1ee60"
|
|
7591
7611
|
},
|
|
7592
7612
|
{
|
|
7593
7613
|
"path": "src/bongos/routes/github.js",
|
|
@@ -7657,7 +7677,7 @@
|
|
|
7657
7677
|
{
|
|
7658
7678
|
"path": "src/bongos/serve-internal.js",
|
|
7659
7679
|
"mode": "0000644",
|
|
7660
|
-
"sha256": "
|
|
7680
|
+
"sha256": "a5036b24dbcd9d3f78de44781e60accb07ef62e458004d7e195af1c60f7e89f3"
|
|
7661
7681
|
},
|
|
7662
7682
|
{
|
|
7663
7683
|
"path": "src/bongos/stealth-gate.js",
|
|
@@ -7692,7 +7712,7 @@
|
|
|
7692
7712
|
{
|
|
7693
7713
|
"path": "src/module-api.js",
|
|
7694
7714
|
"mode": "0000644",
|
|
7695
|
-
"sha256": "
|
|
7715
|
+
"sha256": "f9dbf12c1dc4cbb8e2e01b2bc43110853a9667b08bbee731f09bc998cf9d0a35"
|
|
7696
7716
|
},
|
|
7697
7717
|
{
|
|
7698
7718
|
"path": "src/module-loader/catalog.js",
|
|
@@ -8134,6 +8154,11 @@
|
|
|
8134
8154
|
"mode": "0000644",
|
|
8135
8155
|
"sha256": "0d7bfd5a02e0beefe5f4a4b60daa4d3dee57f8e893febfd366732b419a75678f"
|
|
8136
8156
|
},
|
|
8157
|
+
{
|
|
8158
|
+
"path": "tests/canonical_profile_url.mjs",
|
|
8159
|
+
"mode": "0000644",
|
|
8160
|
+
"sha256": "6f9d5c57d61aa3e1d9c6e8d7be7568d8a2a0d1d6832b8880ce3204a8b67f3a6c"
|
|
8161
|
+
},
|
|
8137
8162
|
{
|
|
8138
8163
|
"path": "tests/cap_newcomer_tasks.mjs",
|
|
8139
8164
|
"mode": "0000644",
|
|
@@ -9717,7 +9742,7 @@
|
|
|
9717
9742
|
{
|
|
9718
9743
|
"path": "tests/profile_connections_ui.mjs",
|
|
9719
9744
|
"mode": "0000644",
|
|
9720
|
-
"sha256": "
|
|
9745
|
+
"sha256": "1e6923437bcafbbb396d43202a4fe5c9d49dd9baef059d84aa2520bd64a195b6"
|
|
9721
9746
|
},
|
|
9722
9747
|
{
|
|
9723
9748
|
"path": "tests/profile_fields.mjs",
|
|
@@ -9742,7 +9767,7 @@
|
|
|
9742
9767
|
{
|
|
9743
9768
|
"path": "tests/profile_route.mjs",
|
|
9744
9769
|
"mode": "0000644",
|
|
9745
|
-
"sha256": "
|
|
9770
|
+
"sha256": "0189425a0430dbab84044bdca2ac99e4dde87db061b8226412aa2fc393bb3119"
|
|
9746
9771
|
},
|
|
9747
9772
|
{
|
|
9748
9773
|
"path": "tests/profile_ui_cross_project.mjs",
|
|
@@ -9762,7 +9787,7 @@
|
|
|
9762
9787
|
{
|
|
9763
9788
|
"path": "tests/projects_hub.mjs",
|
|
9764
9789
|
"mode": "0000644",
|
|
9765
|
-
"sha256": "
|
|
9790
|
+
"sha256": "293c92d90457da1a3bd164f96bc897409275a7772c92c76b0b5b9bb6db8337e1"
|
|
9766
9791
|
},
|
|
9767
9792
|
{
|
|
9768
9793
|
"path": "tests/projects_hub_pre_uat.mjs",
|
|
@@ -9822,7 +9847,7 @@
|
|
|
9822
9847
|
{
|
|
9823
9848
|
"path": "tests/provisioning_settings.mjs",
|
|
9824
9849
|
"mode": "0000644",
|
|
9825
|
-
"sha256": "
|
|
9850
|
+
"sha256": "ffea349b46de6b790884eddcefbf7f756215c281f7d726ad43a81e22edcd4679"
|
|
9826
9851
|
},
|
|
9827
9852
|
{
|
|
9828
9853
|
"path": "tests/provisioning_settings_apply.mjs",
|
|
@@ -9832,7 +9857,7 @@
|
|
|
9832
9857
|
{
|
|
9833
9858
|
"path": "tests/provisioning_settings_env.mjs",
|
|
9834
9859
|
"mode": "0000644",
|
|
9835
|
-
"sha256": "
|
|
9860
|
+
"sha256": "4c8f1c7bdec5a063bcd3e76f4d43245c9491e3b8529593cdfaf33a3fbc85e319"
|
|
9836
9861
|
},
|
|
9837
9862
|
{
|
|
9838
9863
|
"path": "tests/provisioning_slug_available.mjs",
|
|
@@ -34,7 +34,7 @@ export interface GetBuildersDirectoryResponse { builders: unknown }
|
|
|
34
34
|
export interface GetBuildersIdActiveClaimsResponse { builder_id: unknown; claims: unknown; stale_hours: unknown }
|
|
35
35
|
export interface GetBuildersIdActivityResponse { builder_id: unknown; section: unknown; role: unknown; rows: unknown; total: unknown }
|
|
36
36
|
export interface GetBuildersIdOnboardingResponse { builder_id: unknown; onboarding: unknown }
|
|
37
|
-
export interface GetBuildersIdProfileResponse { builder: unknown; shipped_count: unknown; recent_ships: unknown; achievements: unknown; cross_project?: unknown }
|
|
37
|
+
export interface GetBuildersIdProfileResponse { builder: unknown; shipped_count: unknown; recent_ships: unknown; achievements: unknown; cross_project?: unknown; handle?: unknown }
|
|
38
38
|
export interface GetBuildersMePendingRankChangeResponse { change: unknown; greeting?: unknown }
|
|
39
39
|
export interface GetBuildersRosterResponse { builders: unknown; stale_hours: unknown }
|
|
40
40
|
export interface GetClosenessForMeResponse { closeness: unknown; count: unknown }
|
|
@@ -93,7 +93,7 @@ export interface GetProvisioningFleetResponse { instances: unknown }
|
|
|
93
93
|
export interface GetProvisioningGithubAppStatusResponse { ok: boolean; status: unknown; ready: unknown; terminal: unknown; domain: unknown; instance_status: unknown; app_slug: unknown }
|
|
94
94
|
export interface GetProvisioningHealthResponse { module: unknown; ok: boolean; coreVersion: unknown }
|
|
95
95
|
export interface GetProvisioningInstancesIdResponse { instance: unknown; open_intent: unknown; github_app: unknown }
|
|
96
|
-
export interface GetProvisioningInstancesIdSettingsResponse { ok: boolean; settings: unknown; options: unknown }
|
|
96
|
+
export interface GetProvisioningInstancesIdSettingsResponse { ok: boolean; settings: unknown; planet: unknown; options: unknown }
|
|
97
97
|
export interface GetProvisioningInstancesResponse { instances: unknown }
|
|
98
98
|
export interface GetProvisioningOnboardPlanResponse { mode: unknown; steps: unknown }
|
|
99
99
|
export interface GetProvisioningSlugAvailableResponse { slug: unknown; available: unknown; reason: unknown; address: unknown }
|
|
@@ -168,8 +168,8 @@ export interface PatchProjectsIdFeaturedRequest { featured: boolean }
|
|
|
168
168
|
export interface PatchProjectsIdFeaturedResponse { project: unknown }
|
|
169
169
|
export interface PatchProvisioningInstancesIdDetailRequest { description?: string; detail?: Record<string, unknown> }
|
|
170
170
|
export interface PatchProvisioningInstancesIdDetailResponse { ok: boolean; description: unknown; detail: unknown; options: unknown }
|
|
171
|
-
export interface PatchProvisioningInstancesIdSettingsRequest { platform_visibility?: string; joinability?: string }
|
|
172
|
-
export interface PatchProvisioningInstancesIdSettingsResponse { ok: boolean; saved: unknown; push: unknown }
|
|
171
|
+
export interface PatchProvisioningInstancesIdSettingsRequest { platform_visibility?: string; joinability?: string; planet_template?: string; planet_accent?: string }
|
|
172
|
+
export interface PatchProvisioningInstancesIdSettingsResponse { ok: boolean; saved: unknown; planet: unknown; push: unknown }
|
|
173
173
|
export interface PatchTaskRecommendationsIdRequest { status: "declined" | "withdrawn" }
|
|
174
174
|
export interface PatchTaskRecommendationsIdResponse { ok: boolean; recommendation: unknown }
|
|
175
175
|
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; title?: string; status?: unknown }
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# ADR 0214 — The canonical profile URL gets one resolver, and it stays dark until the page it names actually resolves
|
|
2
|
+
|
|
3
|
+
- **Status:** accepted
|
|
4
|
+
- **Date:** 2026-08-29
|
|
5
|
+
- **Task:** [task 1002804](https://cloudbongos.com/builders#/task/1002804) (P08, goal 1000045 — Public builder profiles, criterion C1)
|
|
6
|
+
- **Relates to:** ADR 0171 D1/D2/D8 (link out instead of inlining; `hide_stats` suppresses numbers, never existence; `github_id` is the join key and the handle is presentation), ADR 0202 D4 / ADR 0210 (one shared visibility predicate, never a second variant), ADR 0141 (identity federated, authority local), ADR 0083 (module boundary), ADR 0016 (identity is never authority), `docs/specs/<redacted>.md` Decision 1 + Decision 3, `docs/specs/<redacted>.md` D2 (404, never 403)
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
Profile-page spec Decision 1 names one canonical public profile with two render paths: `cloudbongos.com/u/<handle>` is canonical and server-rendered on the hub; `builders.<instance>/profile` stays the LOCAL view and emits `rel=canonical` to it; and the numeric `?id=` form "never dies" as a rename-proof fallback that redirects to the canonical handle URL. ADR 0171 D1 amended that: federated non-hub instances **link out** rather than inlining cross-project numbers, and named this task the owner of the federated render path — the visible link, the `rel=canonical` emission, and the numeric-id redirect.
|
|
11
|
+
|
|
12
|
+
Four facts about the tree, established before any of this was designed:
|
|
13
|
+
|
|
14
|
+
1. **`/u/<handle>` does not exist.** No route, no page, no hub search, no sitemap (proved by task 1002806, confirmed by task 1002296). The page is goal 1000048 R14 / [task 1002365](https://cloudbongos.com/builders#/task/1002365), which sits at `backlog`. So does R13 (cross-instance canonical), R12 (robots.txt) and R15 (sitemap). R01 — the ADR that *decides the SEO namespace* — is only `ready`, and R03 in the same goal proposes moving platform pages under `/bongos/…`. **The path string `/u/<handle>` is not merely unbuilt; its shape is not yet settled.**
|
|
15
|
+
2. **The READ behind that page already exists.** `GET /profiles/:handle` (`modules/platform-identity/routes/public-profile.js`, R05) resolves current-or-previous handle through `resolveHandle`, which floors on the shared `accountVisibleSql` predicate and answers 404 for unknown / provisional / private alike — never 403.
|
|
16
|
+
3. **`GET /builders/:id/profile` whitelists nine core columns and `handle` is not one of them.** Task 1002296 hit this and routed around it: `profile-connections.js` hands the connections API a `github_login` and uses the handle the response echoes back. That workaround has a hole its own header names — the rename window is a **single** `previous_handle` slot, so a builder who has renamed **twice** has a login that matches neither `handle` nor `previous_handle`, the degree call 404s, and the connections section silently never appears.
|
|
17
|
+
4. **No served page carries a hub origin.** `clientBranding()` deliberately omits `domains`; `applyBrandTokens()` stamps `{{publicOrigin}}` and `{{buildersOrigin}}` (this instance's own addresses) and nothing else. The only hub origin anywhere at runtime is `loadIdpConfig().origin` in `src/bongos/auth-config.js` — server-side auth wiring, never rendered. ADR 0171 D1 flagged exactly this gap.
|
|
18
|
+
|
|
19
|
+
## Decision
|
|
20
|
+
|
|
21
|
+
### D1 — One resolver owns the canonical URL, and it answers `null` while the page does not resolve
|
|
22
|
+
|
|
23
|
+
`modules/platform-identity/canonical-profile.js` is THE place that answers "what is this builder's canonical profile URL?". Everything that needs the answer — the `rel=canonical` link, the visible link-out, the numeric-id redirect — asks it and renders nothing when it answers `null`. One resolver, three consumers, so the three can never disagree about where a profile lives or about whether it is safe to point there.
|
|
24
|
+
|
|
25
|
+
`CANONICAL_PROFILE_PAGE_LIVE` is `false`. **Today the resolver answers `null` everywhere and all three surfaces are dark.**
|
|
26
|
+
|
|
27
|
+
**Why dark, and not "canonical ahead of the page".** Emitting a canonical at a URL that is planned but unbuilt is a normal SEO staging move when the target is imminent and its address is fixed. Neither holds here:
|
|
28
|
+
|
|
29
|
+
- **The target 404s.** A `rel=canonical` pointing at a 404 does not consolidate anything — a 404 cannot be indexed, so there is no signal to consolidate onto. What it *can* do is cost: the declaring page is the only one that works, and a canonical that names a non-indexable URL is a documented way to have a working page reported as a duplicate of nothing and dropped. We would be spending the one page that ranks to point at a page that does not exist.
|
|
30
|
+
- **The address is not settled.** R01 has not been written and R03 proposes relocating platform pages under `/bongos/…`. Baking `/u/<handle>` into every federated instance's served HTML now means every one of them advertises a path that a sibling task may still move — and a canonical is precisely the kind of claim that is expensive to retract, because crawlers cache it and users bookmark what it redirects them to.
|
|
31
|
+
- **There is no upside to being early.** Canonical consolidation begins when the target becomes indexable, not before. Emitting now buys nothing and risks the working page.
|
|
32
|
+
|
|
33
|
+
So the honest answer is: build the machinery, prove it, and **do not point at a 404**. The flip is one boolean in one file, and R14 cannot land the page without noticing it — `tests/canonical_profile_url.mjs` fails if a `/u/` page route appears anywhere in the tree while the flag is still `false`. The flag cannot silently lie in either direction.
|
|
34
|
+
|
|
35
|
+
**What must be true to flip it** (the checklist R13/R14 inherit, stated once so nobody has to re-derive it): the page is served at the path this file names; R01 has ratified that path; the page 404s (never 403) for absent / provisional / private, matching `GET /profiles/:handle`; and `noindex`-until-first-ship (P09) is in place so flipping the flag does not publish a farm of empty profiles.
|
|
36
|
+
|
|
37
|
+
### D2 — The hub origin becomes a server-side stamp, delivered as a `<meta>`, not a `<script>`
|
|
38
|
+
|
|
39
|
+
`applyBrandTokens()` gains `{{hubOrigin}}`. It resolves to the instance's **own** `publicOrigin` when `platform-identity` is enabled (that module *is* the hub flag — `src/platform-server.js`), to `loadIdpConfig().origin` on a federated instance, and to `''` on a vanilla one. This is the "server-side hub-origin stamp token" ADR 0171 D1 asked for, and it is the only way a served page can carry a correct cross-host canonical: `clientBranding()` cannot reach `domains`, and a baked value would make every instance advertise whoever authored the file (the fitness identity gate, task 1002748).
|
|
40
|
+
|
|
41
|
+
It is stamped as `<meta name="hub-origin" content="{{hubOrigin}}">`, following the `{{instanceBase}}` precedent — a meta attribute, never a script global. **The token is deliberately not written directly into a `href`**: `dropEmptyHrefLinks` only drops an href that is a *bare* origin token, so `href="{{hubOrigin}}/u/x"` on an instance with no hub would silently become the same-host relative URL `/u/x` — a canonical pointing at the instance itself. Reading the meta and refusing an empty value makes that failure impossible rather than merely unlikely.
|
|
42
|
+
|
|
43
|
+
### D3 — `handle` joins the numeric profile response under the EXISTENCE predicate, not the rollup one
|
|
44
|
+
|
|
45
|
+
`GET /builders/:id/profile` gains a top-level `handle`, attached only where the `platform-identity.hub` port resolves, sourced from a new port function `getPublicHandle(githubId)` that floors on `isAccountVisible` — the shared predicate, never a variant.
|
|
46
|
+
|
|
47
|
+
**Why the existence predicate and not the rollup one.** `account-visibility.js` is explicit that `hide_stats` suppresses NUMBERS and never existence, and `resolveHandle` already says the same out loud: hiding removes the rollup, not the page. A builder who hides their stats still has a public profile at `/u/<handle>` — it just carries no numbers. Gating the handle on `rollupVisibleSql` would deny a canonical URL and a connect button to exactly the people who chose a public profile without public numbers.
|
|
48
|
+
|
|
49
|
+
**This does not create a new inference.** It is true that `handle` present together with `cross_project: null` narrows the four indistinguishable rollup-null cases to "hidden". That pair is **already published, unauthenticated, on `/profiles/:handle`**, which answers `200 { account: { handle }, cross_project: null }` for exactly that account. The change re-presents on the numeric route a pair the handle route already serves; it does not widen what the platform discloses. The three cases ADR 0171 D2 requires to stay merged — no account, provisional, private — all still produce `handle` absent **and** `cross_project` null, byte-identical.
|
|
50
|
+
|
|
51
|
+
**What this closes today, with the flag still off:** fact 3 above. `profile-connections.js` can take the canonical handle straight from the profile response instead of guessing with a `github_login`, so a twice-renamed builder gets their connections section back.
|
|
52
|
+
|
|
53
|
+
### D4 — The numeric `?id=` form redirects on the hub and canonicalizes on a federated instance
|
|
54
|
+
|
|
55
|
+
The spec's two clauses — "the local view stays as-is" and "the numeric form redirects to the canonical URL" — read as a contradiction only until you ask *which instance*:
|
|
56
|
+
|
|
57
|
+
- **On the hub**, `builders.<apex>/profile?id=N` and `<apex>/u/<handle>` describe the same person from the same database. Two URLs for one page on one host is precisely the duplication Decision 1 exists to end, so the numeric form **redirects**.
|
|
58
|
+
- **On a federated instance**, `builders.foo.com/profile?id=N` shows *foo's* local rank, works and achievements. That is genuinely different content, so it **must not** redirect. It stays the local view, emits `rel=canonical`, and shows the visible link-out — ADR 0171 D1's federated render path exactly.
|
|
59
|
+
- **On a vanilla instance** nothing changes at all.
|
|
60
|
+
|
|
61
|
+
A page cannot both redirect to a URL and declare it canonical, and it does not have to: the two behaviours belong to different instances.
|
|
62
|
+
|
|
63
|
+
The hub redirect is a client-side `location.replace` (it replaces rather than pushes, so Back still leaves the site cleanly) issued after the profile read resolves the handle. It is not a true 301 and does not claim to be: the hall page is served from a boot-time in-memory HTML map with no per-request database access, and giving that path a DB read to serve a redirect is a real cost for a page that is not indexed today (there is no robots.txt yet — R12). **When R14 lands the server-rendered page it should serve the numeric form as a real 301 in its own namespace, using this same resolver**; that is written here so the follow-up is a decision already made rather than one rediscovered.
|
|
64
|
+
|
|
65
|
+
### D5 — The redirect is not an existence oracle
|
|
66
|
+
|
|
67
|
+
The hub redirect fires iff the profile response carries a `handle`, which it does iff `isAccountVisible` — the same predicate `/profiles/:handle` already floors on. Three properties make the fallback silent:
|
|
68
|
+
|
|
69
|
+
1. **The non-redirect branch is a working page, not an error.** No account, provisional, and private all render the ordinary local profile — the same markup, the same status, the same everything. There is no error shape to read a signal off.
|
|
70
|
+
2. **The three "no" cases are indistinguishable from each other.** `getPublicHandle` returns `null` for all three; the response omits the key identically. The redirect can only ever separate "publicly visible account" from "not that", never one *reason* from another.
|
|
71
|
+
3. **The one bit it does expose is already public.** "This builder has a publicly visible platform account" is exactly what an unauthenticated `GET /profiles/<handle>` answers with 200-vs-404 today. The redirect derives its target from a handle the response already carried; it adds no bit the caller could not already read.
|
|
72
|
+
|
|
73
|
+
And the redirect target is a handle, never a `github_id` — the join key stays internal, as `<redacted>` already insists.
|
|
74
|
+
|
|
75
|
+
### D6 — Publishing the handle map puts the numeric profile read under the EXISTING public read ceiling
|
|
76
|
+
|
|
77
|
+
`GET /builders/:id/profile` answered at line rate. That is structural rather than an oversight: `rateLimitMiddleware` returns early for every non-write method, and the one read limiter is mounted under `/public/*`, which this route is not (ADR 0209's finding, one route over). Tolerable while the route re-presented leaderboard data everyone can already read; not once it also carries `handle`, because an id walk then yields the **builder → canonical-profile-handle map**, one call per builder, for every publicly visible account. `modules/platform-identity/routes/public-profile.js` caps `/profiles/:handle` for exactly that mapping, with exactly that reasoning. Two routes publishing one map get one ceiling.
|
|
78
|
+
|
|
79
|
+
So the route mounts `publicReadRateLimitMiddleware` — **the shared instance** (120/60s per IP, the `/public/*` posture), not a new budget and not a private copy. ADR 0209's load-bearing point applies verbatim: two limiters configured identically are indistinguishable in a diff and hand an enumerator twice the budget, so the test asserts the shared instance **by object identity**.
|
|
80
|
+
|
|
81
|
+
**This required strengthening a guard, not weakening one.** `tests/profile_route.mjs` (R06.1) pinned the route as public by requiring the registration line to read `router.get(path, async (req, res) =>` — the handler and nothing else. Its own comment says what it is for: *"a future PR walling this behind `requireBuilder` would break 'another account can see it'"*. That intent is **auth**, and throttling is not authentication — `/public/*` is rate-limited and public. But a line-regex cannot tell the two apart, and it silently missed the more likely bypass anyway: a gate mounted with `router.use`. The guard now reads the **real router stack**, rejects any function exported by `../auth` on that route, rejects any router-level middleware at all, and allows exactly one named handler — the shared limiter — with its reason. Six mutations were run against it; the strengthened form catches all of them, including the two `router.use` forms the old regex let through.
|
|
82
|
+
|
|
83
|
+
## Consequences
|
|
84
|
+
|
|
85
|
+
- **Three surfaces ship dark and one ships live.** The canonical link, the link-out and the numeric redirect are all gated on one boolean that is `false`; the widened `handle` on the profile read is live immediately and closes the twice-renamed connect-button hole. A reviewer should expect exactly that asymmetry.
|
|
86
|
+
- **R13/R14 inherit a one-line flip and a checklist.** `CANONICAL_PROFILE_PAGE_LIVE` in `modules/platform-identity/canonical-profile.js`, with D1's four preconditions in its doc-comment and a test that fails if a `/u/` page route lands while it is still `false`.
|
|
87
|
+
- **A cross-version window exists and is bounded.** A federated instance running a core release where the flag is `true` while its hub is pinned to an older release would link out to a 404. The blast radius is a link and a canonical, not a broken page, and the flag ships in the same release as the page it names.
|
|
88
|
+
- **The link-out discloses that a builder has a public platform account** — the same bit D5 analyses, now visible on a federated instance's page rather than only via the handle route. Deliberate: that is what "link out to the canonical record" means.
|
|
89
|
+
- **`applyBrandTokens` grows a seventh token.** Its signature is now at seven injectable parameters; a further one should probably move the origins into an object rather than extend the list again.
|
|
90
|
+
- **Not decided here:** the sitemap and `noindex` rules (P09 / R15), the served page itself and its caching posture (R14), whether the numeric permalink on the hub should key on the core builder id or a platform-side identifier once the page exists (R14's call, with D5's rule binding either way), and whether federated instances ever inline cross-project numbers — ADR 0171 D1 names the pull-with-cache follow-up and it stays a separate task.
|
package/docs/adr/README.md
CHANGED
|
@@ -298,6 +298,7 @@ This keeps the decision history honest and traceable.
|
|
|
298
298
|
| 0209 | [**Two routes answering one question spend one budget; whether the answer itself collapses is the owner's call** ([task 1003339](https://cloudbongos.com/builders#/task/1003339), goal 1000045 — criterion `i-control-my-own-access-and-exposure`). `GET /access-requests/status` (public, returns `member|invited|pending|dismissed|none`) and `GET /auth/web/admission-status` (its bare-boolean twin) both answer "does this GitHub login have an account here?", and neither was throttled — `rateLimitMiddleware` returns early for every non-write method, and the one read limiter is mounted only under `/public/*`. Scripted at line rate either becomes a login→membership roster: who builds here, and confirmation that a named person is present. Decision: ship the ceiling as ONE budget — `accountExistenceReadRateLimit`, 120 reads/60s per IP over a single bucket, exported as one middleware INSTANCE rather than a factory and mounted on both routes, because two limiters each configured 120/60s look identical in a diff, pass any test that counts to the limit per route, and hand an enumerator 240/min by alternating. The onboarding module reaches it through the doorway, so `src/bongos/middleware/rate-limit.js` joins `KERNEL_FILES` on `logger.js`'s argument: authz decides WHO may ask, the ceiling decides HOW OFTEN, and the file imports nothing. Deliberately NOT decided here: collapsing the response so a non-subject caller cannot tell `pending`/`dismissed`/`none` from "not admitted" — the consumer sweep found route 1 has exactly ONE consumer (`scripts/gds/login.js`), which already treats `pending` and `none` identically, so collapsing costs exactly one thing: the only message in the codebase that tells a declined applicant they were declined. That is a judgement about what an applicant is owed, so it goes to the owner as a blocker with the evidence. Noted either way: collapsing does not close the oracle — `admitted:true` still means member-or-invited. Rejected: a separate limiter per route; a `createReadRateLimit` factory (two matching call sites get two buckets — the generality loses the property); vendoring a fourth copy of the sliding window; hardening only the smaller route; collapsing on my own judgement because "nothing much" reads it.](<redacted>.md) | security / rate limiting |
|
|
299
299
|
| 0207 | [**Building the connection graph: three departures from ADR 0202, and the three limits it is honest about** ([task 1002295](https://cloudbongos.com/builders#/task/1002295), goal 1000045 criterion C6 — the build of the connections wave). ADR 0202 decided the graph; building it surfaced three places where the decision's MECHANISM could not be written literally without losing the PROPERTY it was about, and three places where the shipped code cannot promise as much as a casual reading suggests. Departures: (1) **level-wise CTEs, not `WITH RECURSIVE`** — a recursive term can carry neither a per-level fan-out cap nor an ordered per-level truncation, which are the two things D3 actually decided, so a recursive build would have had a depth bound and no fan-out bound; a test asserts `RECURSIVE` appears in neither query. (2) **1st degree is an unbounded pair-index probe**, never a scan of the capped frontier — reading it off the `FANOUT_1`-truncated 1-hop reports "2nd" for someone in the viewer's own connections list once they pass 500 connections, and the rule this states is that **a cap may only ever make an answer less specific (`null`), never wrong**. (3) The shared visibility predicate lands in its own file, `modules/platform-identity/account-visibility.js` (`isAccountVisible` / `accountVisibleSql` / `accountActiveSql`), because task 1002967 had not shipped and `platform-identity.js` sat at the size ratchet with zero headroom — **1002967 adopts it rather than writing a second predicate**. Also decided: node visibility during traversal is viewer-relative and asymmetric on purpose (your OWN 1-hop floors on consent alone so a friend who goes private does not vanish from your list; every deeper hop clears the public predicate); cancel is a `DELETE` an ADDRESSEE may not use, so the 90-day cooldown cannot be dodged by calling a decline a delete; and the create response is `201 { status: 'pending' }` and nothing else, so a swallowed re-ask has nothing to fabricate. Limits stated rather than implied: a swallowed re-ask is silent in the RESPONSE and observable by its ABSENCE from the requester's outbox (the claim is narrowed, not the code — a decoy row would be a lie in the database to protect a lie in the API); the TARGET's 1-hop is capped too and that truncation is deliberately NOT reported, because saying so leaks one bit about a third party's degree; and the query COUNT is bounded while the frontier's WORK is O(sum of degree over the 1-hop), since `ORDER BY … LIMIT` materialises before it truncates — the revisit trigger is a measured p95, and the cheap next move is a `statement_timeout` returning the `null` D3 already chose. Deferred and named: a per-builder cap on OUTBOUND requests (the cooldown stops repeat asks to one person, not one builder asking a hundred — a real number is owner-signed), and a real-Postgres pass on the expire-then-insert write path, which CI cannot run.](<redacted>.md) | platform identity / social graph / privacy |
|
|
300
300
|
| 0212 | [**The connections section is built on the profile page that exists, and it addresses a builder by asking the API to name them** ([task 1002296](https://cloudbongos.com/builders#/task/1002296), R20 · goal 1000045 criterion C6 — the UI half of the connections wave). The profile-page spec puts section 6 on the canonical `cloudbongos.com/u/<handle>`, which [task 1002806](https://cloudbongos.com/builders#/task/1002806) proved does not exist (no route, no page, no search entry, no sitemap row) and which belongs to goal 1000048 R14. Decisions: (1) **section 6 lands in the reserved slot R04 left on the hall profile page** (`modules/hall-ui/public/profile.{html,js}`), as a separate `profile-connections.js` so neither the page script nor `platform-identity.js` grows against its line ratchet, and it follows the CROSS-PROJECT section’s contract exactly — the markup ships `hidden` and reveals only where the hub answered, so an instance without `platform-identity` never renders it. It does not pre-empt the canonical page, which inherits the same three own-scope calls. (2) **Addressing: the page hands the API the `github_login` it holds and uses the `handle` the response echoes back for every write** — core `GET /builders/:id/profile` whitelists nine columns and `handle` is not one of them, and widening that whitelist is task 1002804’s canonical-URL work. `findAccountByHandle` already resolves current-or-previous handle and the route answers with the canonical one, which is the same move the API built for a stale Connect link. It fails closed into the deliberately indistinguishable 404 (unknown / provisional / private stranger), so the ONE honest gap is stated rather than discovered: **a twice-renamed handle has no connect button on this surface**, and it disappears when `/u/<handle>` lands. (3) Three rendering rules, enforced by `tests/profile_connections_ui.mjs` (real router over a WHERE-derived fake pool on a real socket, real client above it): `degree: null` renders "Out of network" and never "not connected", with copy that is BYTE-IDENTICAL whether `truncated` was set — varying it would rebuild the ADR 0207 L2 side channel in words; a sent request claims exactly `201 { status: 'pending' }`, so a re-ask swallowed by the 90-day cooldown simply shows "Connect" again next load with no explanation anywhere; and every count is the length of the list beside it, with `seen_at` and `truncated` never reaching the screen. Rejected: waiting for the hub page (it would block C6’s proof on another goal); building `/u/<handle>` here (the spec says only one such page gets built); adding `handle` to the core profile whitelist or to `cross_project` (the latter is `null` under `hide_stats`, which is exactly where the edge must still show — ADR 0202 D4 §3); resolving through public `GET /profiles/:handle` (it 404s for a private account the viewer IS connected to). Deferred and named: a public per-builder connections list (there is deliberately no such route), removal from the other person’s profile, and sections 7–8.](<redacted>.md) | platform identity / social graph / hall UI |
|
|
301
|
+
| 0214 | [**The canonical profile URL gets one resolver, and it stays dark until the page it names actually resolves** ([task 1002804](https://cloudbongos.com/builders#/task/1002804), P08 · goal 1000045 criterion C1 — the canonical/redirect wiring). Profile-page spec Decision 1 wants `rel=canonical` on the instance, a link-out to the hub, and a numeric-id redirect; `/u/<handle>` does not exist (goal 1000048 R14 is `backlog`, and R01 — the ADR that fixes its PATH — is unwritten, while R03 proposes moving platform pages under `/bongos/…`). Decisions: (1) **one resolver** (`modules/platform-identity/canonical-profile.js`) answers where a profile lives, and it answers `null` while the page 404s — a canonical naming a non-indexable URL consolidates nothing and risks the one page that works, and the address is not even settled yet; one boolean flips all three surfaces together, and a test reds if a `/u/` route lands while it is still false. (2) **`{{hubOrigin}}`** joins `applyBrandTokens` (own `publicOrigin` on the hub, `loadIdpConfig().origin` when federated, `''` otherwise) — the server-side stamp ADR 0171 D1 asked for — delivered as a `<meta>`, never inside an `href`, because an empty value in an href degrades to a same-host URL claiming to be the hub. (3) **`handle` joins `GET /builders/:id/profile`** under the EXISTENCE predicate (`isAccountVisible`), not the rollup one: `hide_stats` suppresses numbers and never addressability, and the `handle`+`cross_project: null` pair is already published unauthenticated by `GET /profiles/:handle`. This closes ADR 0212’s stated gap — a twice-renamed builder now has a connect button. (4) **The hub redirects and a federated instance canonicalizes**, which is how Decision 1’s two apparently contradictory clauses reconcile: the hub serves the same person at both URLs, a federated instance serves genuinely different content. (5) **The redirect is not an existence oracle** — its non-redirect branch is the ordinary working page, absent/provisional/private are byte-identical, and the one bit it exposes is what `/profiles/:handle` already answers with 200-vs-404.](<redacted>.md) | platform identity / SEO / hall UI |
|
|
301
302
|
| 0210 | [**The recruiting directory graduates on an instance-asserted ownership claim, and the recruiting opt-out gates the directory and nothing else** ([task 1002290](https://cloudbongos.com/builders#/task/1002290), R14, goal 1000045 criterion C5 — "project owners, not only Archons, browse builder profiles to recruit"). Two independent problems wore one task id. The reach flag `recruiter_discoverable` protected nobody — task 1002967 said so, and the Settings row stayed in the future tense because of it. And "widen the Archon-only /scouting" was already stale (ADR 0157 lowered `project.curate` to metic) while hiding the real question: **a project owner is not a rank**, and someone who registers a project is an Archon on their OWN instance and usually a bare `xenos` on the hub where the directory lives, so no floor reaches them without reaching every stranger. **D1** "project owner" on the hub is `<redacted>.membership_kind = 'owner'` — written by the PROJECT itself at `POST /sso/membership/check-in` from its own first-admin config, and refused unless the hub's own `signin_verified_at` witness stands (ADR 0205) — and never `platform_identity_projects.owner_github_id`, the hub-curated column ADR 0201 D3 names and forbids; the prohibition is about a hub-held list consulted before an ADMISSION on someone else's box, and this is the hub reading the instance's own mirrored claim before a read of the hub's OWN directory. **D2** the two doors are an OR in `modules/platform-identity/scouting-authz.js` (the `goal-authz.js` owner-or-rank shape, kept out of `platform-identity.js` at its size ratchet): ownership is asked first and a "no" falls through to the UNMODIFIED `requirePermission`, so a refusal keeps its exact code, `required`/`held` and floors map — the widening can only admit; the atom is spelled in the ROUTE's middleware list, because a gate hidden in the helper would make `route-rank-check.js` and `gen-api-docs.js` record this privileged route as `any-builder`; and a broken ownership read falls closed onto the rank door rather than admitting or 500ing. No atom is minted — `project.recruit` belongs to the console wave (spike 1002786 R3). **D3** `PAGE_GATES` gains an optional `alsoAdmits` naming a function on the `platform-identity.hub` port, declared on `requireProjectCuratePage` alone and consulted only after the permission check refused, so the `/people` shell admits exactly the two audiences its data route does; absent port, absent function or a throw all deny. **D4** the reach COALESCE gets its SQL twin in `account-visibility.js` (its sole owner) and composes into `scoutingListableSql` = rollup AND reach, which `listScoutingRollup` joins — but reach is deliberately NOT folded into `rollupVisibleSql`, because the community spec D3 says the opt-out governs the recruiting surface only and folding it down would silently empty a future public leaderboard. **D5** no migration: both columns exist and migration 003's `(github_id)` index already serves the probe. **D6** the ownership query JOINs `platform_identity_sso_clients` on `status='active'` — a membership row never expires and nothing ages it out, so suspending the client (the lever that already stops a project signing anyone in) had to revoke the read it derives, or suspending a rogue project would leave its owner reading every builder on the platform forever. The task's own adversarial review also found the sole-owner drift guard was matching only the UNALIASED spelling of the reach COALESCE — i.e. not the one that ships — and two fail-open defaults (a prototype-reachable `alsoAdmits`, and an alias guard resting on template evaluation order); all three fixed and mutation-checked. ⚠ Honest limits recorded, not fixed: the hall NAV still hides People from a project owner (task 1003349, sequenced with R15 which owns that surface), the `touchMembership` write path stays as ADR 0205 left it, and `openapi.json` cannot express "or owner". ⚠ And DELEGATES do not reach the directory yet (blocker 1000122 + task 1003350, linked) — an instance reports `owner` for its first admin only, so the registrant half works and the delegate half needs the instance to report `owner` for any archon, which changes a cross-version wire contract and a shipped bucketing; filed as blocker 1000122, and nothing here changes when it is answered. Rejected: lowering the floor further (a floor low enough to admit a registrant excludes nobody); a `project.owner` atom (ADR 0201 D3 rejected it); minting `project.recruit` here; gating on `owner_github_id`; folding reach into the rollup predicate; widening `membershipKind()` on our own judgement; shipping the route without the page.](<redacted>.md) | platform identity / authz + privacy |
|
|
302
303
|
| 0213 | [**The platform T&C acceptance is the hub's own witness, written once, and consent becomes two columns** ([task 1002913](https://cloudbongos.com/builders#/task/1002913), goal 1000045). ADR 0141 §4 deferred consent for cross-project aggregation to "the platform T&C" and ADR 0171 D3 wrote its own definition around the moment that acceptance "can later be captured" - but no acceptance record existed, and while `/terms` DOES exist (task 1003251) it says nothing about work crossing between projects, so the public-profile premise ("already carries platform T&C consent") was untrue for every account. The scouting.js:6 pointer the task named had also been overwritten by ADR 0210, so the debt was going invisible rather than getting paid. Decision: `terms_accepted_at` + `terms_version` on `platform_identity_accounts` (migration 019), written ONLY by `upsertAccount`'s `activate` branch - one caller, /sso/authorize, where `activate: true` is a source literal and every field comes from the hub's resolved session (the ADR 0205 D3 property; a record a caller could assert is evidence of nothing). COALESCE makes it write-once, so the column keeps meaning WHEN consent was given. The consent axis in `account-visibility.js` becomes `profile_state = 'active' AND terms_accepted_at IS NOT NULL`, composed into every other predicate, which is what makes the tie structural rather than a promise. NO backfill (D4): existing active accounts re-accept on their next hub sign-in - ADR 0171 D3's own rule and P03's ratified rationale - and that visible consequence, plus the wording itself, went to the owner on blocker 1000123. Rejected: an acceptances TABLE (no event stream yet), a click-through gate (an interstitial in a redirect built to be invisible; additive later), backfilling (records a consent that never happened), deriving profile_state from the timestamp, and putting the predicate in `terms-acceptance.js` (a second owner of an effective-visibility answer, which ADR 0202 D4 forbids).](<redacted>.md) | platform identity / consent / privacy |
|
|
303
304
|
|
package/docs/api/openapi.json
CHANGED
|
@@ -2847,7 +2847,7 @@
|
|
|
2847
2847
|
"builders"
|
|
2848
2848
|
],
|
|
2849
2849
|
"summary": "GET /builders/:id/profile",
|
|
2850
|
-
"description": "GET /builders/:id/profile — the viewable builder profile page's read (task 1467, goal 25 \"bx-identity\"). Returns ONE builder's public portfolio: identity + rank + economy counters + shipped-work count + recent ships + unlocked achievements. Backs the shareable /builders/profile page. rank: public — every field returned here is ALREADY world-readable: the leaderboard (GET /public/leaderboard, no auth) exposes rank, total_credits, karma, and the top achievement ids; GET /builders/:id/onboarding is likewise an unauthenticated roster-visible read. This route is a per-builder re- presentation of the SAME already-public data, so a portfolio link is shareable without a sign-in wall (that IS the feature). It deliberately WHITELISTS the public column subset off the full builder row — the private columns getBuilderById also returns (anthropic_email / anthropic_user_id / monthly_budget_usd / system_role / box_blocked*) are NEVER spread into the JSON. A leak-prevention unit test (tests/profile_route.mjs) asserts their absence + the 404-on-unknown-id behaviour.\n\n**Rank:** `public` — No authentication — any caller.",
|
|
2850
|
+
"description": "GET /builders/:id/profile — the viewable builder profile page's read (task 1467, goal 25 \"bx-identity\"). Returns ONE builder's public portfolio: identity + rank + economy counters + shipped-work count + recent ships + unlocked achievements. Backs the shareable /builders/profile page. rank: public — every field returned here is ALREADY world-readable: the leaderboard (GET /public/leaderboard, no auth) exposes rank, total_credits, karma, and the top achievement ids; GET /builders/:id/onboarding is likewise an unauthenticated roster-visible read. This route is a per-builder re- presentation of the SAME already-public data, so a portfolio link is shareable without a sign-in wall (that IS the feature). It deliberately WHITELISTS the public column subset off the full builder row — the private columns getBuilderById also returns (anthropic_email / anthropic_user_id / monthly_budget_usd / system_role / box_blocked*) are NEVER spread into the JSON. A leak-prevention unit test (tests/profile_route.mjs) asserts their absence + the 404-on-unknown-id behaviour. THROTTLED since task 1002804. The global limiter meters WRITES only (rateLimitMiddleware returns early for GET) and the public read limiter is mounted under /public/*, which this is not — so this route answered at line rate. That was tolerable while it re-presented leaderboard data; it is not now that it also carries `handle`, because an id walk then yields the builder → canonical-profile-handle map in one call per builder. modules/platform-identity/routes/public-profile.js caps /profiles/:handle for exactly that mapping, so the two routes that publish it get the same ceiling. The EXISTING shared /public/* budget (120/60s per IP), not a new one — ADR 0209's rule that routes answering one question spend one budget.\n\n**Rank:** `public` — No authentication — any caller.",
|
|
2851
2851
|
"x-rank": "public",
|
|
2852
2852
|
"x-source": "src/bongos/routes/builders.js",
|
|
2853
2853
|
"parameters": [
|
|
@@ -15820,7 +15820,8 @@
|
|
|
15820
15820
|
"shipped_count": {},
|
|
15821
15821
|
"recent_ships": {},
|
|
15822
15822
|
"achievements": {},
|
|
15823
|
-
"cross_project": {}
|
|
15823
|
+
"cross_project": {},
|
|
15824
|
+
"handle": {}
|
|
15824
15825
|
},
|
|
15825
15826
|
"required": [
|
|
15826
15827
|
"builder",
|
|
@@ -16605,11 +16606,13 @@
|
|
|
16605
16606
|
"type": "boolean"
|
|
16606
16607
|
},
|
|
16607
16608
|
"settings": {},
|
|
16609
|
+
"planet": {},
|
|
16608
16610
|
"options": {}
|
|
16609
16611
|
},
|
|
16610
16612
|
"required": [
|
|
16611
16613
|
"ok",
|
|
16612
16614
|
"settings",
|
|
16615
|
+
"planet",
|
|
16613
16616
|
"options"
|
|
16614
16617
|
]
|
|
16615
16618
|
},
|
|
@@ -17575,6 +17578,12 @@
|
|
|
17575
17578
|
},
|
|
17576
17579
|
"joinability": {
|
|
17577
17580
|
"type": "string"
|
|
17581
|
+
},
|
|
17582
|
+
"planet_template": {
|
|
17583
|
+
"type": "string"
|
|
17584
|
+
},
|
|
17585
|
+
"planet_accent": {
|
|
17586
|
+
"type": "string"
|
|
17578
17587
|
}
|
|
17579
17588
|
},
|
|
17580
17589
|
"additionalProperties": false
|
|
@@ -17586,11 +17595,13 @@
|
|
|
17586
17595
|
"type": "boolean"
|
|
17587
17596
|
},
|
|
17588
17597
|
"saved": {},
|
|
17598
|
+
"planet": {},
|
|
17589
17599
|
"push": {}
|
|
17590
17600
|
},
|
|
17591
17601
|
"required": [
|
|
17592
17602
|
"ok",
|
|
17593
17603
|
"saved",
|
|
17604
|
+
"planet",
|
|
17594
17605
|
"push"
|
|
17595
17606
|
]
|
|
17596
17607
|
},
|
package/docs/api-reference.md
CHANGED
|
@@ -455,7 +455,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
455
455
|
| POST | `/api/bongos/provisioning/instances/:id/github-app` | `any-builder` | `account` | POST /provisioning/instances/:id/github-app — mint a GitHub-App-Manifest onboarding link for the caller's OWN instance (task 2080, ADR 01… |
|
|
456
456
|
| POST | `/api/bongos/provisioning/instances/:id/restart` | `any-builder` | — | POST /provisioning/instances/:id/restart — restart an ACTIVE project the heartbeat cannot reach (task 1003238). |
|
|
457
457
|
| GET | `/api/bongos/provisioning/instances/:id/settings` | `any-builder` | — | GET /provisioning/instances/:id/settings — read a project's effective settings + the allowed values (so a form can render its choices). |
|
|
458
|
-
| PATCH | `/api/bongos/provisioning/instances/:id/settings` | `any-builder` | `platform_visibility`, `joinability` | PATCH /provisioning/instances/:id/settings — change a project's settings. |
|
|
458
|
+
| PATCH | `/api/bongos/provisioning/instances/:id/settings` | `any-builder` | `platform_visibility`, `joinability`, `planet_template`, `planet_accent` | PATCH /provisioning/instances/:id/settings — change a project's settings. |
|
|
459
459
|
| POST | `/api/bongos/provisioning/instances/:id/teardown` | `any-builder` | — | POST /provisioning/instances/:id/teardown — request teardown of the caller's OWN instance. |
|
|
460
460
|
| GET | `/api/bongos/provisioning/onboard-plan` | `public` | — | GET /provisioning/onboard-plan?mode=greenfield\|adopt&domain=<host> — the CANONICAL onboarding step sequence (task 2085; goal 35 / criteri… |
|
|
461
461
|
| GET | `/api/bongos/provisioning/slug-available` | `any-builder` | — | GET /provisioning/slug-available?slug=<handle> — is this handle free to create under? rank: any authenticated builder (same gate as the c… |
|