@bongos/core 1.19.430 → 1.19.432

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.bongos-core.json +64 -44
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +2 -0
  4. package/clients/bongos-client/index.cjs +2 -0
  5. package/clients/bongos-client/index.d.ts +3 -0
  6. package/clients/bongos-client/index.mjs +2 -0
  7. package/docs/adr/0215-the-recruiter-sliver-is-a-ceiling-not-a-step.md +116 -0
  8. package/docs/adr/0216-a-fake-pool-interpreter-must-fail-loudly-not-silently-ignore.md +102 -0
  9. package/docs/adr/README.md +2 -0
  10. package/docs/api/openapi.json +64 -3
  11. package/docs/api-reference.md +3 -2
  12. package/docs/copy-inventory.md +48 -48
  13. package/docs/copy-registry.json +57 -57
  14. package/docs/file-map.md +1 -0
  15. package/docs/module-api-changelog.md +4 -0
  16. package/modules/dev-box/app/src/vendor/bongos-client.cjs +2 -0
  17. package/modules/hall-ui/public/settings.html +1 -1
  18. package/modules/hall-ui/public/settings.js +17 -8
  19. package/modules/platform-identity/account-visibility.js +47 -0
  20. package/modules/platform-identity/platform-identity.js +4 -4
  21. package/modules/platform-identity/recruiter-sliver.js +197 -0
  22. package/modules/platform-identity/routes/scouting.js +58 -0
  23. package/package-lock.json +2 -2
  24. package/package.json +1 -1
  25. package/src/module-api.js +1 -1
  26. package/tests/account_privacy_flags.mjs +115 -9
  27. package/tests/application_echo.mjs +43 -9
  28. package/tests/application_lifecycle.mjs +18 -7
  29. package/tests/connections_ownership.mjs +44 -4
  30. package/tests/effective_visibility_predicate.mjs +137 -50
  31. package/tests/government_require_permission.mjs +22 -0
  32. package/tests/handle_machinery.mjs +15 -1
  33. package/tests/hub_profile_leak.mjs +18 -4
  34. package/tests/own_session_revoke.mjs +77 -9
  35. package/tests/profile_connections_ui.mjs +49 -10
  36. package/tests/profile_privacy_owner_view.mjs +68 -37
  37. package/tests/profile_rollup_consent.mjs +46 -14
  38. package/tests/profile_route.mjs +6 -0
  39. package/tests/recruiter_sliver_boundary.mjs +841 -0
  40. package/tests/scouting_owner_access_and_reach.mjs +175 -39
  41. package/tests/scouting_rollup_fanout.mjs +15 -4
