@bongos/core 1.19.430 → 1.19.431

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 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.430",
6
- "core_contract": "1.19.430",
7
- "source_commit": "262b66056ab6867973959a2b1eacff9f625bca07",
5
+ "core_version": "1.19.431",
6
+ "core_contract": "1.19.431",
7
+ "source_commit": "4fd9f0748403564b999e3e0459d44c169c751d4b",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-29T21:13:01.178Z",
9
+ "built_at": "2026-08-29T21:48:51.014Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
- "docs_redacted": 364,
12
+ "docs_redacted": 365,
13
13
  "agent_docs_stubbed": 23,
14
- "functional_verbatim": 1738,
14
+ "functional_verbatim": 1740,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2125,
20
- "tree_sha256": "4781e80e202329cceb896cb95f60c931bd52807b92019cf69855068527aa6bc4",
19
+ "file_count": 2128,
20
+ "tree_sha256": "bc4e3ea3fba769c809456cccf0abb0abcc8d513d047083307f7431afbc38f780",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -352,12 +352,12 @@
352
352
  {
353
353
  "path": "clients/bongos-client/README.md",
354
354
  "mode": "0000644",
355
- "sha256": "f190d3844d06a676480e270b05f14c13bd797915a43619be0a18affa06117000"
355
+ "sha256": "6ad13168a2196e29e37c2810108686b3c24f30f739691634f9085ebfcd1fc804"
356
356
  },
357
357
  {
358
358
  "path": "clients/bongos-client/bongos-client.global.js",
359
359
  "mode": "0000644",
360
- "sha256": "562e26ce56898840c4bacf89ab7b31f7f80b30fa393374a3f8dc9c7ef973e48f"
360
+ "sha256": "dfaca6bbdd87f2d32b1684bbddd7bb4454367ccf1624a9bd5b38b571dd3708db"
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": "a4b6049666626ab1a99c8c30b0e8fa0ce40e11ba3e4ae61e5763c6ce6338a5e5"
370
+ "sha256": "92bcaf81301220166e1126d65492ecc011b3b2fac94e594bf22f8049aa45aece"
371
371
  },
372
372
  {
373
373
  "path": "clients/bongos-client/index.d.ts",
374
374
  "mode": "0000644",
375
- "sha256": "f9249af254590fbc92dd50837af339f6d8995db613ee7aeb2b19405262751dd0"
375
+ "sha256": "3ab2c5433e9a59032143ad2bc90dd9e4bad51ccaa9a2029034f0e53d199472ce"
376
376
  },
377
377
  {
378
378
  "path": "clients/bongos-client/index.mjs",
379
379
  "mode": "0000644",
380
- "sha256": "a8ea6b45b485350bb0190839cd89429706f9a046c9d2be2a7fe99555e691b852"
380
+ "sha256": "cf02ec862ca9fcf7700ed2566c2e148c83c763d9194a450a09cb3d191ad4c90b"
381
381
  },
382
382
  {
383
383
  "path": "clients/bongos-client/package.json",
@@ -1569,20 +1569,25 @@
1569
1569
  "mode": "0000644",
1570
1570
  "sha256": "b964e1674d615cb4525e94bb4cac63d4ac0813e3c1ea9236620bf030d7c53124"
1571
1571
  },
1572
+ {
1573
+ "path": "docs/adr/0215-the-recruiter-sliver-is-a-ceiling-not-a-step.md",
1574
+ "mode": "0000644",
1575
+ "sha256": "8c248b7e041f21da26ef55cb79c8bcaf20713d3e271ea7ee0de851d452c19e58"
1576
+ },
1572
1577
  {
1573
1578
  "path": "docs/adr/README.md",
1574
1579
  "mode": "0000644",
1575
- "sha256": "9b2d411decbc309773631064ed286ee0d134860ec688204ac45b827a28748d7a"
1580
+ "sha256": "5cf8b81ec623dd3614683c2114c1fee38ddc5c3a1bf73e2c69ac4fe8ef79ff25"
1576
1581
  },
1577
1582
  {
1578
1583
  "path": "docs/api-reference.md",
1579
1584
  "mode": "0000644",
1580
- "sha256": "50d3d00e2183fb0e1065e36a0e61a0ef2dee5cf76e51042c1c284ffc539a7f03"
1585
+ "sha256": "c20be8f4ab3d66c348c6c2d4d4f45c3390a4078899459831fc4851d51a740ea2"
1581
1586
  },
1582
1587
  {
1583
1588
  "path": "docs/api/openapi.json",
1584
1589
  "mode": "0000644",
1585
- "sha256": "85d13548f3c1d43bed1f157b4992eda7a2948386d851ed87d1d5971d21f4a8b1"
1590
+ "sha256": "8998dc287e00a54bfd206d427a923700874dc34db5a459671f8c90d8b6ec117c"
1586
1591
  },
1587
1592
  {
1588
1593
  "path": "docs/architecture.md",
@@ -1607,12 +1612,12 @@
1607
1612
  {
1608
1613
  "path": "docs/copy-inventory.md",
1609
1614
  "mode": "0000644",
1610
- "sha256": "9fd0b4e8b21ebafbd172d990c35d0038b4a3d11cf3c582260395e943dace4b02"
1615
+ "sha256": "cd0071623f3a81620e356ad59ff90d68ab4f5a5630dff9399856bfd8ab0de508"
1611
1616
  },
1612
1617
  {
1613
1618
  "path": "docs/copy-registry.json",
1614
1619
  "mode": "0000644",
1615
- "sha256": "3f038ff873b92b85167ef9e2a91e6a82ddf51fd3dc6131d2a3eb847b38876a6e"
1620
+ "sha256": "c4ebb37c87a7416aa4005812702980a084f39f69fe336d3676baf3dc8f0e6530"
1616
1621
  },
1617
1622
  {
1618
1623
  "path": "docs/design/apex-pass-2-direction.md",
@@ -1962,7 +1967,7 @@
1962
1967
  {
1963
1968
  "path": "docs/file-map.md",
1964
1969
  "mode": "0000644",
1965
- "sha256": "23121260ec5061645027d50609712217a619ea9ae8ad539cdeeb18e1cd552755"
1970
+ "sha256": "b0b9355c79b2f1caa965baf8afd645140d341f30a56483a3724697611e20322a"
1966
1971
  },
1967
1972
  {
1968
1973
  "path": "docs/handoff-template.md",
@@ -1972,7 +1977,7 @@
1972
1977
  {
1973
1978
  "path": "docs/module-api-changelog.md",
1974
1979
  "mode": "0000644",
1975
- "sha256": "b01ae85c05dc319d5baa59b256712d56bcf342c31e03c9823c12879efe3202d0"
1980
+ "sha256": "bd4bbd81fbdd0cbffbcc882bb8bd876caf4aacc863a0e4ae3b331ee8133053e6"
1976
1981
  },
1977
1982
  {
1978
1983
  "path": "docs/modules-contract.md",
@@ -3287,7 +3292,7 @@
3287
3292
  {
3288
3293
  "path": "modules/dev-box/app/src/vendor/bongos-client.cjs",
3289
3294
  "mode": "0000644",
3290
- "sha256": "a4b6049666626ab1a99c8c30b0e8fa0ce40e11ba3e4ae61e5763c6ce6338a5e5"
3295
+ "sha256": "92bcaf81301220166e1126d65492ecc011b3b2fac94e594bf22f8049aa45aece"
3291
3296
  },
3292
3297
  {
3293
3298
  "path": "modules/dev-box/app/test/unit.mjs",
@@ -4227,12 +4232,12 @@
4227
4232
  {
4228
4233
  "path": "modules/hall-ui/public/settings.html",
4229
4234
  "mode": "0000644",
4230
- "sha256": "81da0b750da880c411a8604687a8c300852c631ffdd07d4fd1bb3c4533e650c0"
4235
+ "sha256": "3760dd190ea83370de2d45f74b152f8bd24abe2a8761d5bb681d26600fdbd29e"
4231
4236
  },
4232
4237
  {
4233
4238
  "path": "modules/hall-ui/public/settings.js",
4234
4239
  "mode": "0000644",
4235
- "sha256": "d3ddd434100c65a5839e3004ad1a5c4537d2da23ecd4441ee3eff3aa0ee8b5a1"
4240
+ "sha256": "3d5881896057de05daba30382b7ebf5aeb9f61cba55aae75916594ed3dc1a80f"
4236
4241
  },
4237
4242
  {
4238
4243
  "path": "modules/hall-ui/public/shell.js",
@@ -4832,7 +4837,7 @@
4832
4837
  {
4833
4838
  "path": "modules/platform-identity/account-visibility.js",
4834
4839
  "mode": "0000644",
4835
- "sha256": "788d0a51e29e0bd7971a63b8ed168ce71beb2a30a170bd0dbfc457d96b275233"
4840
+ "sha256": "020279ae50f2f9e7a004fe571e5ca14493ad26bd22c395cc4805f759f5560b72"
4836
4841
  },
4837
4842
  {
4838
4843
  "path": "modules/platform-identity/application-echo.js",
@@ -4962,7 +4967,7 @@
4962
4967
  {
4963
4968
  "path": "modules/platform-identity/platform-identity.js",
4964
4969
  "mode": "0000644",
4965
- "sha256": "09a9be4a79097faf612ee52b1eea1c5b845bba873032853aab36c55411395833"
4970
+ "sha256": "1542edccc61a1cdb77540f5b6521a91477234af03cdde9178a1ed5516363e1fd"
4966
4971
  },
4967
4972
  {
4968
4973
  "path": "modules/platform-identity/pollers/code-sweep.js",
@@ -4974,6 +4979,11 @@
4974
4979
  "mode": "0000644",
4975
4980
  "sha256": "2e898799632a2b5c2ead9cb44fa0b8029fcb6d217f7d5f26c8a39b34c75bbea7"
4976
4981
  },
4982
+ {
4983
+ "path": "modules/platform-identity/recruiter-sliver.js",
4984
+ "mode": "0000644",
4985
+ "sha256": "e1385b83ac5bb82d13ffbe5070043c2fd0ca20385b3d62e8331ef917d9fca496"
4986
+ },
4977
4987
  {
4978
4988
  "path": "modules/platform-identity/routes/connections.js",
4979
4989
  "mode": "0000644",
@@ -5002,7 +5012,7 @@
5002
5012
  {
5003
5013
  "path": "modules/platform-identity/routes/scouting.js",
5004
5014
  "mode": "0000644",
5005
- "sha256": "e9afb0c1839c2933b1222158a3fccca8c3e3659282bb705e9d5c80575471200c"
5015
+ "sha256": "0f898c038abc6c0aceb2f5649008cdc7fc66bc1b7f83129aae3da65f1eb33c15"
5006
5016
  },
5007
5017
  {
5008
5018
  "path": "modules/platform-identity/routes/sso.js",
@@ -5577,12 +5587,12 @@
5577
5587
  {
5578
5588
  "path": "package-lock.json",
5579
5589
  "mode": "0000644",
5580
- "sha256": "39bed51a410e133578366d4dd1c3b563b33053f266d0f77df36588a14094184d"
5590
+ "sha256": "bf42d45381a85e4f5794f08f0deaf9e7baee6787599cbb0f034153c03e06affb"
5581
5591
  },
5582
5592
  {
5583
5593
  "path": "package.json",
5584
5594
  "mode": "0000644",
5585
- "sha256": "20414384b301f90cb64dd0349b014cdcc377f873dec08b80432f9d08f1ad004f"
5595
+ "sha256": "b2c2d95bd1b8ef2a9d5c6dfa42362827ddf2e50beddbc8765460885ecabd4cf5"
5586
5596
  },
5587
5597
  {
5588
5598
  "path": "public-docs/index.html",
@@ -7712,7 +7722,7 @@
7712
7722
  {
7713
7723
  "path": "src/module-api.js",
7714
7724
  "mode": "0000644",
7715
- "sha256": "f9dbf12c1dc4cbb8e2e01b2bc43110853a9667b08bbee731f09bc998cf9d0a35"
7725
+ "sha256": "a5f2a1e430a0dc50156d93a88ed4bd1b6c6bc232eed2ea1aab4ae017ee768777"
7716
7726
  },
7717
7727
  {
7718
7728
  "path": "src/module-loader/catalog.js",
@@ -7772,7 +7782,7 @@
7772
7782
  {
7773
7783
  "path": "tests/account_privacy_flags.mjs",
7774
7784
  "mode": "0000644",
7775
- "sha256": "d0c32156de102ab98447542def38978caded0dbe03e7a6f5004240484ba037ec"
7785
+ "sha256": "ab99d502635f87dd4021ecdd0badb88f842889b1b8902919fe6511aa363457b1"
7776
7786
  },
7777
7787
  {
7778
7788
  "path": "tests/achievements_reconcile.mjs",
@@ -8582,7 +8592,7 @@
8582
8592
  {
8583
8593
  "path": "tests/effective_visibility_predicate.mjs",
8584
8594
  "mode": "0000644",
8585
- "sha256": "1aeeaea5d344543ab48b8dde994f4375bea9ef7678ce50e09402376334731f76"
8595
+ "sha256": "88ff4b377f71785545e3aed908df70b505af40453f47695646a9f72bf7250ab0"
8586
8596
  },
8587
8597
  {
8588
8598
  "path": "tests/est_advisory.mjs",
@@ -8957,7 +8967,7 @@
8957
8967
  {
8958
8968
  "path": "tests/government_require_permission.mjs",
8959
8969
  "mode": "0000644",
8960
- "sha256": "36596b8870d2edc60bd810af5e10fa864d7f35e62e358d2dff58ed85b69922c0"
8970
+ "sha256": "a2711b93fcb7e6a602d22fbc864676f5dfdc6c503cc6551badc32e36facb723c"
8961
8971
  },
8962
8972
  {
8963
8973
  "path": "tests/government_resolver.mjs",
@@ -9767,7 +9777,7 @@
9767
9777
  {
9768
9778
  "path": "tests/profile_route.mjs",
9769
9779
  "mode": "0000644",
9770
- "sha256": "0189425a0430dbab84044bdca2ac99e4dde87db061b8226412aa2fc393bb3119"
9780
+ "sha256": "ab6881bf65742f82dcb748daa5b8665c3a852a8fcc5aedb0a7040cafd6a3bdf4"
9771
9781
  },
9772
9782
  {
9773
9783
  "path": "tests/profile_ui_cross_project.mjs",
@@ -9959,6 +9969,11 @@
9959
9969
  "mode": "0000644",
9960
9970
  "sha256": "6d82e99c69d23a4dd7a844ffdd7f239ea70c94d9c509e2fc8e1a672cd9f05a28"
9961
9971
  },
9972
+ {
9973
+ "path": "tests/recruiter_sliver_boundary.mjs",
9974
+ "mode": "0000644",
9975
+ "sha256": "cad269f556293e0caa234561f5764f90998a5d133b1e0147a5bbf22403c55e3a"
9976
+ },
9962
9977
  {
9963
9978
  "path": "tests/redteam_patrol.mjs",
9964
9979
  "mode": "0000644",
@@ -10047,7 +10062,7 @@
10047
10062
  {
10048
10063
  "path": "tests/scouting_owner_access_and_reach.mjs",
10049
10064
  "mode": "0000644",
10050
- "sha256": "5c53f51280b77816b5498808ab097a209a368c2e3b6cbdbc6a8ed2a55e11d8b9"
10065
+ "sha256": "db4aa5ab365d1d2e9884b2c5a3f2abc40942e123844b7d5434434ddce680fd13"
10051
10066
  },
10052
10067
  {
10053
10068
  "path": "tests/scouting_page_gate.mjs",
@@ -5,7 +5,7 @@ A **generated**, zero-dependency typed client for the Bongos API — produced fr
5
5
  by hand; it regenerates when the spec changes, so it can never drift from the routes.
6
6
 
7
7
  - API version: **v1** (served at `/api/bongos/v1`)
8
- - 338 operations across 53 resource groups
8
+ - 339 operations across 53 resource groups
9
9
 
10
10
  ## Install
11
11
 
@@ -705,6 +705,8 @@ function createClient(opts = {}) {
705
705
  "scouting": {
706
706
  // GET /scouting — rank: metic+archon — GET /scouting
707
707
  getScouting: (args) => request("GET", "/scouting", { hasBody: false }, args),
708
+ // GET /scouting/{handle} — rank: archon — GET /scouting/:handle
709
+ getScoutingHandle: (args) => request("GET", "/scouting/{handle}", { hasBody: false }, args),
708
710
  },
709
711
  "search": {
710
712
  // POST /search — rank: any-builder — POST /search
@@ -704,6 +704,8 @@ function createClient(opts = {}) {
704
704
  "scouting": {
705
705
  // GET /scouting — rank: metic+archon — GET /scouting
706
706
  getScouting: (args) => request("GET", "/scouting", { hasBody: false }, args),
707
+ // GET /scouting/{handle} — rank: archon — GET /scouting/:handle
708
+ getScoutingHandle: (args) => request("GET", "/scouting/{handle}", { hasBody: false }, args),
707
709
  },
708
710
  "search": {
709
711
  // POST /search — rank: any-builder — POST /search
@@ -110,6 +110,7 @@ export interface GetPublicRepoInfoResponse { blob_base: unknown; tree_base: unkn
110
110
  export interface GetPublicTasksIdResponse { task: unknown }
111
111
  export interface GetPublicUptimeResponse { services: unknown }
112
112
  export interface GetPublicVersionsResponse { versions: unknown; page: PageInfo }
113
+ export interface GetScoutingHandleResponse { sliver: unknown }
113
114
  export interface GetScoutingResponse { builders: unknown }
114
115
  export interface GetSecurityDocsResponse { docs: unknown }
115
116
  export interface GetSecurityDocsSlugResponse { doc: unknown }
@@ -1012,6 +1013,8 @@ export interface BongosClient {
1012
1013
  "scouting": {
1013
1014
  /** GET /scouting — rank: metic+archon */
1014
1015
  getScouting(args?: RequestArgs): Promise<GetScoutingResponse>;
1016
+ /** GET /scouting/{handle} — rank: archon */
1017
+ getScoutingHandle(args?: RequestArgs): Promise<GetScoutingHandleResponse>;
1015
1018
  };
1016
1019
  "search": {
1017
1020
  /** POST /search — rank: any-builder */
@@ -701,6 +701,8 @@ export function createClient(opts = {}) {
701
701
  "scouting": {
702
702
  // GET /scouting — rank: metic+archon — GET /scouting
703
703
  getScouting: (args) => request("GET", "/scouting", { hasBody: false }, args),
704
+ // GET /scouting/{handle} — rank: archon — GET /scouting/:handle
705
+ getScoutingHandle: (args) => request("GET", "/scouting/{handle}", { hasBody: false }, args),
704
706
  },
705
707
  "search": {
706
708
  // POST /search — rank: any-builder — POST /search
@@ -0,0 +1,116 @@
1
+ # ADR 0215 — The pre-accept recruiter sliver is a CEILING, not a step: a whitelist keyed by consent-and-reach, on the recruiting surface's own door
2
+
3
+ - **Status:** accepted
4
+ - **Date:** 2026-08-29
5
+ - **Task:** [task 1002968](https://cloudbongos.com/builders#/task/1002968) (PV3, goal 1000045 — Public builder profiles, criteria C2/C5)
6
+ - **Relates to:** `docs/specs/<redacted>.md` **D3** (the owner-signed sliver, 2026-08-16) and D1/D2 (the two privacy axes), [ADR 0171](<redacted>.md) D2/D4/D5 (rollup consent, no partial numbers, no-store), [ADR 0210](<redacted>.md) D2/D4 (the recruiting door; reach composed but not folded down), [ADR 0201](<redacted>.md) D3 (the `project.recruit` atom belongs to the console wave), [ADR 0016](<redacted>.md) (cross-project numbers are descriptive, never authority), [ADR 0202](<redacted>.md) D4 (one shared effective-visibility predicate)
7
+
8
+ > 0213 and 0214 are held by sessions in flight; this ADR takes 0215 rather than race them (the ADR 0199 / 0202 / 0207 / 0210 precedent).
9
+
10
+ ## Context
11
+
12
+ Privacy spec D2 has a row that nothing could serve: **private · discoverable**. A builder who wants no public profile page but does want to be found for work sets `account_visibility = 'private'` and `recruiter_discoverable = true`, and until now that combination bought them exactly nothing — [ADR 0210](<redacted>.md) D4 says so in as many words: *"a private account is absent from the directory on purpose: the full rollup is never its disclosure — the D3 sliver is."* The sliver was the unbuilt half.
13
+
14
+ Two consequences had accumulated around that gap. The Settings toggle's copy was deliberately **narrowed** by task 1002290 to "the People directory", because the sliver was unbuilt and no guard could hold a wider promise. And [ADR 0201](<redacted>.md) had to ship the cross-project application queue showing a private applicant's *name, handle and note only*, deferring the richer record to "the sliver half, sequenced behind the recruiter port".
15
+
16
+ The design question is not what to show. The owner signed that on 2026-08-16 and it is short: **handle, display name, avatar, the disciplines union, and a coarse "active in the last 30 days" boolean — nothing else**; name-only under `hide_stats`; never for a provisional account; never a profile-page link. The question is what *kind of thing* the sliver is, and everything below follows from one answer: **it is a ceiling, not a step.** Nothing a recruiter holds after reading it gets them closer to the record it withholds.
17
+
18
+ ## Decision
19
+
20
+ ### D1 — The sliver's audience predicate is CONSENT **and** REACH, and it deliberately omits the EXISTENCE axis
21
+
22
+ `recruiterSliverVisibleSql` / `isRecruiterSliverVisible` join `modules/platform-identity/account-visibility.js`, the sole owner of every effective-visibility answer (ADR 0202 D4). Like `rollupVisibleSql` and `scoutingListableSql` it is **composed**, never restated:
23
+
24
+ ```
25
+ recruiterSliverVisibleSql = accountActiveSql AND recruiterReachSql
26
+ ```
27
+
28
+ It is the one composed answer in that file that does **not** contain `accountVisibleSql`, and that omission is the decision. Every other public read floors on `account_visibility = 'public'`; ANDing it here would empty the sliver of precisely the account it exists to serve, leaving a poorer, redundant copy of the directory.
29
+
30
+ **Dropping the existence axis is safe only because the reach half is an explicit opt-IN for a private account.** `recruiterReachSql` is `COALESCE(recruiter_discoverable, account_visibility = 'public')`: for a private account the `NULL` default resolves to **false**, so a private account reaches this surface only when its owner set the flag by hand. The widening is therefore not the platform's; it is the account's. Consent, not a relaxed floor.
31
+
32
+ `profile_state = 'active'` still floors it — a provisional account is never recruiter-visible, exactly as spec D3 says out loud — and `hide_stats` appears nowhere in the predicate, because hiding changes the **shape** of the sliver, never who appears in it.
33
+
34
+ **And the opt-in has to be the one they MEANT — which needed a write-path change, not only a predicate.** That safety argument holds only if an explicit `recruiter_discoverable = true` on a private account was given *as* a private account, and it need not have been. Toggle discoverability off and on again while **public** and the column stops being `NULL`: it becomes an explicit `true` meaning *"recruiters may see my public profile"*. Set the profile to Private later — reading a row that promises the page answers "not found" to every visitor — and that stale `true` used to be **inert**, because every recruiting surface floored on the existence axis. This ADR's own predicate is what would have activated it: silently, with no re-consent and no notice. So `setAccountPrivacy` now resets `recruiter_discoverable` to `NULL` — back to "follow visibility" — whenever a PATCH turns an account private **without naming reach**; naming it in the same PATCH *is* the re-consent and stands. The D2 "private · discoverable" row still exists; it now takes two deliberate moves instead of one forgotten one. (Found in adversarial review. The write's D2 matrix is resolved from the statement's own `CASE` in `tests/account_privacy_flags.mjs`, so deleting the arm reds it.)
35
+
36
+ Because that predicate is the only shared answer missing the existence axis, `tests/effective_visibility_predicate.mjs` **pins it to its one reader**. Naming it in that guard's `ADOPTS` list is what lets the sliver's own query pass the adoption rule, and that would otherwise be a hole: a future public read joining it would look adopted while quietly serving private accounts.
37
+
38
+ ### D2 — The projection is a literal whitelist, `hide_stats` decides its SHAPE, and the hidden case never fetches what it will not show
39
+
40
+ `modules/platform-identity/recruiter-sliver.js` builds the object key by key. No row is ever spread, so a column added to either query cannot ride along. Two shapes, and only two:
41
+
42
+ | | keys |
43
+ |---|---|
44
+ | full | `handle`, `display_name`, `avatar_url`, `disciplines`, `active_recently` |
45
+ | `hide_stats` | `handle`, `display_name`, `avatar_url` |
46
+
47
+ The two rollup-derived keys are **absent, not null** — the sliver does not carry an empty box where a number used to be — and for a hidden account **the activity query is never issued at all**. Dropping the fields after fetching them would be equivalent today and strictly worse tomorrow: the cheapest way for a later edit to leak a number is for the number to already be in hand.
48
+
49
+ Four absences are load-bearing rather than incidental:
50
+
51
+ - **credits / works shipped / karma / project names / project count** — the post-accept record. "The aggregate is not the leak, the name is" (ADR 0171 D6), and a count beside a hidden total is a leak by subtraction, so there is no count either.
52
+ - **`last_active` as a timestamp.** The comparison to the 30-day window happens **in SQL** and only the boolean returns, so the date never enters the process and no later refactor can serialize it. A date is a behavioural trace; a boolean is a yes/no.
53
+ - **`github_id` and `github_login` as FIELDS.** Neither is a key of the sliver, and D3 names the *handle*, not the login. **The limit, stated rather than glossed:** the avatar D3 grants is GitHub's own URL — `https://avatars.githubusercontent.com/u/<github_id>?v=4` — so granting the avatar grants the account id inside it, and that id resolves through GitHub's public API to a login. Granting the avatar is the owner's decision, already made; pretending the id does not ride along was this ADR's. The proof now pins *where* it may appear (inside that URL and nowhere else) instead of asserting an absence the code does not deliver — an earlier draft claimed "the join key stays internal" while its fixture used an invented avatar host, so the sentinel could not have caught it.
54
+ - **any profile-page URL**, which spec D3 forbids outright.
55
+
56
+ ### D3 — The handle is an ADDRESS, not a door — which is what "pre-accept" means operationally
57
+
58
+ The sliver hands a recruiter a handle, and the handle opens nothing: `GET /profiles/:handle` joins `accountVisibleSql` and answers **404** for exactly the private account whose sliver the recruiter just read. That is the whole claim of this ADR reduced to one assertion, and it is asserted — with a control proving the same page renders 200 for a publicly-visible builder, so the 404 cannot pass by the route simply being broken.
59
+
60
+ A **previous** handle resolves to nothing here either, deliberately unlike `resolveHandle`'s 301. The public page redirects so a *shared link* survives a rename; a recruiting read has no shared links to keep working, and resolving one would confirm to a recruiter that the account renamed — a disclosure the sliver never grants. (This was the one mutation the first draft of the proof did not catch; the rule was documented in the source and unheld by a test.)
61
+
62
+ ### D4 — The sliver holds spec D3's **archon floor**; the directory's wider door is not reused, and the audience goes to the owner
63
+
64
+ `GET /scouting/:handle` sits in `modules/platform-identity/routes/scouting.js` beside `GET /scouting`, and the two do **not** share a gate. The sliver is `requireBuilder` → `api.requireRank('archon')`; the directory keeps `requireScoutingReader({ curateGate: requirePermission('project.curate') })`. Each gate is spelled in its own route's middleware list, so `route-rank-check.js` and `gen-api-docs.js` classify each from its real floor rather than recording a privileged route as `any-builder`.
65
+
66
+ Spec D3 says the sliver is "gated at the route by `project.recruit`" — and that atom **does not exist**. ADR 0201 D3 and ADR 0210 both refuse to mint it, on the same grounds: it belongs to the project-admin-console wave ([spike 1002786](https://cloudbongos.com/builders#/task/1002786) R3), which defines it, and minting it in a third place would fork its definition across three waves before any had shipped. **So which door does it use meanwhile?** The first draft reused the directory's, and that was wrong in the dangerous direction. Spec D1 declares the missing atom's shape — **floor archon**, widenable per instance through the governance tab — while the directory admits `project.curate` at the **metic** floor (ADR 0157) **OR any project owner**, who is typically a bare `xenos` on the hub (ADR 0210 D2). That door is *wider* than the signed floor on both halves. Reusing it would have widened an owner-signed privacy decision by implementation convenience, on the first surface that discloses a private account — and narrowing after people have been disclosed is not a recoverable move, while widening later is. So the sliver holds D3's declared floor, by rank, until the atom exists.
67
+
68
+ **The audience is the owner's call, and it is filed rather than taken.** [Blocker 1000126](https://cloudbongos.com/builders#/blocker/1000126) asks whether the wider recruiting audience is authorized here, with the evidence and a recommendation (hold the floor; let `project.recruit` — grantable per instance — settle it, which is exactly the knob the question wants). Answering it moves **one line**, and the tests that pin the floor flip with it. Goal 1000045 C5 (*"project owners, not only Archons, browse builder profiles to recruit"*) pulls the other way, and that tension is what the blocker carries: C5 was written about the DIRECTORY, which shows public builders; this surface shows private ones.
69
+
70
+ The refusal for an unreachable *builder* is **404, never 403**: a 403 would confirm the handle exists, which is precisely the disclosure the opt-out removes. (A refusal of the *caller* is an ordinary rank 403 naming the floor it wanted, so the hall can render why.) And `Cache-Control: no-store`, the `/profiles/:handle` posture, because the rollup half is live-derived and hide must beat any cache (ADR 0171 D5) — the directory route gained the same header here, having been the last rollup read without it.
71
+
72
+ ### D5 — The sliver grants nothing (ADR 0016), and that is enforced structurally
73
+
74
+ Being visible to a recruiter confers no rank, no permission, no claim right, and nothing consults the sliver to decide anything: the module is required by **exactly one** first-party server file, the route that serializes it, enumerated from source so a second reader fails the build. The response carries no rank, role, permission or membership under any name. The reverse holds too and is the point of the reach axis: a builder's recruiting opt-out changes who can *see* them and grants or denies them nothing.
75
+
76
+ ### D6 — The Settings copy widens back to name **both** recruiting surfaces, held by a guard rather than by good intentions
77
+
78
+ Task 1002290 narrowed the discoverable toggle from "every recruiting view" to "the People directory" on the copy rule that *a toggle may promise exactly what is enforced* — the sliver was unbuilt, so no guard could hold the wider claim. It is built and gated now, so the row names both: *"removes you from both recruiting surfaces — the People directory, and a recruiter's look-up of one builder — straight away."*
79
+
80
+ "Both" rather than "every", and that word choice is the decision. `tests/account_privacy_flags.mjs` asserts that `routes/scouting.js` mounts **exactly two** routes and that `scoutingListableSql` and `recruiterSliverVisibleSql` each compose `recruiterReachSql`. A third recruiting read in that file, or either reader dropping the reach axis, reds it. "Every recruiting view" would be an open-ended promise no guard can hold, and it stays banned.
81
+
82
+ **The half that was actually wrong, though, was the GRANT.** Spec D3 makes stating it a MUST — *"the discoverable toggle's settings copy must state exactly this grant"* — and the shipped copy was false: it told a hidden account it would show *"your name and nothing else"* while `SLIVER_KEYS_HIDDEN` also ships the **avatar**, and the shown branch never mentioned the picture at all. Neither branch told a *private* account that this is the one thing a recruiter sees while their page answers "not found". All three are fixed, and the guard no longer checks transcribed phrases: it iterates `SLIVER_KEYS` / `SLIVER_KEYS_HIDDEN` from the module itself, so a sixth granted field cannot ship without the copy being made to name it.
83
+
84
+ ### D7 — No migration, and no new file in `platform-identity.js`
85
+
86
+ Both columns exist (migration `<redacted>.sql`); the activity table's `(github_id)` index already serves both reads. Spec D3 asks for the port "beside `getPublicProfileExtras`" and it is beside it *in the module*, not in the file: `platform-identity.js` sits on the 1500-line ratchet with no headroom — the `account-visibility.js` / `scouting-authz.js` / `application-echo.js` precedent, now five sessions deep.
87
+
88
+ ## Consequences
89
+
90
+ - Privacy spec **D2's "private · discoverable" row is finally served**, and [task 1002972](https://cloudbongos.com/builders#/task/1002972) — the application queue rendering a private applicant's record — has the port it was sequenced behind. Its own half of the problem is unchanged and still real: ADR 0201 notes the hub cannot check an *instance* permission from a cross-origin browser read, so how the reviewer's browser reaches this route is that task's decision, not this one's.
91
+ - The recruiting surface now has two reads and one door. When `project.recruit` lands, it replaces `project.curate` in one expression and both routes move together.
92
+ - **The directory is unchanged.** A private account — discoverable or not — is still absent from `GET /scouting`, and a public leaderboard must still join `rollupVisibleSql`, never `scoutingListableSql` (ADR 0210 D4). Reach still governs the recruiting surface only.
93
+
94
+ ⚠ **Honest limits, recorded rather than fixed:**
95
+
96
+ - **No UI consumes the sliver yet.** This ships the port, the route and the proof; the recruiting *view* is the console wave's (spec D6, `<redacted>.md` CT6) and the application queue's (task 1002972). The route is reachable and gated today; nothing in the hall calls it.
97
+ - **The audience is UNRESOLVED, not settled** ([blocker 1000126](https://cloudbongos.com/builders#/blocker/1000126)). The archon floor is the conservative reading of an owner-signed spec, not a call this ADR is entitled to make — and it is *narrower* than goal C5 describes, so project owners cannot use the sliver today. An earlier draft of this section called the shipped gate "narrower than the spec's atom" and "not a widening in any direction"; both were wrong, and wrong in the dangerous direction, because the SUBJECT set strictly grows: private accounts were on no recruiter surface at all before this.
98
+ - **The rank gate is a documented HOLDOUT against the module-gate migration rule** (BV1.R105/R107, ADR 0151): `modules/*/routes` gates are supposed to be permission atoms, and `tests/government_require_permission.mjs` enforces it from source. This route is listed in that test's `RETAINED` set with its full argument — the atom it wants is specified and owned by another wave, minting it forks the definition, borrowing a different archon atom would name the wrong authority on a privacy surface, and the sibling's door is wider than the signed floor. It is temporary by construction: spike 1002786 R3 removes the entry.
99
+ - **The two gates are spelled twice, on purpose.** An earlier draft called them "one gate expression … a one-line change reaching both"; they never were one, and now they are deliberately different.
100
+ - **A recruiting read added in another FILE would not trip the two-route guard.** D6's count is over `routes/scouting.js` alone, so the "both recruiting surfaces" copy could become false without reddening. Sweeping every route file for reach-joining reads is a structural guard of its own and belongs with the shared-predicate work, not here.
101
+ - **`getRecruiterSliver` (the by-id port spec D3 names) has no production caller yet** — [task 1002972](https://cloudbongos.com/builders#/task/1002972) is its consumer. The route uses the by-handle sibling; the proof exercises both.
102
+ - **`active_recently` is coarse by design and coarse in practice.** It is derived from `max(last_active)` across the rollup, which is refreshed on federated sign-in and on ship — so a builder active only on a project that never reports will read as inactive. That understates rather than overstates, which is the safe direction for a privacy surface.
103
+ - **This route is not rate-limited, and there is an asymmetry worth naming.** `GET /profiles/:handle` carries a per-IP read limiter precisely so the handle→identity mapping cannot be scripted at line rate (task 1002798), and `GET /scouting/:handle` — which is authenticated and permission-gated, so it starts from a much stronger position — has none. The asymmetry that remains is specific: a reader of this surface can probe guessed handles unmetered and learn *which private accounts opted into recruiting*, a set no other surface publishes. It is bounded by needing to guess a private account's handle (nothing publishes it) and by the audience already holding the full rollup of every public builder, so it is a widening of degree, not of kind. It is **not** fixable by reusing `accountExistenceReadRateLimit`: ADR 0209's whole decision is one budget per oracle, and this is a different oracle — sharing it would hand an enumerator the sum of two budgets on the route it cares about. The right fix is a dedicated budget behind a doorway-exported factory, which is a decision about the doorway rather than about the sliver; filed rather than smuggled in here.
104
+
105
+ **Rejected:**
106
+
107
+ - **Flooring the sliver on `accountVisibleSql`** — it would empty the surface of the exact account D3 exists to serve.
108
+ - **Folding reach into `rollupVisibleSql`** so the sliver could reuse it — ADR 0210 D4's reason stands: it would silently empty a future public leaderboard of every opted-out builder.
109
+ - **Minting `project.recruit` here** — ADR 0201 D3 and ADR 0210 both refuse it; it belongs to the console wave that defines it.
110
+ - **A second route file** for one route — the two recruiting reads live in one file (they share consent machinery and a reader's mental model); what they deliberately do NOT share is a gate.
111
+ - **Reusing the directory's door for the sliver** — wider than the owner-signed floor on both halves, on the one surface that discloses a private account.
112
+ - **Leaving a stale `recruiter_discoverable = true` to be read as consent** after the account went private.
113
+ - **Returning the rollup-derived keys as `null` under `hide_stats`** — an empty box is still a box, and it means the numbers were fetched.
114
+ - **Returning a profile URL, `github_login`, a project count, or `last_active`** — each forbidden by spec D3 or by ADR 0171 D4's leak-by-subtraction rule.
115
+ - **301-ing a previous handle**, the way the public page does — a recruiting read has no shared links to preserve, and the redirect would confirm a rename.
116
+ - **403 for a non-discoverable builder** — it confirms the handle exists.
@@ -300,6 +300,7 @@ This keeps the decision history honest and traceable.
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
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 |
302
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 |
303
+ | 0215 | [**The pre-accept recruiter sliver is a CEILING, not a step: a whitelist keyed by consent-and-reach, on the recruiting surface’s own door** ([task 1002968](https://cloudbongos.com/builders#/task/1002968), PV3 · goal 1000045 criteria C2/C5; privacy spec D3, owner-signed 2026-08-16). Privacy spec D2 has a row nothing could serve — **private · discoverable** — and ADR 0210 D4 named the reason: *“the full rollup is never its disclosure — the D3 sliver is”*. Decisions: **D1** the audience predicate is `recruiterSliverVisibleSql` = `accountActiveSql AND recruiterReachSql`, composed in `account-visibility.js` (its sole owner) and the ONE composed answer that deliberately **omits the existence axis** — ANDing `accountVisibleSql` would empty the sliver of exactly the account it serves; dropping it is safe only because reach is `COALESCE(recruiter_discoverable, visibility=’public’)`, so a private account reaches this surface only by its owner’s explicit opt-IN — and, after adversarial review, that opt-IN must be the one they MEANT: `setAccountPrivacy` now resets `recruiter_discoverable` to NULL when a PATCH turns an account private without naming reach, because a `true` set while PUBLIC meant “recruiters may see my public profile” and this predicate would have activated that stale consent silently. `profile_state=’active’` still floors it (a provisional account is never recruiter-visible) and `hide_stats` is absent, because hide changes the sliver’s SHAPE, never who appears in it; the guard PINS that existence-less predicate to its one reader, since a future public read joining it would look adopted while serving private accounts. **D2** the projection is a literal whitelist — `handle`, `display_name`, `avatar_url`, `disciplines`, `active_recently`, and under `hide_stats` the last two are **absent, not null**, with the activity query never issued at all (the cheapest way for a later edit to leak a number is for it to already be in hand). `last_active` is compared to the 30-day window IN SQL and only the boolean returns, so the date never enters the process; `github_id`/`github_login` are never KEYS, every count and any profile URL stay out — with one limit stated rather than glossed: the avatar D3 grants is GitHub’s own URL and embeds the account id, so the proof pins WHERE the id may appear instead of claiming an absence the code does not deliver. **D3** the handle is an ADDRESS, not a door — `GET /profiles/:handle` joins `accountVisibleSql` and 404s for the very account whose sliver was just read (asserted, with a 200 control), and a PREVIOUS handle resolves to nothing here unlike the public page’s 301, because a recruiting read has no shared links to preserve and a redirect would confirm a rename. **D4** one route, `GET /scouting/:handle`, beside the directory in `routes/scouting.js` but deliberately NOT sharing its gate: **no `project.recruit` atom is minted** (ADR 0201 D3 and ADR 0210 both reserve it for the console wave that defines it), and until it exists the sliver holds spec D1’s declared floor for that atom — `api.requireRank('archon')`. The first draft reused the directory’s door and that was WIDER than the owner-signed floor on both halves (`project.curate` at metic, OR any project owner — typically a bare xenos), on the first surface that discloses a PRIVATE account; narrowing after people are disclosed is not recoverable, widening later is. The audience question is the owner’s and is FILED, not taken — blocker 1000126, with the C5 tension it carries and a one-line swap either way. 404 never 403 (a 403 confirms the handle), `no-store` (ADR 0171 D5). **D5** it grants nothing (ADR 0016): the module is required by exactly ONE first-party server file — the route that serializes it — enumerated from source, and no rank/role/permission/membership travels with it. **D6** the Settings copy widens back from “the People directory” (narrowed by task 1002290 while this was unbuilt) to name **both** recruiting surfaces — “both” and not “every”, because a guard asserts `routes/scouting.js` mounts exactly two routes and that both readers compose `recruiterReachSql`. The half that was actually WRONG was the grant itself: the copy promised a hidden account “your name and nothing else” while the sliver also ships the AVATAR, never named the picture in the shown branch, and never told a private account this is the one thing a recruiter sees while their page answers “not found”. The guard now iterates SLIVER_KEYS from the module instead of checking transcribed phrases, so a sixth granted field cannot ship undescribed. **D7** no migration; the port lives beside `getPublicProfileExtras` in the MODULE, not the file, which is on the 1500-line ratchet. The proof is a whitelist over the module’s own exported key list, planted improbable sentinels asserted absent from the whole body with controls against vacuity, and a fake pool that reads boolean STRUCTURE — the house interpreter collects terms and ANDs them, so rewriting this predicate’s `AND` to `OR` (which in Postgres serves EVERY active account) passed all three test files green until each term was made to be CONSUMED and the residue required to be pure conjunction. That hole is still open in five sibling proofs (idea 1000877). Four further defects came out of the same review: the credits/karma sentinels sat on rows the sliver never reads, a “control” compared a fixture to itself, and the one-reader pin missed the predicate’s JS twin. ⚠ Honest limits: **no UI consumes it yet** (the recruiting view is the console wave’s and task 1002972’s); **the audience is UNRESOLVED** (blocker 1000126) and the archon floor is narrower than goal C5 describes, so project owners cannot use the sliver today; the two-route guard counts only `routes/scouting.js`, so a recruiting read added elsewhere would not trip it; the route is unmetered (idea 1000876); and `active_recently` understates for a builder whose only project never reports a rollup. Rejected: flooring on `accountVisibleSql`; folding reach into `rollupVisibleSql`; minting `project.recruit`; **reusing the directory’s door**; leaving a stale `recruiter_discoverable = true` to read as consent after an account goes private; a second route file; null-instead-of-absent rollup keys; a profile URL / `github_login` / a project count / a raw `last_active`; 301-ing a previous handle; 403 for a non-discoverable builder.](<redacted>.md) | platform identity / privacy |
303
304
  | 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 |
304
305
 
305
306
  > ⚠️ **Numbering collisions are now machine-enforced, not narrated.** Twenty numbers are shared by two ADRs each — assigned in parallel sessions before anything checked. The files keep their filenames (renumbering would break every existing citation, and a number once assigned is never reused), and this table disambiguates each pair as `NNNN-a` / `NNNN-b`. The authoritative list is `LEGACY_DUPLICATE_ADRS` in [`scripts/gds/adr-namespace.js`](../../scripts/gds/adr-namespace.js), frozen by exact filename: a **new** collision — or a third file joining a legacy number, or a rename of either half — hard-fails the `unit` CI gate, as does an index row that points at no file or an ADR with no row. The list may only shrink. This paragraph used to narrate the collisions one by one and had fallen eight behind reality; see [ADR 0195](<redacted>.md) for why the check exists and why the pairs are grandfathered rather than renumbered.
@@ -12089,6 +12089,58 @@
12089
12089
  ]
12090
12090
  }
12091
12091
  },
12092
+ "/scouting/{handle}": {
12093
+ "get": {
12094
+ "operationId": "get_scouting_handle",
12095
+ "tags": [
12096
+ "scouting"
12097
+ ],
12098
+ "summary": "GET /scouting/:handle",
12099
+ "description": "the PRE-ACCEPT SLIVER for ONE builder (privacy spec D3, ADR 0215 D4, task 1002968). This is the FIRST surface on the platform that discloses a PRIVATE account, and its audience is deliberately NOT the directory's. WHY A RANK GATE AND NOT THE RECRUITING DOOR ABOVE. Spec D3 gates this read on `project.recruit`, an atom whose declared shape is FLOOR ARCHON, widenable per instance (spec D1). That atom does not exist — ADR 0201 D3 and ADR 0210 both refuse to mint it outside the project-admin-console wave that defines it (spike 1002786 R3), and this task does not either. The directory's door (`project.curate` at metic, OR any project owner — ADR 0210 D2) is WIDER than the signed floor on both halves, so adopting it here would widen an owner-signed privacy decision by implementation convenience. Until the atom lands, the honest reading of D3 is its floor, held by rank. Whether the wider recruiting audience is authorized for this surface is the owner's call and is filed as a blocker, NOT resolved here; when it is answered, this one line moves and nothing else does. 404, never 403, for a builder who is not recruiter-reachable: a 403 would confirm the handle exists, which is the disclosure the opt-out removes. The sliver's own predicate does the deciding, inside the module.\n\n**Rank:** `archon` — Archon only (rank and identity management + the escalation keys — the trust boundary).",
12100
+ "x-rank": "archon",
12101
+ "x-source": "modules/platform-identity/routes/scouting.js",
12102
+ "parameters": [
12103
+ {
12104
+ "name": "handle",
12105
+ "in": "path",
12106
+ "required": true,
12107
+ "schema": {
12108
+ "type": "string"
12109
+ },
12110
+ "description": "Path parameter `handle`."
12111
+ }
12112
+ ],
12113
+ "responses": {
12114
+ "200": {
12115
+ "description": "Success.",
12116
+ "content": {
12117
+ "application/json": {
12118
+ "schema": {
12119
+ "$ref": "#/components/schemas/GetScoutingHandleResponse"
12120
+ }
12121
+ }
12122
+ }
12123
+ },
12124
+ "400": {
12125
+ "$ref": "#/components/responses/BadRequest"
12126
+ },
12127
+ "401": {
12128
+ "$ref": "#/components/responses/Unauthorized"
12129
+ },
12130
+ "403": {
12131
+ "$ref": "#/components/responses/Forbidden"
12132
+ },
12133
+ "404": {
12134
+ "$ref": "#/components/responses/NotFound"
12135
+ }
12136
+ },
12137
+ "security": [
12138
+ {
12139
+ "builderSession": []
12140
+ }
12141
+ ]
12142
+ }
12143
+ },
12092
12144
  "/search": {
12093
12145
  "post": {
12094
12146
  "operationId": "post_search",
@@ -16804,6 +16856,15 @@
16804
16856
  "page"
16805
16857
  ]
16806
16858
  },
16859
+ "GetScoutingHandleResponse": {
16860
+ "type": "object",
16861
+ "properties": {
16862
+ "sliver": {}
16863
+ },
16864
+ "required": [
16865
+ "sliver"
16866
+ ]
16867
+ },
16807
16868
  "GetScoutingResponse": {
16808
16869
  "type": "object",
16809
16870
  "properties": {
@@ -21376,9 +21437,9 @@
21376
21437
  "description": "A required dependency/feature is not configured or is temporarily down."
21377
21438
  }
21378
21439
  },
21379
- "x-endpoint-count": 338,
21380
- "x-schema-count": 374,
21440
+ "x-endpoint-count": 339,
21441
+ "x-schema-count": 375,
21381
21442
  "x-undocumented-bodies": 9,
21382
- "x-response-schemas": 254,
21443
+ "x-response-schemas": 255,
21383
21444
  "x-generated-by": "scripts/gds/gen-api-docs.js"
21384
21445
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Bongos API reference
4
4
 
5
- > **Generated from the live route files** — the route file is authoritative. 338 endpoints across 54 route files.
5
+ > **Generated from the live route files** — the route file is authoritative. 339 endpoints across 54 route files.
6
6
  > Machine-readable spec: [`docs/api/openapi.json`](api/openapi.json) (OpenAPI 3.1). Rendered docs site: **`/docs`** (e.g. `cloudbongos.com/docs`).
7
7
 
8
8
  Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>.md)): `public` < `any-builder` < `metic+archon` < `archon`.
@@ -484,11 +484,12 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
484
484
  | GET | `/api/bongos/public/uptime` | `public` | — | Uptime: current status + 90-day history bucketed by hour. |
485
485
  | GET | `/api/bongos/public/versions` | `public` | — | read-only surface for the status dashboard (status.<apex>). |
486
486
 
487
- ## `scouting` (1)
487
+ ## `scouting` (2)
488
488
 
489
489
  | Method | Path | Rank | Body | Description |
490
490
  |---|---|---|---|---|
491
491
  | GET | `/api/bongos/scouting` | `metic+archon` | — | the internal talent directory across every project. |
492
+ | GET | `/api/bongos/scouting/:handle` | `archon` | — | the PRE-ACCEPT SLIVER for ONE builder (privacy spec D3, ADR 0215 D4, task 1002968). |
492
493
 
493
494
  ## `search` (2)
494
495