@@ -0,0 +1,841 @@
1
+ // tests/recruiter_sliver_boundary.mjs — THE PRE-ACCEPT BOUNDARY PROOF
2
+ // (task 1002968, goal 1000045; privacy spec D3, ADR 0215, ADR 0171 D2/D4/D5,
3
+ // ADR 0016).
4
+ //
5
+ // The sliver is a CONSENT BOUNDARY, so the thing worth proving is not that it
6
+ // works — it is that it stops. Four claims, in the order that would hurt most:
7
+ //
8
+ // 1. IT CARRIES EXACTLY FIVE KEYS, AND NOTHING ELSE. Asserted as a WHITELIST:
9
+ // the response's own key set is compared to the module's exported
10
+ // SLIVER_KEYS, so a sixth key fails whether or not this file was told about
11
+ // it. A blacklist ("no credits key") passes forever against the field
12
+ // nobody thought to name.
13
+ //
14
+ // 2. NOTHING RIDES ALONG IN THE TEXT. Every value the sliver must not carry is
15
+ // an IMPROBABLE PLANTED SENTINEL, and the assertion is that none appears
16
+ // ANYWHERE in the raw response body — the task 1002806 technique. A regex
17
+ // like /"credits":null/ is satisfied by a stray sibling key and proves
18
+ // nothing; absence from the whole body is the claim.
19
+ //
20
+ // THE SENTINELS SIT ON THE ROWS THE SLIVER'S OWN QUERIES RETURN, and that
21
+ // is load-bearing rather than incidental. In the first draft the numbers
22
+ // lived on rows those queries never touched, so "credits never leak" — the
23
+ // first thing spec D3 forbids — could not have failed under any projection
24
+ // change. The fake now returns MORE columns than each statement selects,
25
+ // exactly as a real row would if a query grew, so a row spread anywhere in
26
+ // the projection leaks a number and reds this file.
27
+ //
28
+ // CONTROLS, so absence cannot pass vacuously: the same values DO reach a
29
+ // recruiter through the directory for a builder who consented to it, and
30
+ // the SAME hidden account, with hide_stats flipped, DOES get its
31
+ // disciplines from the real port. (The control that used to stand here ran
32
+ // SQL this file wrote against this file's own fake and compared a fixture
33
+ // to itself — it proved that an object equals itself.)
34
+ //
35
+ // ONE HONEST EXCEPTION, asserted rather than glossed: `github_id` is NOT
36
+ // absent from the body. The avatar spec D3 grants is GitHub's own URL and
37
+ // embeds the id verbatim, so the proof pins where it may appear — inside
38
+ // that URL and nowhere else — instead of claiming a property the code does
39
+ // not have.
40
+ //
41
+ // 3. THE BOUNDARY IS A CEILING, NOT A STEP. A recruiter who holds a private
42
+ // builder's sliver holds their HANDLE — and the handle does not open the
43
+ // profile page: GET /profiles/:handle joins accountVisibleSql and answers
44
+ // 404 for exactly that account, while answering 200 for a public one. That
45
+ // is what "pre-accept" means operationally, and it is the assertion that
46
+ // would catch someone "helpfully" relaxing resolveHandle later.
47
+ //
48
+ // 4. IT GRANTS NOTHING (ADR 0016). Structurally: the sliver module is required
49
+ // by exactly one first-party server file, the route that serializes it —
50
+ // nothing consults it to decide anything. And the response carries no rank,
51
+ // role, permission or membership under any name.
52
+ //
53
+ // PLUS the gate, because a bounded projection behind an open door is not
54
+ // bounded. The sliver is ARCHON-gated and the DIRECTORY's audience is not the
55
+ // sliver's (ADR 0215 D4): spec D3 pins this read to an archon-floor atom that has
56
+ // not been minted, and the directory's door — `project.curate` at metic OR any
57
+ // project owner — is wider on both halves. A metic operator and a project owner
58
+ // both read the directory and are both refused here, which is asserted in both
59
+ // directions so a later "tidy-up" onto one shared gate cannot pass quietly.
60
+ //
61
+ // MECHANISM (tests/profile_privacy_owner_view.mjs + tests/connections_ownership.mjs):
62
+ // in-memory tables whose row filter is built FROM EACH STATEMENT'S OWN WHERE
63
+ // CLAUSE, refusing any statement whose placeholders or literal comparisons it did
64
+ // not consume — so a predicate this interpreter cannot read is a loud error and
65
+ // never a silently absent filter. Above the pool everything is real: the real
66
+ // router, the real module, real HTTP.
67
+ //
68
+ // AND IT READS BOOLEAN STRUCTURE, NOT ONLY TERMS. The house version of this
69
+ // interpreter collects the terms it recognises and ANDs them, which re-imposes a
70
+ // conjunction the SQL may no longer contain: rewriting the sliver predicate's
71
+ // `AND` to `OR` would serve every active account on the platform, and the first
72
+ // draft of this file stayed green through exactly that. Each term is now CONSUMED
73
+ // from the WHERE text as it is matched and the residue must be pure conjunction.
74
+ // (The same hole is still open in five sibling proofs — idea 1000877.)
75
+ //
76
+ // DB-free by construction. The last test proves this file runs in the lane that
77
+ // actually executes (learning 1000218: three files declared INTEGRATION in their
78
+ // headers, sat in NEITHER lane, and asserted nothing for months).
79
+ //
80
+ // Run: node tests/recruiter_sliver_boundary.mjs
81
+
82
+ import { strict as assert } from 'node:assert';
83
+ import { test, after } from 'node:test';
84
+ import { createRequire } from 'node:module';
85
+ import http from 'node:http';
86
+ import fs from 'node:fs';
87
+ import path from 'node:path';
88
+ import { fileURLToPath } from 'node:url';
89
+
90
+ // ESM so knip credits the module's test-only exports (a createRequire import
91
+ // reads as unused — the CJS blind spot).
92
+ import {
93
+ recruiterSliverVisibleSql,
94
+ isRecruiterSliverVisible,
95
+ accountActiveSql,
96
+ recruiterReachSql,
97
+ accountVisibleSql,
98
+ } from '../modules/platform-identity/account-visibility.js';
99
+
100
+ // The sliver module's own exports, imported as ESM for the same reason — and
101
+ // SLIVER_KEYS is why: the whitelist assertions compare a response's key set to
102
+ // the module's list, never to a copy of it kept here. A copy is the failure mode
103
+ // a whitelist exists to remove (add a key to both and the proof still passes).
104
+ // Loading it here also loads the real doorway; that is harmless because every
105
+ // call below passes its pool explicitly, and the require.cache stub installed
106
+ // further down still reaches the ROUTE files, which are required after it.
107
+ import {
108
+ SLIVER_KEYS,
109
+ SLIVER_KEYS_HIDDEN,
110
+ ACTIVE_WINDOW_DAYS,
111
+ getRecruiterSliver,
112
+ getRecruiterSliverByHandle,
113
+ } from '../modules/platform-identity/recruiter-sliver.js';
114
+
115
+ const require = createRequire(import.meta.url);
116
+ const express = require('express');
117
+ const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
118
+
119
+ // ===========================================================================
120
+ // The fixture — one account per row of the privacy-spec D2 matrix
121
+ // ===========================================================================
122
+ //
123
+ // Sentinels are per-account improbable literals, so "absent from the body" means
124
+ // absent rather than "did not collide with a 0". `github_login` is deliberately
125
+ // NOT the handle: D3 names the handle and not the login, so the login is itself
126
+ // a sentinel that must never cross.
127
+
128
+ const DIR = '3110001'; // active · public · shown — the CONTROL: the directory carries them
129
+ const PRIV = '3110002'; // active · PRIVATE · reach=true — THE D3 SUBJECT
130
+ const OPTOUT = '3110003'; // active · public · reach=FALSE — opted out of recruiting
131
+ const SHY = '3110004'; // active · public · HIDDEN — the name-only sliver
132
+ const PROV = '3110005'; // PROVISIONAL · reach=true — never recruiter-visible
133
+ const PRIVDEF = '3110006'; // active · PRIVATE · reach=null — private, never opted in
134
+
135
+ // Every account column the tables hold. The queries select a SUBSET; the extra
136
+ // columns are here on purpose, so a row spread leaks something loud.
137
+ const ACCOUNTS = [
138
+ { github_id: DIR, handle: 'dir-handle', github_login: 'dir-login-990101', display_name: 'Dir Person',
139
+ avatar_url: `https://avatars.githubusercontent.com/u/${DIR}?v=4`, previous_handle: null, bio: 'dir-bio-990103',
140
+ links: null, created_at: 'T0', profile_state: 'active', account_visibility: 'public',
141
+ hide_stats: false, recruiter_discoverable: null, terms_accepted_at: 'T1' },
142
+ { github_id: PRIV, handle: 'priv-handle', github_login: 'priv-login-990201', display_name: 'Priv Person',
143
+ avatar_url: `https://avatars.githubusercontent.com/u/${PRIV}?v=4`, previous_handle: 'priv-oldname-990204', bio: 'priv-bio-990203',
144
+ links: null, created_at: 'T0', profile_state: 'active', account_visibility: 'private',
145
+ hide_stats: false, recruiter_discoverable: true, terms_accepted_at: 'T1' },
146
+ { github_id: OPTOUT, handle: 'optout-handle', github_login: 'optout-login-990301', display_name: 'Optout Person',
147
+ avatar_url: `https://avatars.githubusercontent.com/u/${OPTOUT}?v=4`, previous_handle: null, bio: null,
148
+ links: null, created_at: 'T0', profile_state: 'active', account_visibility: 'public',
149
+ hide_stats: false, recruiter_discoverable: false, terms_accepted_at: 'T1' },
150
+ { github_id: SHY, handle: 'shy-handle', github_login: 'shy-login-990401', display_name: 'Shy Person',
151
+ avatar_url: `https://avatars.githubusercontent.com/u/${SHY}?v=4`, previous_handle: null, bio: null,
152
+ links: null, created_at: 'T0', profile_state: 'active', account_visibility: 'public',
153
+ hide_stats: true, recruiter_discoverable: null, terms_accepted_at: 'T1' },
154
+ { github_id: PROV, handle: 'prov-handle', github_login: 'prov-login-990501', display_name: 'Prov Person',
155
+ avatar_url: `https://avatars.githubusercontent.com/u/${PROV}?v=4`, previous_handle: null, bio: null,
156
+ links: null, created_at: 'T0', profile_state: 'provisional', account_visibility: 'public',
157
+ hide_stats: false, recruiter_discoverable: true, terms_accepted_at: 'T1' },
158
+ { github_id: PRIVDEF, handle: 'privdef-handle', github_login: 'privdef-login-990601', display_name: 'Privdef Person',
159
+ avatar_url: `https://avatars.githubusercontent.com/u/${PRIVDEF}?v=4`, previous_handle: null, bio: null,
160
+ links: null, created_at: 'T0', profile_state: 'active', account_visibility: 'private',
161
+ hide_stats: false, recruiter_discoverable: null, terms_accepted_at: 'T1' },
162
+ ];
163
+
164
+ // The cross-project record behind each account. `disciplines` is the ONE
165
+ // rollup-derived value the sliver may carry, so it is not a sentinel for a shown
166
+ // account — but it IS one for the hidden account, whose sliver must be name-only.
167
+ const ROLLUP = {
168
+ [DIR]: { credits: 880001, works_shipped: 880002, karma: 880003, project_count: 880004,
169
+ last_active: 'dir-lastactive-880005', disciplines: ['dir-craft'],
170
+ projects: [{ name: 'Dir-Project-880006', origin: 'https://dir.example', rank_held: 'metic', recent_ships: ['Dir-Ship-880007'], is_public: true }] },
171
+ [PRIV]: { credits: 881001, works_shipped: 881002, karma: 881003, project_count: 881004,
172
+ last_active: 'priv-lastactive-881005', disciplines: ['priv-craft'],
173
+ projects: [{ name: 'Priv-Project-881006', origin: 'https://priv.example', rank_held: 'archon', recent_ships: ['Priv-Ship-881007'], is_public: true }] },
174
+ [OPTOUT]: { credits: 882001, works_shipped: 882002, karma: 882003, project_count: 882004,
175
+ last_active: 'optout-lastactive-882005', disciplines: ['optout-craft'],
176
+ projects: [{ name: 'Optout-Project-882006', origin: 'https://optout.example', rank_held: 'metic', recent_ships: ['Optout-Ship-882007'], is_public: true }] },
177
+ [SHY]: { credits: 883001, works_shipped: 883002, karma: 883003, project_count: 883004,
178
+ last_active: 'shy-lastactive-883005', disciplines: ['shy-craft-883008'],
179
+ projects: [{ name: 'Shy-Project-883006', origin: 'https://shy.example', rank_held: 'metic', recent_ships: ['Shy-Ship-883007'], is_public: true }] },
180
+ [PROV]: { credits: 884001, works_shipped: 884002, karma: 884003, project_count: 884004,
181
+ last_active: 'prov-lastactive-884005', disciplines: ['prov-craft'],
182
+ projects: [] },
183
+ [PRIVDEF]: { credits: 885001, works_shipped: 885002, karma: 885003, project_count: 885004,
184
+ last_active: 'privdef-lastactive-885005', disciplines: ['privdef-craft'],
185
+ projects: [] },
186
+ };
187
+
188
+ // Whether each fixture is "active in the last 30 days" — the coarse boolean the
189
+ // SQL derives. Modelled here because the comparison happens in the database.
190
+ const ACTIVE_RECENTLY = { [DIR]: true, [PRIV]: true, [OPTOUT]: true, [SHY]: true, [PROV]: false, [PRIVDEF]: false };
191
+
192
+ /**
193
+ * Everything about one account that the SLIVER must never disclose, as strings.
194
+ * The account's own `disciplines` are excluded for a shown account (they are
195
+ * granted) and added back by the caller for the hidden one.
196
+ */
197
+ function forbiddenOf(githubId) {
198
+ const a = ACCOUNTS.find((x) => x.github_id === githubId);
199
+ const r = ROLLUP[githubId];
200
+ return [
201
+ // NOT githubId: the avatar D3 grants is GitHub's own URL and embeds the
202
+ // account id verbatim, so the id is DISCLOSED by an authorized field. That is
203
+ // asserted precisely below instead of pretended away here (ADR 0215).
204
+ a.github_login, // D3 names the handle, not the login
205
+ a.bio, // on the account row the sliver reads, and never granted
206
+ String(r.credits), String(r.works_shipped), String(r.karma), String(r.project_count),
207
+ r.last_active, // a timestamp is a trace; only the boolean crosses
208
+ ...(a.previous_handle ? [a.previous_handle] : []), // a rename is itself a disclosure
209
+ ...r.projects.flatMap((p) => [p.name, p.origin, p.rank_held, ...p.recent_ships]),
210
+ ];
211
+ }
212
+
213
+ // ===========================================================================
214
+ // The in-memory tables + their statement interpreter
215
+ // ===========================================================================
216
+
217
+ const KNOWN_COLUMNS = new Set(['profile_state', 'account_visibility', 'hide_stats', 'github_id', 'handle', 'previous_handle', 'recruiter_discoverable']);
218
+
219
+ /**
220
+ * Build a row test from a statement's own WHERE text. Literal comparisons carry
221
+ * as much weight as placeholders: the visibility predicates are made ENTIRELY of
222
+ * literal comparisons, so a fake modelling only $N filters would score every
223
+ * privacy term as absent and pass however the code changed.
224
+ *
225
+ * IT READS STRUCTURE, NOT JUST TERMS — and that is the whole reason this
226
+ * function is written as a consuming parse rather than a list of `.test()`
227
+ * calls. An interpreter that merely COLLECTS the terms it recognises and ANDs
228
+ * them re-imposes an `AND` the SQL may no longer contain: rewrite
229
+ * recruiterSliverVisibleSql's `AND` to `OR` and Postgres would serve the sliver
230
+ * for every active account on the platform, while a term-collecting fake keeps
231
+ * answering exactly as before and every assertion in this file stays green. So
232
+ * each recognised term is REMOVED from the text as it is matched, and whatever
233
+ * survives must be nothing but whitespace, parentheses and `AND`. An `OR`, a
234
+ * `NOT`, a `CASE` — anything this interpreter cannot faithfully model — is a
235
+ * loud error, on the same principle as the unread-placeholder guard below.
236
+ * (Found by adversarial review; the mutation is pinned in the calibration
237
+ * tests, and again on the real predicate.)
238
+ */
239
+ function accountFilter(where, params) {
240
+ const tests = [];
241
+ const consumed = new Set();
242
+ let residue = where;
243
+
244
+ // Match a term, model it, and CONSUME its text so the leftovers can be judged.
245
+ const take = (re, make) => {
246
+ const m = re.exec(residue);
247
+ if (!m) return null;
248
+ residue = residue.slice(0, m.index) + residue.slice(m.index + m[0].length);
249
+ tests.push(make(m));
250
+ return m;
251
+ };
252
+
253
+ // The reach axis FIRST, because the COALESCE spells `account_visibility =
254
+ // 'public'` inside itself as its own fallback — consuming it first is what
255
+ // stops that fallback being scored as an existence check, which would filter
256
+ // out the private account the sliver exists to serve. The tri-state (null
257
+ // follows visibility, an explicit boolean overrides) is modelled exactly as
258
+ // recruiterReachSql spells it.
259
+ take(
260
+ /\bCOALESCE\(\s*(?:[a-z_]+\.)?recruiter_discoverable\s*,\s*(?:[a-z_]+\.)?account_visibility = 'public'\s*\)/,
261
+ () => (r) => (typeof r.recruiter_discoverable === 'boolean'
262
+ ? r.recruiter_discoverable
263
+ : r.account_visibility === 'public'),
264
+ );
265
+ take(/\b(?:[a-z_]+\.)?profile_state = 'active'/, () => (r) => r.profile_state === 'active');
266
+ // The T&C half of the CONSENT floor (task 1002913, ADR 0213). accountActiveSql
267
+ // grew it while this branch was in flight and the sliver predicate composes
268
+ // that helper, so the sliver inherited the tightening without a line changing
269
+ // here — and this interpreter refused the statement outright until it could
270
+ // model the new term, which is the structure guard doing its job.
271
+ take(/\b(?:[a-z_]+\.)?terms_accepted_at IS NOT NULL/, () => (r) => r.terms_accepted_at != null);
272
+ take(/\b(?:[a-z_]+\.)?account_visibility = 'public'/, () => (r) => r.account_visibility === 'public');
273
+ take(/\bNOT (?:[a-z_]+\.)?hide_stats\b/, () => (r) => !r.hide_stats);
274
+
275
+ take(/\b(?:[a-z_]+\.)?github_id = \$(\d+)/, (m) => {
276
+ consumed.add(m[1]);
277
+ const v = params[Number(m[1]) - 1];
278
+ return (r) => String(r.github_id) === String(v);
279
+ });
280
+
281
+ // The handle disjunction is the ONE legitimate OR in this repo's account
282
+ // reads (resolveHandle's rename window), so it is consumed as a single unit
283
+ // — leaving a bare `OR` behind would trip the structure check below.
284
+ const bothHandles = take(
285
+ /lower\(handle\) = \$(\d+) OR lower\(previous_handle\) = \$(\d+)/,
286
+ (m) => {
287
+ consumed.add(m[1]); consumed.add(m[2]);
288
+ const v = String(params[Number(m[1]) - 1]);
289
+ return (r) => String(r.handle || '').toLowerCase() === v
290
+ || String(r.previous_handle || '').toLowerCase() === v;
291
+ },
292
+ );
293
+ if (!bothHandles) {
294
+ take(/lower\(handle\) = \$(\d+)/, (m) => {
295
+ consumed.add(m[1]);
296
+ const v = String(params[Number(m[1]) - 1]);
297
+ return (r) => String(r.handle || '').toLowerCase() === v;
298
+ });
299
+ }
300
+
301
+ // The two TERM guards run before the STRUCTURE guard, so an unreadable term
302
+ // is reported as the unreadable term it is rather than as leftover text.
303
+ for (const m of where.matchAll(/\$(\d+)/g)) {
304
+ if (!consumed.has(m[1])) {
305
+ throw new Error(`fake pool: unread predicate on $${m[1]} in "${where}" — a filter this interpreter cannot read must never degrade to no filter`);
306
+ }
307
+ }
308
+ for (const m of where.matchAll(/\b([a-z_]+)\s*(?:=|<>|!=)\s*'/g)) {
309
+ if (!KNOWN_COLUMNS.has(m[1])) {
310
+ throw new Error(`fake pool: unrecognised literal comparison on ${m[1]} in "${where}"`);
311
+ }
312
+ }
313
+ // What is left must be a pure conjunction. Anything else and this interpreter
314
+ // would be inventing a predicate the database was never given.
315
+ if (!/^[\s()]*(?:AND[\s()]*)*$/.test(residue)) {
316
+ throw new Error(`fake pool: unmodelled boolean structure "${residue.trim()}" in "${where}" — every recognised term is ANDed here, so a predicate that is not a pure conjunction must be a loud error, never a silently different filter`);
317
+ }
318
+ return (r) => tests.every((fn) => fn(r));
319
+ }
320
+
321
+ /** The github_id an activity statement is scoped to, read from its own WHERE. */
322
+ function activityScope(sql, params) {
323
+ const m = /\ba\.github_id = \$(\d+)/.exec(sql);
324
+ if (!m) throw new Error(`fake pool: an activity read with no github_id scope — ${sql.slice(0, 120)}`);
325
+ return String(params[Number(m[1]) - 1]);
326
+ }
327
+
328
+ let calls = [];
329
+ const pool = {
330
+ query: async (text, params = []) => {
331
+ const sql = String(text).replace(/\s+/g, ' ').trim();
332
+ calls.push({ sql, params });
333
+
334
+ // The recruiting DIRECTORY: consent lives in an EXISTS subquery correlated to
335
+ // the activity table, so its predicate is lifted out and judged on its own.
336
+ if (/WITH agg AS/.test(sql) && /platform_identity_accounts pa/.test(sql)) {
337
+ const m = /pa\.github_id = a\.github_id AND ([\s\S]*?)\)\s*GROUP BY/.exec(sql);
338
+ if (!m) throw new Error(`fake pool: could not read the directory's consent predicate — ${sql.slice(0, 200)}`);
339
+ const rows = ACCOUNTS.filter(accountFilter(m[1], [])).map((a) => {
340
+ const r = ROLLUP[a.github_id];
341
+ return {
342
+ github_id: a.github_id, github_login: a.github_login, display_name: a.display_name,
343
+ avatar_url: a.avatar_url, project_count: r.project_count, total_credits: r.credits,
344
+ total_tasks_shipped: r.works_shipped, total_karma: r.karma, last_active: r.last_active,
345
+ disciplines: r.disciplines,
346
+ };
347
+ });
348
+ return { rows, rowCount: rows.length };
349
+ }
350
+
351
+ // The SLIVER's activity half: a boolean and a disciplines union, never a date.
352
+ if (/WITH act AS/.test(sql)) {
353
+ const r = ROLLUP[activityScope(sql, params)];
354
+ return {
355
+ rows: [{
356
+ active_recently: r ? ACTIVE_RECENTLY[activityScope(sql, params)] : null,
357
+ disciplines: r ? r.disciplines : null,
358
+ // NOT selected by the statement. Present because a real row grows
359
+ // columns, and a projection that spreads rather than whitelists would
360
+ // carry these straight into the response. Without them the sentinels
361
+ // for credits/karma/ships — the very fields D3 forbids — sit on rows
362
+ // the sliver's queries never touch, and "absent from the body" is
363
+ // unfalsifiable for exactly the values that matter most.
364
+ last_active: r ? r.last_active : null,
365
+ credits: r ? r.credits : null,
366
+ works_shipped: r ? r.works_shipped : null,
367
+ karma: r ? r.karma : null,
368
+ project_count: r ? r.project_count : null,
369
+ project_name: r ? (r.projects[0] || {}).name ?? null : null,
370
+ }],
371
+ rowCount: 1,
372
+ };
373
+ }
374
+
375
+ // The ownership door (scouting-authz): the REAL query, answered from the
376
+ // viewer's cast. Doubling the exported function instead would prove nothing —
377
+ // requireScoutingReader calls its module-local binding, so the export could be
378
+ // replaced with anything and the gate would never notice.
379
+ if (/platform_identity_project_memberships/.test(sql)) {
380
+ const [askedFor, kind, clientStatus] = params;
381
+ assert.equal(kind, 'owner', 'the ownership door asks only about owner memberships');
382
+ assert.equal(clientStatus, 'active', 'and only about projects still live on the platform (ADR 0210 D6)');
383
+ const owns = !!(viewer && viewer.owns && String(viewer.github_id) === String(askedFor));
384
+ return { rows: owns ? [{ membership_kind: 'owner', signin_verified_at: 'T0' }] : [], rowCount: owns ? 1 : 0 };
385
+ }
386
+
387
+ if (/FROM platform_identity_accounts/.test(sql)) {
388
+ const where = sql.includes(' WHERE ')
389
+ ? sql.slice(sql.indexOf(' WHERE ') + 7).replace(/ ORDER BY .*| LIMIT .*/g, '')
390
+ : '';
391
+ const cols = (/^SELECT (.+?) FROM /.exec(sql) || [, ''])[1];
392
+ // The rollup scalars ride the account row as well — the hidden account
393
+ // never reaches the activity query, so this is the only branch whose
394
+ // widening could leak a number for a name-only sliver.
395
+ let rows = ACCOUNTS.filter(accountFilter(where, params)).map((a) => ({ ...a, ...ROLLUP[a.github_id] }));
396
+ if (/is_current/.test(cols)) {
397
+ const asked = String(params[0]).toLowerCase();
398
+ rows = rows
399
+ .map((r) => ({ ...r, is_current: String(r.handle || '').toLowerCase() === asked }))
400
+ .sort((a, b) => Number(b.is_current) - Number(a.is_current))
401
+ .slice(0, 1);
402
+ }
403
+ return { rows: rows.map((r) => ({ ...r })), rowCount: rows.length };
404
+ }
405
+
406
+ // The per-builder rollup aggregate behind GET /profiles/:handle.
407
+ if (/WITH agg AS/.test(sql) && /platform_identity_builder_project_activity/.test(sql)) {
408
+ const r = ROLLUP[activityScope(sql, params)] || { credits: 0, works_shipped: 0, karma: 0, project_count: 0, last_active: null, disciplines: null };
409
+ return { rows: [{ projects: r.project_count, credits: r.credits, works_shipped: r.works_shipped, karma: r.karma, last_active: r.last_active, disciplines: r.disciplines }], rowCount: 1 };
410
+ }
411
+
412
+ if (/platform_identity_builder_project_activity/.test(sql)) {
413
+ const r = ROLLUP[activityScope(sql, params)];
414
+ const rows = r ? r.projects.map((p) => ({ ...p })) : [];
415
+ return { rows, rowCount: rows.length };
416
+ }
417
+
418
+ throw new Error(`fake pool: unsupported statement — ${sql.slice(0, 140)}`);
419
+ },
420
+ };
421
+
422
+ // ===========================================================================
423
+ // The real router over those tables
424
+ // ===========================================================================
425
+
426
+ // The viewer the stubbed session middleware presents, and what the two doors of
427
+ // the recruiting gate answer for them. `curate` is the permission atom; `owns` is
428
+ // the instance-asserted project ownership scouting-authz reads.
429
+ let viewer = null;
430
+
431
+ const moduleApiPath = require.resolve('../src/module-api.js');
432
+ const helpers = require('../src/bongos/routes/_helpers.js');
433
+ const DENIED_BODY = { ok: false, error: 'forbidden', required: ['project.curate'] };
434
+
435
+ require.cache[moduleApiPath] = {
436
+ id: moduleApiPath,
437
+ filename: moduleApiPath,
438
+ loaded: true,
439
+ exports: {
440
+ pool,
441
+ parsePagination: helpers.parsePagination,
442
+ corsPublicGet: () => (_req, _res, next) => next(),
443
+ logger: () => ({ error: () => {} }),
444
+ requireBuilder: (req, res, next) => {
445
+ if (!viewer) return res.fail('unauthenticated', 401);
446
+ req.builder = { github_id: viewer.github_id };
447
+ next();
448
+ },
449
+ // The REAL gates are core auth over a real session; standing them up here
450
+ // would drag the whole auth graph in. What must be proved is their OUTPUT —
451
+ // WHO each of the two recruiting reads admits — so each is doubled by a gate
452
+ // answering the shape core sends. The two are DIFFERENT on purpose: the
453
+ // directory's permission door, and the sliver's archon floor.
454
+ requirePermission: () => (req, res, next) => {
455
+ if (viewer && viewer.curate) return next();
456
+ return res.status(403).json(DENIED_BODY);
457
+ },
458
+ requireRank: (...allowed) => (req, res, next) => {
459
+ if (viewer && allowed.includes(viewer.rank)) return next();
460
+ return res.fail('rank_forbidden', 403, { required: [...allowed], actual: (viewer && viewer.rank) || null });
461
+ },
462
+ },
463
+ };
464
+
465
+ const buildScoutingRoutes = require('../modules/platform-identity/routes/scouting.js');
466
+ const buildPublicProfileRoutes = require('../modules/platform-identity/routes/public-profile.js');
467
+ const { attachFail } = require('../src/bongos/middleware/error-envelope.js');
468
+
469
+ const app = express();
470
+ app.use(express.json());
471
+ app.use(attachFail);
472
+ app.use('/api/bongos', buildScoutingRoutes());
473
+ app.use('/api/bongos', buildPublicProfileRoutes());
474
+ const server = app.listen(0, '127.0.0.1');
475
+ await new Promise((r) => server.once('listening', r));
476
+ const port = server.address().port;
477
+ after(() => server.close());
478
+
479
+ function get(path_) {
480
+ calls = [];
481
+ return new Promise((resolve, reject) => {
482
+ const req = http.request({ host: '127.0.0.1', port, method: 'GET', path: path_ }, (res) => {
483
+ const chunks = [];
484
+ res.on('data', (c) => chunks.push(c));
485
+ res.on('end', () => {
486
+ const text = Buffer.concat(chunks).toString('utf8');
487
+ let json = null; try { json = JSON.parse(text); } catch { /* not JSON */ }
488
+ resolve({ status: res.statusCode, text, json });
489
+ });
490
+ });
491
+ req.on('error', reject);
492
+ req.end();
493
+ });
494
+ }
495
+
496
+ // `rank` is the caller's rank ON THE HUB; `curate`/`owns` are the directory's two
497
+ // doors. The split is the point: a project owner is typically a bare xenos here
498
+ // and a metic operator holds `project.curate` without being an archon — so
499
+ // neither reaches the sliver, which is the whole of ADR 0215 D4.
500
+ const ARCHON = { github_id: '7000004', rank: 'archon', curate: false, owns: false };
501
+ const RECRUITER = { github_id: '7000001', rank: 'metic', curate: true, owns: false };
502
+ const OWNER = { github_id: '7000002', rank: 'xenos', curate: false, owns: true };
503
+ const STRANGER = { github_id: '7000003', rank: 'xenos', curate: false, owns: false };
504
+
505
+ const sliverOf = (handle) => get(`/api/bongos/scouting/${handle}`);
506
+ const handleOf = (githubId) => ACCOUNTS.find((a) => a.github_id === githubId).handle;
507
+
508
+ // ===========================================================================
509
+ // 0. The instrument is calibrated before it is trusted
510
+ // ===========================================================================
511
+
512
+ test('the interpreter refuses a predicate it cannot read, rather than ignoring it', () => {
513
+ assert.throws(() => accountFilter('github_id = $1 AND some_new_flag = $2', [DIR, true]), /unread predicate on \$2/);
514
+ assert.throws(() => accountFilter("github_id = $1 AND tier = 'gold'", [DIR]), /unrecognised literal comparison on tier/);
515
+ });
516
+
517
+ test('the interpreter really does model the reach tri-state, in both spellings', () => {
518
+ const bare = accountFilter(recruiterReachSql(), []);
519
+ const aliased = accountFilter(recruiterReachSql('pa'), []);
520
+ for (const f of [bare, aliased]) {
521
+ assert.equal(f({ account_visibility: 'public', recruiter_discoverable: null }), true, 'null follows a public visibility');
522
+ assert.equal(f({ account_visibility: 'private', recruiter_discoverable: null }), false, 'and follows a private one');
523
+ assert.equal(f({ account_visibility: 'private', recruiter_discoverable: true }), true, 'an explicit true overrides');
524
+ assert.equal(f({ account_visibility: 'public', recruiter_discoverable: false }), false, 'an explicit false overrides');
525
+ }
526
+ // ...and the reach COALESCE must not be mistaken for the EXISTENCE axis, which
527
+ // is spelled with the same two words inside it.
528
+ const reachOnly = accountFilter(recruiterReachSql(), []);
529
+ assert.equal(reachOnly({ account_visibility: 'private', recruiter_discoverable: true }), true,
530
+ 'a private, discoverable account passes reach — if the interpreter read the COALESCE as an existence check it would not');
531
+ });
532
+
533
+ // ===========================================================================
534
+ // 1. The predicate: consent AND reach, and deliberately NOT existence
535
+ // ===========================================================================
536
+
537
+ test('recruiterSliverVisibleSql is COMPOSED from the consent floor and the reach axis', () => {
538
+ assert.ok(recruiterSliverVisibleSql('pa').includes(accountActiveSql('pa')), 'the consent floor, literally contained');
539
+ assert.ok(recruiterSliverVisibleSql('pa').includes(recruiterReachSql('pa')), 'and the reach axis, literally contained');
540
+ assert.ok(!recruiterSliverVisibleSql('pa').includes(accountVisibleSql('pa')),
541
+ 'and NOT the existence axis: the private, discoverable account is the row D3 exists to serve');
542
+ assert.match(recruiterSliverVisibleSql(), /profile_state = 'active'/, 'a provisional account is never recruiter-visible');
543
+ assert.doesNotMatch(recruiterSliverVisibleSql(), /hide_stats/, 'hide changes the SHAPE of the sliver, never who appears in it');
544
+ assert.throws(() => recruiterSliverVisibleSql("pa; DROP TABLE x --"), /bare SQL identifier/);
545
+ });
546
+
547
+ test('the JS twin answers the D2 matrix exactly as the SQL asks it', () => {
548
+ const rows = [
549
+ [{ terms_accepted_at: 'T1', profile_state: 'active', account_visibility: 'private', recruiter_discoverable: true }, true],
550
+ [{ terms_accepted_at: 'T1', profile_state: 'active', account_visibility: 'private', recruiter_discoverable: null }, false],
551
+ [{ terms_accepted_at: 'T1', profile_state: 'active', account_visibility: 'public', recruiter_discoverable: null }, true],
552
+ [{ terms_accepted_at: 'T1', profile_state: 'active', account_visibility: 'public', recruiter_discoverable: false }, false],
553
+ [{ terms_accepted_at: 'T1', profile_state: 'provisional', account_visibility: 'public', recruiter_discoverable: true }, false],
554
+ ];
555
+ for (const [row, want] of rows) {
556
+ assert.equal(isRecruiterSliverVisible(row), want, JSON.stringify(row));
557
+ assert.equal(accountFilter(recruiterSliverVisibleSql(), [])(row), want, `the SQL fragment agrees for ${JSON.stringify(row)}`);
558
+ }
559
+ for (const bad of [null, undefined, {}, { profile_state: 'active' }]) {
560
+ assert.equal(isRecruiterSliverVisible(bad), false, 'fail-closed on an absent or unreadable account');
561
+ }
562
+ });
563
+
564
+ // ===========================================================================
565
+ // 2. The WHITELIST — exactly these keys, and nothing else
566
+ // ===========================================================================
567
+
568
+ test('a full sliver carries EXACTLY the five keys D3 names', async () => {
569
+ viewer = ARCHON;
570
+ const res = await sliverOf(handleOf(PRIV));
571
+ assert.equal(res.status, 200, 'a private, explicitly-discoverable account IS the sliver subject');
572
+ assert.deepEqual(Object.keys(res.json.sliver).sort(), [...SLIVER_KEYS].sort(),
573
+ 'the response key set is compared to the module\'s own list — a sixth key fails whether or not this file was told about it');
574
+ assert.deepEqual(Object.keys(res.json).sort(), ['sliver'], 'and nothing rides beside it at the top level');
575
+ assert.equal(res.json.sliver.handle, 'priv-handle');
576
+ assert.equal(res.json.sliver.display_name, 'Priv Person');
577
+ assert.deepEqual(res.json.sliver.disciplines, ['priv-craft']);
578
+ assert.equal(res.json.sliver.active_recently, true, 'the activity signal is a BOOLEAN, never a date');
579
+ assert.equal(ACTIVE_WINDOW_DAYS, 30, 'the window is the privacy parameter D3 names, not a tuning knob');
580
+ const activity = calls.find((c) => /WITH act AS/.test(c.sql));
581
+ assert.ok(activity.params.includes(ACTIVE_WINDOW_DAYS),
582
+ 'and it is BOUND to the statement, so the constant and the query cannot drift apart');
583
+ assert.match(activity.sql, /make_interval\(days => \$2::int\)/,
584
+ 'the window comparison happens in SQL — the timestamp never enters this process');
585
+ });
586
+
587
+ test('hide_stats shrinks the sliver to NAME-ONLY, and the activity read is never issued', async () => {
588
+ viewer = ARCHON;
589
+ const res = await sliverOf(handleOf(SHY));
590
+ assert.equal(res.status, 200, 'hiding the record does not remove the person from the recruiting surface');
591
+ assert.deepEqual(Object.keys(res.json.sliver).sort(), [...SLIVER_KEYS_HIDDEN].sort(),
592
+ 'the two rollup-derived keys are ABSENT, not null — there is no empty box where a number used to be');
593
+ assert.ok(!calls.some((c) => /platform_identity_builder_project_activity/.test(c.sql)),
594
+ 'and the numbers are never fetched at all: the cheapest way for a later edit to leak one is for it to already be in hand');
595
+ });
596
+
597
+ test('the port answers the same shapes as the route, keyed by github_id', async () => {
598
+ assert.deepEqual(Object.keys(await getRecruiterSliver(PRIV, { pool })).sort(),
599
+ [...SLIVER_KEYS].sort());
600
+ assert.deepEqual(Object.keys(await getRecruiterSliver(SHY, { pool })).sort(),
601
+ [...SLIVER_KEYS_HIDDEN].sort());
602
+ assert.equal(await getRecruiterSliver('system:bfg', { pool }), null,
603
+ 'a non-numeric principal answers null rather than throwing a bigint cast mid-request');
604
+ assert.equal(await getRecruiterSliverByHandle('NOT A HANDLE', { pool }), null,
605
+ 'and an unusable handle answers null without a query');
606
+ });
607
+
608
+ // ===========================================================================
609
+ // 3. LEAK BY SUBTRACTION — planted sentinels, absent from the whole body
610
+ // ===========================================================================
611
+
612
+ test('no forbidden value appears ANYWHERE in a full sliver response', async () => {
613
+ viewer = ARCHON;
614
+ const res = await sliverOf(handleOf(PRIV));
615
+ for (const sentinel of forbiddenOf(PRIV)) {
616
+ assert.ok(!res.text.includes(sentinel),
617
+ `"${sentinel}" leaked into the sliver body — the whitelist must be built literally, never spread from a row`);
618
+ }
619
+ });
620
+
621
+ test('github_id is never a FIELD — and it reaches a recruiter ONLY inside the avatar D3 grants', async () => {
622
+ // The honest version of a claim this file used to overstate. `github_id` was
623
+ // a plain sentinel asserted absent from the body, and it passed only because
624
+ // the fixture avatars were invented URLs. A real avatar is GitHub's own —
625
+ // https://avatars.githubusercontent.com/u/<github_id>?v=4 — so granting the
626
+ // avatar (spec D3 does, explicitly) grants the id inside it, and that id
627
+ // resolves through api.github.com to a login. Pretending otherwise in a
628
+ // comment does not make it false; asserting exactly where it may appear does.
629
+ viewer = ARCHON;
630
+ const res = await sliverOf(handleOf(PRIV));
631
+ for (const key of ['github_id', 'github_login', 'id', 'login']) {
632
+ assert.ok(!(key in res.json.sliver), `${key} must never be a KEY of the sliver`);
633
+ }
634
+ assert.ok(res.text.includes(PRIV),
635
+ 'CONTROL: the id really is in this body — otherwise the "only inside the avatar" claim below proves nothing');
636
+ assert.match(res.json.sliver.avatar_url, /^https:\/\/avatars\.githubusercontent\.com\/u\/\d+/,
637
+ 'the fixture must carry the SHAPE a real row carries, or this whole test is theatre');
638
+ const withoutAvatar = res.text.split(res.json.sliver.avatar_url).join('');
639
+ assert.ok(!withoutAvatar.includes(PRIV),
640
+ 'the id may ride the avatar and NOTHING else — a second appearance is a field that should not exist');
641
+ });
642
+
643
+ test('a hidden builder leaks neither their numbers NOR their disciplines', async () => {
644
+ viewer = ARCHON;
645
+ const res = await sliverOf(handleOf(SHY));
646
+ for (const sentinel of [...forbiddenOf(SHY), ...ROLLUP[SHY].disciplines]) {
647
+ assert.ok(!res.text.includes(sentinel), `"${sentinel}" leaked into a name-only sliver`);
648
+ }
649
+ });
650
+
651
+ test('CONTROL: those same values DO reach a recruiter for a builder who consented', async () => {
652
+ // Without this, "absent" could mean the sentinels were never in play at all and
653
+ // every assertion above would pass vacuously.
654
+ viewer = RECRUITER;
655
+ const dir = await get('/api/bongos/scouting');
656
+ assert.equal(dir.status, 200);
657
+ for (const sentinel of [String(ROLLUP[DIR].credits), String(ROLLUP[DIR].karma), String(ROLLUP[DIR].works_shipped), ROLLUP[DIR].last_active]) {
658
+ assert.ok(dir.text.includes(sentinel), `the directory must carry ${sentinel} — otherwise the absence tests prove nothing`);
659
+ }
660
+ });
661
+
662
+ test('CONTROL: the SAME builder, unhidden, DOES get those disciplines from the real port', async () => {
663
+ // The control this replaced ran a query THIS FILE wrote against its own fake
664
+ // and compared the fixture to itself — it never entered production code, so it
665
+ // proved only that an object equals itself. What has to be shown is that the
666
+ // name-only sliver is `hide_stats` doing its job and not an empty fixture, and
667
+ // the only honest way to show it is to flip that one flag on that one account
668
+ // and watch the REAL port hand the values over.
669
+ const shy = ACCOUNTS.find((a) => a.github_id === SHY);
670
+ shy.hide_stats = false;
671
+ try {
672
+ const full = await getRecruiterSliver(SHY, { pool });
673
+ assert.deepEqual(Object.keys(full).sort(), [...SLIVER_KEYS].sort(),
674
+ 'unhidden, the same account yields the FULL five-key sliver');
675
+ assert.deepEqual(full.disciplines, ROLLUP[SHY].disciplines,
676
+ 'and its disciplines really are reachable — so their absence above is the hide flag, not an empty fixture');
677
+ assert.equal(full.active_recently, true);
678
+ } finally {
679
+ shy.hide_stats = true;
680
+ }
681
+ });
682
+
683
+ // ===========================================================================
684
+ // 4. WHO APPEARS — the opt-out empties the recruiting surface, both halves
685
+ // ===========================================================================
686
+
687
+ test('an opted-out builder is absent from the recruiting surface ENTIRELY', async () => {
688
+ viewer = ARCHON;
689
+ const one = await sliverOf(handleOf(OPTOUT));
690
+ assert.equal(one.status, 404, 'the sliver refuses them');
691
+ assert.equal(one.json.error.code, 'builder_not_found', '404, never 403 — a 403 would confirm the handle exists');
692
+ const dir = await get('/api/bongos/scouting');
693
+ assert.ok(!dir.text.includes(String(ROLLUP[OPTOUT].credits)), 'and so does the directory');
694
+ assert.ok(!dir.text.includes(ACCOUNTS.find((a) => a.github_id === OPTOUT).display_name));
695
+ assert.equal(await getRecruiterSliver(OPTOUT, { pool }), null, 'and the port itself, not merely the route');
696
+ });
697
+
698
+ test('a PRIVATE account never appears unless its owner opted in by hand', async () => {
699
+ viewer = ARCHON;
700
+ // The default: private + no explicit flag = not reachable.
701
+ assert.equal((await sliverOf(handleOf(PRIVDEF))).status, 404);
702
+ assert.equal(await getRecruiterSliver(PRIVDEF, { pool }), null);
703
+ // And a private account is absent from the DIRECTORY even when it IS
704
+ // discoverable — the full rollup is never its disclosure, the sliver is.
705
+ const dir = await get('/api/bongos/scouting');
706
+ assert.ok(!dir.text.includes('Priv Person'), 'the private, discoverable account stays out of the directory');
707
+ assert.ok(!dir.text.includes(String(ROLLUP[PRIV].credits)), 'with none of its numbers');
708
+ });
709
+
710
+ test('a PROVISIONAL account is never recruiter-visible, however its flags read', async () => {
711
+ viewer = ARCHON;
712
+ assert.equal((await sliverOf(handleOf(PROV))).status, 404,
713
+ 'recruiter_discoverable=true does not survive the consent floor (spec D3)');
714
+ assert.equal(await getRecruiterSliver(PROV, { pool }), null);
715
+ });
716
+
717
+ // ===========================================================================
718
+ // 5. THE BOUNDARY IS A CEILING — the handle opens no page
719
+ // ===========================================================================
720
+
721
+ test('the handle a recruiter is given does NOT open the profile page', async () => {
722
+ viewer = ARCHON;
723
+ const sliver = await sliverOf(handleOf(PRIV));
724
+ assert.equal(sliver.status, 200);
725
+ const page = await get(`/api/bongos/profiles/${sliver.json.sliver.handle}`);
726
+ assert.equal(page.status, 404,
727
+ 'the pre-accept sliver is a CEILING, not a step: the public page joins the EXISTENCE axis and this account is private');
728
+ for (const sentinel of forbiddenOf(PRIV)) {
729
+ assert.ok(!page.text.includes(sentinel), `"${sentinel}" reached a recruiter through the profile page instead`);
730
+ }
731
+ });
732
+
733
+ test('a PREVIOUS handle opens nothing — the sliver does not confirm a rename', async () => {
734
+ viewer = ARCHON;
735
+ const stale = await sliverOf('priv-oldname-990204');
736
+ assert.equal(stale.status, 404,
737
+ 'unlike the public page, which answers 301 so a SHARED LINK survives a rename, a recruiting read has '
738
+ + 'no shared links to keep working — and resolving one would confirm to a recruiter that this account '
739
+ + 'renamed, a disclosure the sliver never grants');
740
+ assert.equal((await sliverOf(handleOf(PRIV))).status, 200,
741
+ 'CONTROL: the CURRENT handle still resolves — otherwise the 404 above proves only that the route is broken');
742
+ });
743
+
744
+ test('CONTROL: that same page DOES render for a builder who is publicly visible', async () => {
745
+ viewer = RECRUITER;
746
+ const page = await get(`/api/bongos/profiles/${handleOf(DIR)}`);
747
+ assert.equal(page.status, 200, 'otherwise the 404 above proves only that the route is broken');
748
+ assert.ok(page.text.includes(String(ROLLUP[DIR].credits)), 'and it carries the post-accept record the sliver withholds');
749
+ });
750
+
751
+ // ===========================================================================
752
+ // 6. THE GATE — a bounded projection behind an open door is not bounded
753
+ // ===========================================================================
754
+
755
+ test('the sliver is ARCHON-gated, and the directory audience does not reach it', async () => {
756
+ // ADR 0215 D4. Privacy spec D3 pins this read to `project.recruit`, floor
757
+ // ARCHON. The directory's door — `project.curate` at metic, OR any project
758
+ // owner (ADR 0210 D2) — is WIDER on both halves, so reusing it would widen an
759
+ // owner-signed privacy decision by implementation convenience. This is the
760
+ // first surface that discloses a private account, so it holds the signed floor
761
+ // until the atom exists; a blocker asks the owner about the wider audience.
762
+ viewer = ARCHON;
763
+ assert.equal((await sliverOf(handleOf(PRIV))).status, 200, 'an archon reads the sliver');
764
+ for (const who of [RECRUITER, OWNER]) {
765
+ viewer = who;
766
+ assert.equal((await sliverOf(handleOf(PRIV))).status, 403,
767
+ `a ${who.rank} holding the directory's door must NOT reach the sliver`);
768
+ assert.equal((await get('/api/bongos/scouting')).status, 200,
769
+ 'while still reading the directory — two audiences, not one narrowed');
770
+ }
771
+ });
772
+
773
+ test('the refusal names the RANK it wanted, and no session reaches either door', async () => {
774
+ viewer = STRANGER;
775
+ const one = await sliverOf(handleOf(PRIV));
776
+ assert.equal(one.status, 403);
777
+ assert.deepEqual(one.json.error.details, { required: ['archon'], actual: 'xenos' },
778
+ 'a rank refusal says which floor it wanted, so the hall can render why — it must not degrade to a bare 403');
779
+ assert.equal((await get('/api/bongos/scouting')).status, 403, 'and the directory refuses them too');
780
+ viewer = null;
781
+ assert.equal((await sliverOf(handleOf(PRIV))).status, 401, 'no session reaches either door');
782
+ });
783
+
784
+ test('a refusal happens BEFORE any builder data is read', async () => {
785
+ viewer = STRANGER;
786
+ await sliverOf(handleOf(PRIV));
787
+ assert.ok(!calls.some((c) => /platform_identity_accounts/.test(c.sql)),
788
+ 'the gate refuses without touching the subject account at all');
789
+ });
790
+
791
+ test('each route spells its OWN gate in its OWN middleware list', () => {
792
+ const src = fs.readFileSync(path.join(ROOT, 'modules/platform-identity/routes/scouting.js'), 'utf8');
793
+ assert.equal((src.match(/requirePermission\('project\.curate'\)/g) || []).length, 1,
794
+ 'the directory names its atom where route-rank-check.js and gen-api-docs.js can see it — a gate hidden in a helper records a privileged route as any-builder');
795
+ assert.equal((src.match(/api\.requireRank\('archon'\)/g) || []).length, 1,
796
+ 'and the sliver names its floor the same way, so the generated docs record archon and not the directory metic');
797
+ const sliverBlock = src.slice(src.indexOf("'/scouting/:handle'"));
798
+ assert.doesNotMatch(sliverBlock.slice(0, sliverBlock.indexOf('async (req, res)')), /requireScoutingReader/,
799
+ 'and the sliver must never pick up the directory wider door by accident');
800
+ });
801
+
802
+ // ===========================================================================
803
+ // 7. IT GRANTS NOTHING (ADR 0016)
804
+ // ===========================================================================
805
+
806
+ test('the sliver module is read by exactly one first-party server file — the one that serializes it', () => {
807
+ const files = [];
808
+ const walk = (dir) => {
809
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
810
+ const p = path.join(dir, e.name);
811
+ if (e.isDirectory()) { if (e.name !== 'node_modules') walk(p); }
812
+ else if (e.name.endsWith('.js')) files.push(p);
813
+ }
814
+ };
815
+ for (const d of ['src', 'modules', 'scripts']) walk(path.join(ROOT, d));
816
+ assert.ok(files.length > 300, `only ${files.length} files scanned — the sweep went vacuous`);
817
+ const readers = files
818
+ .filter((f) => /require\(\s*['"][^'"]*recruiter-sliver['"]\s*\)/.test(fs.readFileSync(f, 'utf8')))
819
+ .map((f) => path.relative(ROOT, f).split(path.sep).join('/'));
820
+ assert.deepEqual(readers, ['modules/platform-identity/routes/scouting.js'],
821
+ 'nothing may consult the sliver to DECIDE anything: cross-project numbers are descriptive analytics, never authority (ADR 0016)');
822
+ });
823
+
824
+ test('no rank, role, permission or membership travels with the sliver, under any name', async () => {
825
+ viewer = ARCHON;
826
+ const res = await sliverOf(handleOf(PRIV));
827
+ for (const word of ['rank', 'role', 'permission', 'membership', 'archon', 'metic', 'credits', 'karma', 'project']) {
828
+ assert.ok(!res.text.toLowerCase().includes(word),
829
+ `"${word}" appears in a sliver body — being visible to a recruiter must confer and reveal nothing`);
830
+ }
831
+ });
832
+
833
+ // ===========================================================================
834
+ // 8. This file runs in the lane that executes
835
+ // ===========================================================================
836
+
837
+ test('this proof runs in the DB-free unit lane', () => {
838
+ const runner = fs.readFileSync(path.join(ROOT, 'scripts/gds/run-unit-tests.js'), 'utf8');
839
+ assert.ok(!/recruiter_sliver_boundary/.test(runner),
840
+ 'named in run-unit-tests.js it would be parked into INTEGRATION or KNOWN_FAILING and gate nothing (learning 1000218)');
841
+ });