@bongos/core 1.19.803 → 1.19.805
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 +261 -236
- package/.claude/skills/ask-for-help/SKILL.md +6 -6
- package/.claude/skills/backlog-review/SKILL.md +1 -1
- package/.claude/skills/blocker-review/SKILL.md +7 -7
- package/.claude/skills/blocker-solve/SKILL.md +5 -5
- package/.claude/skills/bug-triage/SKILL.md +7 -7
- package/.claude/skills/builder-claim/SKILL.md +3 -3
- package/.claude/skills/builder-cost/SKILL.md +2 -2
- package/.claude/skills/builder-end/SKILL.md +2 -2
- package/.claude/skills/builder-exit/SKILL.md +7 -7
- package/.claude/skills/builder-key/SKILL.md +2 -2
- package/.claude/skills/builder-reauth/SKILL.md +2 -2
- package/.claude/skills/builder-redteam/SKILL.md +7 -7
- package/.claude/skills/builder-release/SKILL.md +2 -2
- package/.claude/skills/builder-sequence/SKILL.md +1 -1
- package/.claude/skills/builder-setup/SKILL.md +3 -3
- package/.claude/skills/builder-ship/SKILL.md +1 -1
- package/.claude/skills/builder-stage/SKILL.md +1 -1
- package/.claude/skills/builder-start/SKILL.md +1 -1
- package/.claude/skills/fix-task/SKILL.md +4 -4
- package/.claude/skills/goal-create/SKILL.md +18 -18
- package/.claude/skills/goal-review/SKILL.md +7 -7
- package/.claude/skills/grade-audit/SKILL.md +5 -5
- package/.claude/skills/grade-recover/SKILL.md +4 -4
- package/.claude/skills/grader-health/SKILL.md +6 -6
- package/.claude/skills/idea-triage/SKILL.md +14 -14
- package/.claude/skills/merge-mode/SKILL.md +3 -3
- package/.claude/skills/planning-session/SKILL.md +20 -20
- package/.claude/skills/priority-session/SKILL.md +10 -10
- package/.claude/skills/recall/SKILL.md +1 -1
- package/.claude/skills/scan-before-install/SKILL.md +3 -3
- package/.claude/skills/status/SKILL.md +2 -2
- package/.claude/skills/strand-fix/SKILL.md +2 -2
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +2 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/api/openapi.json +45 -11
- package/docs/api-reference.md +11 -10
- package/docs/module-api-changelog.md +4 -0
- package/modules/builder-settings/profile-nudge-prefs.js +1 -1
- package/modules/builder-settings/render-prefs.js +1 -1
- package/modules/dev-box/app/README.md +1 -1
- package/modules/dev-box/app/src/gds-client.js +11 -9
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +2 -0
- package/modules/dev-box/app/test/unit.mjs +13 -13
- package/modules/dev-box/box-access.js +3 -3
- package/modules/dev-box/box-onboard.js +7 -7
- package/modules/discord/bug-attachments.js +1 -1
- package/modules/economy/cost-classify.js +6 -6
- package/modules/grading/grader-rubric.json +1 -1
- package/modules/grading/grader-score.js +1 -1
- package/modules/hall-ui/module.json +1 -1
- package/modules/hall-ui/public/drachmae.js +3 -3
- package/modules/hall-ui/public/idea.js +1 -1
- package/modules/hall-ui/public/md-reader.js +1 -1
- package/modules/hall-ui/public/people.html +5 -1
- package/modules/hall-ui/public/people.js +44 -5
- package/modules/hall-ui/public/primer.js +4 -4
- package/modules/hall-ui/public/roster.js +1 -1
- package/modules/hall-ui/public/shell.js +1 -1
- package/modules/hall-ui/public/task.js +1 -1
- package/modules/ideas/inbox.js +2 -2
- package/modules/lifecycle/db-analytics.js +1 -1
- package/modules/lifecycle/done-when.js +4 -4
- package/modules/lifecycle/version-close.js +1 -1
- package/modules/memory/learnings.js +2 -2
- package/modules/memory/memory.js +1 -1
- package/modules/onboarding/onboarding.js +1 -1
- package/modules/platform-identity/routes/my-projects.js +1 -1
- package/modules/platform-identity/routes/sso.js +2 -2
- package/modules/public-landing/public/index.html +2 -2
- package/modules/public-landing/public/projects-floor.states.json +1 -1
- package/modules/public-landing/public/projects.html +1 -1
- package/modules/public-landing/public/projects.states.json +1 -1
- package/modules/sessions/db.js +120 -0
- package/modules/sessions/module.json +2 -2
- package/modules/sessions/pollers/session-upload-health.js +111 -0
- package/modules/sessions/pulse.js +2 -2
- package/modules/sessions/routes/sessions.js +46 -0
- package/modules/sessions/upload-health.js +197 -0
- package/modules/status-ui/module.json +1 -1
- package/modules/status-ui/public/explore.js +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/agent-invoke.js +3 -3
- package/scripts/gds/api.js +4 -4
- package/scripts/gds/api.sh +1 -1
- package/scripts/gds/architect-audit.js +1 -1
- package/scripts/gds/artifact-audit.js +2 -2
- package/scripts/gds/audit-ships.js +2 -2
- package/scripts/gds/backfill-cost.js +1 -1
- package/scripts/gds/backfill-sessions.js +1 -1
- package/scripts/gds/backfill-subagent-rewards.js +1 -1
- package/scripts/gds/backfill-task-rewards.js +2 -2
- package/scripts/gds/box-auth-check.js +1 -1
- package/scripts/gds/box-sync.js +3 -3
- package/scripts/gds/box.js +10 -10
- package/scripts/gds/build-cli-package.js +3 -3
- package/scripts/gds/capture.js +2 -2
- package/scripts/gds/ci-grade.js +1 -1
- package/scripts/gds/claim-recall.js +2 -2
- package/scripts/gds/cli-lib.js +3 -3
- package/scripts/gds/context-pack.js +1 -1
- package/scripts/gds/copy-apply.js +2 -2
- package/scripts/gds/dev-lib.js +2 -2
- package/scripts/gds/fetch-art-key.js +2 -2
- package/scripts/gds/fetch-sound-prefs.js +3 -3
- package/scripts/gds/grade-smoke.js +1 -1
- package/scripts/gds/hooks/pre-push.js +1 -1
- package/scripts/gds/init.js +9 -9
- package/scripts/gds/key-card.js +1 -1
- package/scripts/gds/learning-capture.js +2 -2
- package/scripts/gds/linkify-refs.js +2 -2
- package/scripts/gds/local-preview-lib.js +1 -1
- package/scripts/gds/login.js +11 -10
- package/scripts/gds/main-audit.js +1 -1
- package/scripts/gds/newcomer-floor.config.json +1 -1
- package/scripts/gds/newcomer-floor.js +2 -2
- package/scripts/gds/newcomer-floor.templates.json +2 -2
- package/scripts/gds/onboarding-ship.js +1 -1
- package/scripts/gds/paste-token.js +3 -3
- package/scripts/gds/portability-smoke-memory.sh +5 -5
- package/scripts/gds/prove-strict-versioning.js +2 -2
- package/scripts/gds/provision-net.js +2 -2
- package/scripts/gds/provision-units.js +3 -3
- package/scripts/gds/pulse.js +2 -2
- package/scripts/gds/push-path-brief.js +2 -2
- package/scripts/gds/reauth.js +1 -1
- package/scripts/gds/recall.js +1 -1
- package/scripts/gds/redteam-patrol.js +3 -3
- package/scripts/gds/render-ideas.js +5 -5
- package/scripts/gds/repo-metrics.js +1 -1
- package/scripts/gds/sandbox-stage.js +3 -3
- package/scripts/gds/seed-adopt-tasks.js +3 -3
- package/scripts/gds/seed-board-room-tasks.js +1 -1
- package/scripts/gds/seed-bongos-app-tasks.js +2 -2
- package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +3 -3
- package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +4 -4
- package/scripts/gds/seed-bongos-tasks.js +5 -5
- package/scripts/gds/seed-bongos-v1-idea-routing-tasks.js +1 -1
- package/scripts/gds/seed-bongos-v1-seo-tasks.js +1 -1
- package/scripts/gds/seed-bv1-community-tasks.js +1 -1
- package/scripts/gds/seed-bv1-privacy-invites-tasks.js +1 -1
- package/scripts/gds/seed-bv1-profile-page-tasks.js +1 -1
- package/scripts/gds/seed-bv1-profiles-tasks.js +1 -1
- package/scripts/gds/seed-bv1-r01-delta-tasks.js +1 -1
- package/scripts/gds/seed-gds-v4-tasks.js +3 -3
- package/scripts/gds/seed-medusa-tasks.js +1 -1
- package/scripts/gds/seed-productization-tasks.js +1 -1
- package/scripts/gds/seed-project-admin-console-tasks.js +1 -1
- package/scripts/gds/seed-provisioning-tasks.js +2 -2
- package/scripts/gds/seed-strict-versioning-tasks.js +3 -3
- package/scripts/gds/seed-v2-world-tasks.js +2 -2
- package/scripts/gds/seed-verify-criteria.js +1 -1
- package/scripts/gds/session-digest.js +1 -1
- package/scripts/gds/session.js +2 -2
- package/scripts/gds/setup.js +7 -7
- package/scripts/gds/ship-flow.js +3 -3
- package/scripts/gds/ship-grade-steps.js +3 -3
- package/scripts/gds/ship-memory-sync.js +1 -1
- package/scripts/gds/ship-session-upload.js +3 -3
- package/scripts/gds/smoke-batch-claim.sh +10 -10
- package/scripts/gds/smoke-batch-release.sh +14 -14
- package/scripts/gds/smoke-claim-consistency.sh +19 -19
- package/scripts/gds/smoke-dependencies.sh +9 -9
- package/scripts/gds/smoke-write.sh +11 -11
- package/scripts/gds/start.js +1 -1
- package/scripts/gds/status.js +2 -2
- package/scripts/gds/strand-watch.js +1 -1
- package/scripts/gds/triage.js +4 -4
- package/scripts/gds/version-close.js +2 -2
- package/scripts/hall-preview/README.md +1 -1
- package/scripts/hall-preview/server.js +1 -1
- package/src/bongos/auth-admission.js +3 -3
- package/src/bongos/auth-idp.js +2 -2
- package/src/bongos/auth.js +3 -3
- package/src/bongos/db.js +2 -2
- package/src/bongos/llm-cache.js +2 -2
- package/src/bongos/metrics.js +1 -1
- package/src/bongos/middleware/audit.js +3 -3
- package/src/bongos/middleware/rate-limit.js +1 -1
- package/src/bongos/platform-visibility-gate.js +2 -2
- package/src/bongos/routes/_helpers.js +1 -1
- package/src/bongos/routes/audit-log.js +1 -1
- package/src/bongos/routes/auth.js +5 -5
- package/src/bongos/routes/backup.js +1 -1
- package/src/bongos/routes/builders.js +1 -1
- package/src/bongos/routes/instance.js +1 -1
- package/src/bongos/routes/live.js +1 -1
- package/src/bongos/routes/me.js +2 -2
- package/src/bongos/routes/modules.js +2 -2
- package/src/bongos/routes/public.js +1 -1
- package/src/bongos/routes/security.js +4 -4
- package/src/bongos/routes.js +1 -1
- package/src/bongos/serve-internal.js +6 -6
- package/src/branding.js +1 -1
- package/src/module-api.js +1 -1
- package/src/module-seams.js +2 -2
- package/src/platform-server.js +2 -2
- package/tests/api_alias_exceptions.mjs +67 -0
- package/tests/application_echo.mjs +12 -12
- package/tests/artifact_audit.mjs +1 -1
- package/tests/auth_page_gate.mjs +4 -4
- package/tests/auth_web_recovery.mjs +11 -11
- package/tests/bongos_login.mjs +7 -4
- package/tests/box_env.mjs +6 -6
- package/tests/bug_attachments.mjs +1 -1
- package/tests/cli_sessions.mjs +1 -1
- package/tests/dev_lib.mjs +1 -1
- package/tests/discord_bugs.mjs +2 -2
- package/tests/discord_inbound.mjs +2 -2
- package/tests/explore_visible_states.mjs +1 -1
- package/tests/government_page_gates.mjs +1 -1
- package/tests/hall_reference_world.mjs +1 -1
- package/tests/idea_ideate_full.mjs +1 -1
- package/tests/idp_federation.mjs +2 -2
- package/tests/llm-cache.mjs +1 -1
- package/tests/manage_manifest_shared_read.mjs +3 -3
- package/tests/people_directory_clip.mjs +219 -0
- package/tests/planning_session_skill.mjs +1 -1
- package/tests/platform_visibility_gate.mjs +15 -15
- package/tests/profile_route.mjs +37 -26
- package/tests/projects_hub.mjs +2 -2
- package/tests/provision.mjs +2 -2
- package/tests/provision_settings_apply.mjs +4 -4
- package/tests/sandbox_stage.mjs +8 -8
- package/tests/scouting_owner_access_and_reach.mjs +3 -3
- package/tests/session_upload_health.mjs +329 -0
- package/tests/setup_device_flow_fallback.mjs +1 -1
- package/tests/skill_ask_for_help.mjs +3 -3
- package/tests/skill_grade_audit.mjs +2 -2
- package/tests/skill_grader_health.mjs +3 -3
- package/tests/smoke_write_contract.mjs +1 -1
|
@@ -98,7 +98,7 @@ const GATE_FLOOR = {
|
|
|
98
98
|
// ADR 0157: `page.view.government` floor archon→metic.
|
|
99
99
|
requireGovernmentPage: 'metic',
|
|
100
100
|
};
|
|
101
|
-
const SIGN_IN = '/api/
|
|
101
|
+
const SIGN_IN = '/api/bongos/auth/web/start';
|
|
102
102
|
|
|
103
103
|
// --- doubles ----------------------------------------------------------------
|
|
104
104
|
function fakeRes() {
|
|
@@ -241,7 +241,7 @@ const costSummaryFixture = JSON.parse(
|
|
|
241
241
|
test('the treasury reads the endpoint that already carried the series — no new route', () => {
|
|
242
242
|
// The whole point of the task: the payload had both series all along, and the
|
|
243
243
|
// route's own comment calls daily[] "the treasury chart's cumulative line".
|
|
244
|
-
assert.match(drachmaeJs, /TREASURY_URL = '\/api\/
|
|
244
|
+
assert.match(drachmaeJs, /TREASURY_URL = '\/api\/bongos\/public\/cost-summary'/);
|
|
245
245
|
assert.ok(costSummaryFixture.daily.length, 'the payload really carries daily[]');
|
|
246
246
|
assert.ok(costSummaryFixture.daily_credits.length, 'and daily_credits[]');
|
|
247
247
|
assert.ok(Number(costSummaryFixture.total_usd) > 0, 'and the totals the prose states');
|
|
@@ -93,7 +93,7 @@ test('it describes NO second write path', () => {
|
|
|
93
93
|
// ends up sending a different payload than the form does, and the two records
|
|
94
94
|
// drift while both claim to be Full Ideas.
|
|
95
95
|
assert.ok(!/curl/i.test(SKILL), 'no hand-rolled HTTP');
|
|
96
|
-
assert.ok(!/api\.js POST \/api\/
|
|
96
|
+
assert.ok(!/api\.js POST \/api\/bongos\/inbox/.test(SKILL), 'no raw POST for filing an idea');
|
|
97
97
|
const grade = [...SKILL.matchAll(/grade\s*=\s*'([a-z]+)'/g)].map((m) => m[1]);
|
|
98
98
|
assert.ok(grade.every((g) => g === 'full'), `only the full grade is named: ${JSON.stringify(grade)}`);
|
|
99
99
|
});
|
package/tests/idp_federation.mjs
CHANGED
|
@@ -29,7 +29,7 @@ t('builds the hub /sso/authorize URL with client_id, redirect_uri, state', () =>
|
|
|
29
29
|
const url = auth.idpAuthorizeUrl('https://cloudbongos.com', 'client-abc', 'st8', 'https://proj.example.com/api/gds/auth/web/callback');
|
|
30
30
|
const u = new URL(url);
|
|
31
31
|
assert.equal(u.origin, 'https://cloudbongos.com');
|
|
32
|
-
assert.equal(u.pathname, '/api/
|
|
32
|
+
assert.equal(u.pathname, '/api/bongos/sso/authorize');
|
|
33
33
|
assert.equal(u.searchParams.get('client_id'), 'client-abc');
|
|
34
34
|
assert.equal(u.searchParams.get('redirect_uri'), 'https://proj.example.com/api/gds/auth/web/callback');
|
|
35
35
|
assert.equal(u.searchParams.get('state'), 'st8');
|
|
@@ -37,7 +37,7 @@ t('builds the hub /sso/authorize URL with client_id, redirect_uri, state', () =>
|
|
|
37
37
|
|
|
38
38
|
t('trims a trailing slash on the hub origin', () => {
|
|
39
39
|
const url = auth.idpAuthorizeUrl('https://cloudbongos.com/', 'c', 's', 'https://p/cb');
|
|
40
|
-
assert.equal(new URL(url).pathname, '/api/
|
|
40
|
+
assert.equal(new URL(url).pathname, '/api/bongos/sso/authorize');
|
|
41
41
|
assert.ok(!url.includes('com//api'), 'no double slash between origin and path');
|
|
42
42
|
});
|
|
43
43
|
|
package/tests/llm-cache.mjs
CHANGED
|
@@ -88,7 +88,7 @@ await arun('lookup: routes to apiCall on a HIT (right method/path/args)', async
|
|
|
88
88
|
const r = await c.lookup(KEY, { tokensSavedEst: 12 });
|
|
89
89
|
assert.equal(r.result_stdout, 'X');
|
|
90
90
|
assert.equal(calls[0][0], 'POST');
|
|
91
|
-
assert.equal(calls[0][1], '/api/
|
|
91
|
+
assert.equal(calls[0][1], '/api/bongos/llm-cache/lookup');
|
|
92
92
|
assert.deepEqual(calls[0][2], { cache_key: KEY, tokens_saved_est: 12 });
|
|
93
93
|
assert.deepEqual(calls[0][3], { allowUnauthenticated: true });
|
|
94
94
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// manifest per manage entry, EXECUTED (task 1003554).
|
|
3
3
|
//
|
|
4
4
|
// The defect this pins shut: entering /projects?view=manage fetched
|
|
5
|
-
// https://<project>/api/
|
|
5
|
+
// https://<project>/api/bongos/instance twice, identically and cross-origin,
|
|
6
6
|
// because the two cards that render a "what it runs right now" line — how the
|
|
7
7
|
// project is reached (brand.project.platformVisibility) and how people join
|
|
8
8
|
// (brand.project.joinability) — each asked the project for themselves. Two
|
|
@@ -99,7 +99,7 @@ test('the two cards of one manage entry cost ONE round trip, not two', async ()
|
|
|
99
99
|
|
|
100
100
|
// Assert
|
|
101
101
|
assert.equal(ui.requests.length, 1, 'one cross-origin GET served both lines');
|
|
102
|
-
assert.equal(ui.requests[0].url, 'https://demo.example.com/api/
|
|
102
|
+
assert.equal(ui.requests[0].url, 'https://demo.example.com/api/bongos/instance');
|
|
103
103
|
assert.equal(ui.requests[0].mode, 'cors', 'the project is somebody else’s origin');
|
|
104
104
|
for (const a of answers) {
|
|
105
105
|
assert.equal(a.ok, true);
|
|
@@ -151,7 +151,7 @@ test('switching projects is never served the previous project’s manifest', asy
|
|
|
151
151
|
|
|
152
152
|
// Assert
|
|
153
153
|
assert.equal(ui.requests.length, 2, 'the memo is keyed on the project, not merely present');
|
|
154
|
-
assert.equal(ui.requests[1].url, 'https://two.example.com/api/
|
|
154
|
+
assert.equal(ui.requests[1].url, 'https://two.example.com/api/bongos/instance');
|
|
155
155
|
});
|
|
156
156
|
|
|
157
157
|
test('a project that answered-and-refused RESOLVES carrying its status — both lines can say so', async () => {
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// tests/people_directory_clip.mjs — the People directory says so when it is
|
|
2
|
+
// clipped (task 1003110, goal 1000095).
|
|
3
|
+
//
|
|
4
|
+
// WHY THIS FILE EXISTS. `people.js` asks `GET /scouting` for 500 rows, which is
|
|
5
|
+
// that route's HARD CEILING (parsePagination maxLimit: 500). The rollup returns
|
|
6
|
+
// no count of the whole set and the route takes no offset, so a response that
|
|
7
|
+
// comes back FULL is the only truncation signal the page will ever get — and
|
|
8
|
+
// until this task the page simply printed "500 builders listed" and said nothing
|
|
9
|
+
// else. A directory that lies by omission is worse than one that admits it is
|
|
10
|
+
// incomplete.
|
|
11
|
+
//
|
|
12
|
+
// It runs the REAL people.js in a vm against a stub DOM and a fake fetch, so
|
|
13
|
+
// what is asserted is the sentence a reader actually sees, not a regex agreeing
|
|
14
|
+
// with a regex. A source-text assertion would have passed just as happily with
|
|
15
|
+
// the comparison written the wrong way round.
|
|
16
|
+
//
|
|
17
|
+
// The four behaviours:
|
|
18
|
+
// 1. the page asks for exactly the number its copy then names (no drift)
|
|
19
|
+
// 2. a SHORT response is not hedged — the exact count is the honest one
|
|
20
|
+
// 3. a FULL response hedges every sentence that states a total: the pill, the
|
|
21
|
+
// subtitle, and a footnote about the lenses
|
|
22
|
+
// 4. one row short of the ceiling is NOT clipped (the boundary, in the
|
|
23
|
+
// direction that would otherwise cry wolf on every load)
|
|
24
|
+
//
|
|
25
|
+
// Run: node tests/people_directory_clip.mjs
|
|
26
|
+
import { strict as assert } from 'node:assert';
|
|
27
|
+
import fs from 'node:fs';
|
|
28
|
+
import path from 'node:path';
|
|
29
|
+
import vm from 'node:vm';
|
|
30
|
+
import { fileURLToPath } from 'node:url';
|
|
31
|
+
import { makeRunner } from './helpers.mjs';
|
|
32
|
+
|
|
33
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
34
|
+
const read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
|
|
35
|
+
const KIT_JS = read('modules/hall-ui/public/hall-kit.js');
|
|
36
|
+
const PEOPLE_JS = read('modules/hall-ui/public/people.js');
|
|
37
|
+
const PEOPLE_HTML = read('modules/hall-ui/public/people.html');
|
|
38
|
+
|
|
39
|
+
// The route's ceiling, spelled here as the CONTRACT rather than read out of the
|
|
40
|
+
// page — a test that imports the page's own constant cannot notice it changing.
|
|
41
|
+
const CEILING = 500;
|
|
42
|
+
|
|
43
|
+
const { test, summary } = makeRunner();
|
|
44
|
+
const tick = () => new Promise((r) => setTimeout(r, 0));
|
|
45
|
+
|
|
46
|
+
function makeEl(id) {
|
|
47
|
+
return {
|
|
48
|
+
id,
|
|
49
|
+
innerHTML: '', textContent: '', value: '',
|
|
50
|
+
hidden: false, disabled: false, className: '',
|
|
51
|
+
options: [], style: {}, dataset: {},
|
|
52
|
+
classList: { add() {}, remove() {}, toggle() {}, contains: () => false },
|
|
53
|
+
appendChild() {}, setAttribute() {}, removeAttribute() {},
|
|
54
|
+
addEventListener() {},
|
|
55
|
+
querySelector() { return makeEl(); },
|
|
56
|
+
querySelectorAll() { return []; },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// `count` builders, each distinct enough that a filter lens has something to bite.
|
|
61
|
+
function buildersFor(count) {
|
|
62
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
63
|
+
github_id: 1000 + i,
|
|
64
|
+
github_login: `builder${i}`,
|
|
65
|
+
display_name: `Builder ${i}`,
|
|
66
|
+
avatar_url: null,
|
|
67
|
+
project_count: 1,
|
|
68
|
+
total_credits: count - i,
|
|
69
|
+
total_tasks_shipped: 1,
|
|
70
|
+
total_karma: 1,
|
|
71
|
+
last_active: '2026-09-01T00:00:00Z',
|
|
72
|
+
disciplines: ['engineer'],
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function harness({ count }) {
|
|
77
|
+
const els = new Map();
|
|
78
|
+
const getEl = (id) => {
|
|
79
|
+
if (!els.has(id)) els.set(id, makeEl(id));
|
|
80
|
+
return els.get(id);
|
|
81
|
+
};
|
|
82
|
+
const documentObj = {
|
|
83
|
+
documentElement: { dataset: {}, style: {}, classList: { add() {}, remove() {} } },
|
|
84
|
+
getElementById: getEl,
|
|
85
|
+
querySelector(sel) { return getEl(String(sel).replace(/^#/, '')); },
|
|
86
|
+
querySelectorAll() { return []; },
|
|
87
|
+
createElement() { return makeEl(); },
|
|
88
|
+
addEventListener() {},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const requests = [];
|
|
92
|
+
const store = new Map();
|
|
93
|
+
const windowObj = {
|
|
94
|
+
location: { assign() {}, href: '/people' },
|
|
95
|
+
localStorage: {
|
|
96
|
+
getItem: (k) => (store.has(k) ? store.get(k) : null),
|
|
97
|
+
setItem: (k, v) => store.set(k, String(v)),
|
|
98
|
+
removeItem: (k) => store.delete(k),
|
|
99
|
+
},
|
|
100
|
+
scrollTo() {},
|
|
101
|
+
addEventListener() {},
|
|
102
|
+
matchMedia: () => ({ matches: false, addEventListener() {}, addListener() {} }),
|
|
103
|
+
OTBBranding: { currencyLabel: () => 'Credits' },
|
|
104
|
+
OTB: {
|
|
105
|
+
escapeHtml: (s) => String(s == null ? '' : s),
|
|
106
|
+
$: (sel) => documentObj.querySelector(sel),
|
|
107
|
+
num: (n) => Number(n) || 0,
|
|
108
|
+
fmtInt: (n) => String(Number(n) || 0),
|
|
109
|
+
fmtCompact: (n) => String(Number(n) || 0),
|
|
110
|
+
fmtDate: (iso) => String(iso ?? '').slice(0, 10),
|
|
111
|
+
rankLabel: (r) => String(r ?? ''),
|
|
112
|
+
toast() {},
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const sandbox = {
|
|
117
|
+
window: windowObj, document: documentObj, location: windowObj.location,
|
|
118
|
+
console: { error() {}, warn() {}, log() {} },
|
|
119
|
+
setTimeout, clearTimeout, URLSearchParams, encodeURIComponent, Date, Math, JSON,
|
|
120
|
+
fetch: (url) => {
|
|
121
|
+
requests.push(String(url));
|
|
122
|
+
return Promise.resolve({
|
|
123
|
+
ok: true,
|
|
124
|
+
status: 200,
|
|
125
|
+
json: () => Promise.resolve({ builders: buildersFor(count) }),
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
sandbox.globalThis = sandbox;
|
|
130
|
+
vm.createContext(sandbox);
|
|
131
|
+
// The real kit, for the same reason sessions_kit_pager.mjs runs it: the page is
|
|
132
|
+
// held to the shipped contract, not to a stub that agrees with it.
|
|
133
|
+
vm.runInContext(KIT_JS, sandbox, { filename: 'hall-kit.js' });
|
|
134
|
+
vm.runInContext(PEOPLE_JS, sandbox, { filename: 'people.js' });
|
|
135
|
+
|
|
136
|
+
return { getEl, requests };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
await test('the page markup carries the footnote slot, hidden until the page writes it', () => {
|
|
142
|
+
assert.match(PEOPLE_HTML, /id="people-foot"/, 'the clipped-directory footnote has a mount');
|
|
143
|
+
const tag = PEOPLE_HTML.match(/<p[^>]*id="people-foot"[^>]*>/);
|
|
144
|
+
assert.ok(tag, 'the footnote is a <p>');
|
|
145
|
+
assert.match(tag[0], /\bhidden\b/, 'it starts hidden — an empty note is still a visible gap');
|
|
146
|
+
assert.match(tag[0], /class="ledger__note"/, "the shell's ledger footnote class, not a bespoke one");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await test('the page asks for exactly the ceiling, and asks for it ONCE', async () => {
|
|
150
|
+
const h = harness({ count: 3 });
|
|
151
|
+
await tick(); await tick();
|
|
152
|
+
assert.equal(h.requests.length, 1, 'one directory read');
|
|
153
|
+
assert.match(h.requests[0], /\/api\/bongos\/scouting\?limit=/);
|
|
154
|
+
const limit = Number(new URLSearchParams(h.requests[0].split('?')[1]).get('limit'));
|
|
155
|
+
assert.equal(limit, CEILING, `the page must ask for the route ceiling (${CEILING})`);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
await test('a SHORT directory is not hedged — the exact count is the honest one', async () => {
|
|
159
|
+
const h = harness({ count: 3 });
|
|
160
|
+
await tick(); await tick();
|
|
161
|
+
assert.equal(h.getEl('people-count').textContent, '3', 'the pill states the real count');
|
|
162
|
+
assert.equal(h.getEl('people-count').hidden, false);
|
|
163
|
+
assert.match(h.getEl('people-sub').textContent, /^3 builders listed/, 'the subtitle states the real count');
|
|
164
|
+
assert.doesNotMatch(h.getEl('people-sub').textContent, /first|more than/i,
|
|
165
|
+
'nothing is clipped, so nothing may suggest it is');
|
|
166
|
+
assert.equal(h.getEl('people-foot').hidden, true, 'no footnote on a complete directory');
|
|
167
|
+
assert.equal(h.getEl('people-foot').textContent, '', 'and no stale text left behind it');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
await test('a FULL response hedges the pill, the subtitle AND the lenses', async () => {
|
|
171
|
+
const h = harness({ count: CEILING });
|
|
172
|
+
await tick(); await tick();
|
|
173
|
+
|
|
174
|
+
const pill = h.getEl('people-count').textContent;
|
|
175
|
+
assert.equal(pill, `${CEILING}+`, 'the pill may not state a total the page cannot know');
|
|
176
|
+
|
|
177
|
+
const sub = h.getEl('people-sub').textContent;
|
|
178
|
+
assert.match(sub, new RegExp(`first ${CEILING}\\b`), 'the subtitle says which slice this is');
|
|
179
|
+
assert.match(sub, /more than this page can load/, 'and says the directory is larger');
|
|
180
|
+
assert.doesNotMatch(sub, new RegExp(`^${CEILING} builders listed`),
|
|
181
|
+
'the old sentence claimed the slice was the whole set');
|
|
182
|
+
|
|
183
|
+
const foot = h.getEl('people-foot');
|
|
184
|
+
assert.equal(foot.hidden, false, 'the footnote is shown');
|
|
185
|
+
// THE LENSES ARE THE SHARPER LIE. Search/sort/craft re-order only the loaded
|
|
186
|
+
// set, so on a clipped directory a builder outside the top 500 cannot be found
|
|
187
|
+
// by any of them — a reader told only "the list is long" would still trust the
|
|
188
|
+
// search box.
|
|
189
|
+
assert.match(foot.textContent, /search/i, 'the footnote names the search');
|
|
190
|
+
assert.match(foot.textContent, /sort/i, 'and the sort lenses');
|
|
191
|
+
assert.match(foot.textContent, new RegExp(`will not appear`),
|
|
192
|
+
'and says plainly that a builder outside the slice is unreachable through them');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
await test('one row short of the ceiling is NOT clipped', async () => {
|
|
196
|
+
const h = harness({ count: CEILING - 1 });
|
|
197
|
+
await tick(); await tick();
|
|
198
|
+
assert.equal(h.getEl('people-count').textContent, String(CEILING - 1),
|
|
199
|
+
'a nearly-full page is still a complete one');
|
|
200
|
+
assert.equal(h.getEl('people-foot').hidden, true, 'crying wolf here would train the notice away');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await test('every number in the clipped copy is the number that was requested', async () => {
|
|
204
|
+
const h = harness({ count: CEILING });
|
|
205
|
+
await tick(); await tick();
|
|
206
|
+
const limit = new URLSearchParams(h.requests[0].split('?')[1]).get('limit');
|
|
207
|
+
// The drift this guards: someone raises the request to 1000 and leaves "500"
|
|
208
|
+
// written in three sentences. Every integer the clipped copy prints must be
|
|
209
|
+
// the one the request carried.
|
|
210
|
+
const copy = [h.getEl('people-count').textContent, h.getEl('people-sub').textContent,
|
|
211
|
+
h.getEl('people-foot').textContent].join(' ');
|
|
212
|
+
const printed = copy.match(/\d+/g) || [];
|
|
213
|
+
assert.ok(printed.length >= 3, 'the clipped copy states the number more than once');
|
|
214
|
+
for (const n of printed) {
|
|
215
|
+
assert.equal(n, limit, `copy prints ${n} but the page requested ${limit}`);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
summary();
|
|
@@ -31,7 +31,7 @@ function test(name, fn) {
|
|
|
31
31
|
test('the seeding contract documents the self-check convention', () => {
|
|
32
32
|
assert.ok(NORM.includes('verifySeedCriteriaLinks'), 'names the helper function');
|
|
33
33
|
assert.ok(NORM.includes('seed-verify-criteria.js'), 'names the shared helper file');
|
|
34
|
-
assert.ok(NORM.includes('GET /api/
|
|
34
|
+
assert.ok(NORM.includes('GET /api/bongos/goals/<id>'), 'names the endpoint the self-check fetches');
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
test('the self-check is described as failing loudly on any mismatch, zero included', () => {
|
|
@@ -85,7 +85,7 @@ test('gated mode: a stranger meets the door on a page, with the way in on it', a
|
|
|
85
85
|
assert.equal(nexted, false);
|
|
86
86
|
assert.equal(res.statusCode, 401, 'a refused knock is a 401, not a 200 or a redirect');
|
|
87
87
|
assert.match(res.body, /Sign in/, 'the door carries the way in');
|
|
88
|
-
assert.match(res.body, /href="\/api\/
|
|
88
|
+
assert.match(res.body, /href="\/api\/bongos\/auth\/web\/start\?return=%2Fprojects%3Fview%3Dmap"/,
|
|
89
89
|
'the sign-in start returns the member to the page they knocked on — RELATIVE, which safeReturnTo honours everywhere');
|
|
90
90
|
assert.match(res.body, /Spike Harbor/, 'it names the project');
|
|
91
91
|
assert.match(res.body, /noindex/, 'a members-only project is not indexed');
|
|
@@ -172,7 +172,7 @@ test('the exempt list: the door, the manifest, the probes, the downloads — on
|
|
|
172
172
|
'/api/bongos/v1/public/progress', '/api/gds/instance/anything', '/instance', '/healthzz', '/downloads',
|
|
173
173
|
'/api/other/instance', '/api/bongos/provisioning/instances',
|
|
174
174
|
// the apply door is METHOD-scoped, so the bare path — no verb — is not exempt
|
|
175
|
-
'/api/
|
|
175
|
+
'/api/bongos/access-requests', '/api/bongos/access-requests/status',
|
|
176
176
|
]) assert.equal(isExempt(p), false, `${p} must be gated`);
|
|
177
177
|
});
|
|
178
178
|
|
|
@@ -184,7 +184,7 @@ test('the apply door: POST <api>/access-requests answers a stranger — and only
|
|
|
184
184
|
for (const p of [
|
|
185
185
|
'/api/gds/access-requests', '/api/bongos/access-requests', '/api/bongos/v1/access-requests',
|
|
186
186
|
// the router's own tolerances, the same ones the rest of the list is held to
|
|
187
|
-
'/API/BONGOS/ACCESS-REQUESTS', '/api/
|
|
187
|
+
'/API/BONGOS/ACCESS-REQUESTS', '/api/bongos/access-requests/',
|
|
188
188
|
]) assert.equal(isExempt(p, 'POST'), true, `POST ${p} must reach the apply route`);
|
|
189
189
|
|
|
190
190
|
// The bare GET on that path is the OWNER'S QUEUE (requireBuilder +
|
|
@@ -192,15 +192,15 @@ test('the apply door: POST <api>/access-requests answers a stranger — and only
|
|
|
192
192
|
// in front of it regardless — an exemption that ignored the verb would have
|
|
193
193
|
// silently removed a layer from the queue that lists would-be builders by name.
|
|
194
194
|
for (const m of ['GET', 'HEAD', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', '']) {
|
|
195
|
-
assert.equal(isExempt('/api/
|
|
195
|
+
assert.equal(isExempt('/api/bongos/access-requests', m), false, `${m || '(no verb)'} on the queue must stay gated`);
|
|
196
196
|
}
|
|
197
197
|
// and the exemption does not spread down the path
|
|
198
|
-
for (const p of ['/api/
|
|
198
|
+
for (const p of ['/api/bongos/access-requests/status', '/api/bongos/access-requests/invite', '/api/bongos/access-requests/1']) {
|
|
199
199
|
assert.equal(isExempt(p, 'POST'), false, `POST ${p} must stay gated`);
|
|
200
200
|
}
|
|
201
201
|
// a method-BLIND entry is unchanged by the new shape: it still exempts any verb
|
|
202
202
|
assert.equal(isExempt('/healthz', 'POST'), true, 'a bare-RegExp entry ignores the verb');
|
|
203
|
-
assert.equal(isExempt('/api/
|
|
203
|
+
assert.equal(isExempt('/api/bongos/instance', 'GET'), true);
|
|
204
204
|
});
|
|
205
205
|
|
|
206
206
|
// task 1003525 — the other half of the apply door. `bongos login` cannot re-poll the
|
|
@@ -210,15 +210,15 @@ test('the status poll: GET <api>/access-requests/status answers a stranger — t
|
|
|
210
210
|
for (const p of [
|
|
211
211
|
'/api/gds/access-requests/status', '/api/bongos/access-requests/status',
|
|
212
212
|
'/api/bongos/v1/access-requests/status',
|
|
213
|
-
'/API/BONGOS/ACCESS-REQUESTS/STATUS', '/api/
|
|
213
|
+
'/API/BONGOS/ACCESS-REQUESTS/STATUS', '/api/bongos/access-requests/status/',
|
|
214
214
|
]) assert.equal(isExempt(p, 'GET'), true, `GET ${p} must reach the status route`);
|
|
215
215
|
|
|
216
216
|
// the two halves do not lend each other their verbs
|
|
217
|
-
assert.equal(isExempt('/api/
|
|
218
|
-
assert.equal(isExempt('/api/
|
|
217
|
+
assert.equal(isExempt('/api/bongos/access-requests/status', 'POST'), false, 'the status route takes no write');
|
|
218
|
+
assert.equal(isExempt('/api/bongos/access-requests', 'GET'), false, 'the owner queue is still not a status poll');
|
|
219
219
|
|
|
220
220
|
// and neither opens the review surfaces that share the prefix
|
|
221
|
-
for (const p of ['/api/
|
|
221
|
+
for (const p of ['/api/bongos/access-requests/invite', '/api/bongos/access-requests/1/approve', '/api/bongos/access-requests/1']) {
|
|
222
222
|
for (const m of ['GET', 'POST']) {
|
|
223
223
|
assert.equal(isExempt(p, m), false, `${m} ${p} must stay gated`);
|
|
224
224
|
}
|
|
@@ -238,10 +238,10 @@ test('gated mode: an exempt path is never looked up, and a CORS preflight passes
|
|
|
238
238
|
});
|
|
239
239
|
|
|
240
240
|
test('the sign-in return is the knocked-on page for GET/HEAD only, relative, and never a foreign host', () => {
|
|
241
|
-
assert.equal(signInHref(fakeReq({ path: '/builders/roadmap' })), '/api/
|
|
242
|
-
assert.equal(signInHref(fakeReq({ path: '/builders/roadmap', method: 'POST' })), '/api/
|
|
243
|
-
assert.equal(signInHref(fakeReq({ path: '//evil.example/x' })), '/api/
|
|
244
|
-
assert.equal(signInHref(fakeReq({ path: '/' })), '/api/
|
|
241
|
+
assert.equal(signInHref(fakeReq({ path: '/builders/roadmap' })), '/api/bongos/auth/web/start?return=%2Fbuilders%2Froadmap');
|
|
242
|
+
assert.equal(signInHref(fakeReq({ path: '/builders/roadmap', method: 'POST' })), '/api/bongos/auth/web/start', 'a write has nothing safe to replay');
|
|
243
|
+
assert.equal(signInHref(fakeReq({ path: '//evil.example/x' })), '/api/bongos/auth/web/start', 'a protocol-relative path is dropped');
|
|
244
|
+
assert.equal(signInHref(fakeReq({ path: '/' })), '/api/bongos/auth/web/start', 'the front page needs no return');
|
|
245
245
|
});
|
|
246
246
|
|
|
247
247
|
// ── 2. the real booted app, pack = gated ─────────────────────────────────────
|
|
@@ -365,7 +365,7 @@ test('booted gated + apply: the status poll answers, and the review surfaces bes
|
|
|
365
365
|
assert.equal(j.error.code, 'bad_github_login', `GET ${p}: the status route answered, not the member door`);
|
|
366
366
|
}
|
|
367
367
|
// the owner's review surfaces share the prefix and stay behind the door
|
|
368
|
-
for (const p of ['/api/
|
|
368
|
+
for (const p of ['/api/bongos/access-requests', '/api/bongos/access-requests/invite']) {
|
|
369
369
|
const res = await request(p);
|
|
370
370
|
assert.equal(res.status, 401, `GET ${p} stays gated`);
|
|
371
371
|
assert.equal(JSON.parse(res.body).error.code, 'unauthenticated');
|
package/tests/profile_route.mjs
CHANGED
|
@@ -198,7 +198,7 @@ function buildApp() {
|
|
|
198
198
|
const app = express();
|
|
199
199
|
app.use(express.json());
|
|
200
200
|
app.use(attachFail); // res.fail() ahead of the routers, exactly as the real app does
|
|
201
|
-
app.use('/api/
|
|
201
|
+
app.use('/api/bongos', buildBuildersRouter());
|
|
202
202
|
return app;
|
|
203
203
|
}
|
|
204
204
|
|
|
@@ -227,7 +227,7 @@ function get(port, path, headers) {
|
|
|
227
227
|
const { server, port } = await listen(buildApp());
|
|
228
228
|
|
|
229
229
|
await t('known id → 200 with the public field subset', async () => {
|
|
230
|
-
const r = await get(port, '/api/
|
|
230
|
+
const r = await get(port, '/api/bongos/builders/95/profile');
|
|
231
231
|
assert.equal(r.status, 200);
|
|
232
232
|
assert.ok(r.json && r.json.builder, 'response has a builder object');
|
|
233
233
|
for (const k of PUBLIC_KEYS) {
|
|
@@ -242,7 +242,7 @@ await t('known id → 200 with the public field subset', async () => {
|
|
|
242
242
|
});
|
|
243
243
|
|
|
244
244
|
await t('known id → NO private column leaks on the builder object', async () => {
|
|
245
|
-
const r = await get(port, '/api/
|
|
245
|
+
const r = await get(port, '/api/bongos/builders/95/profile');
|
|
246
246
|
assert.equal(r.status, 200);
|
|
247
247
|
for (const k of PRIVATE_KEYS) {
|
|
248
248
|
assert.ok(!(k in r.json.builder), `private field "${k}" must NOT be present on the builder`);
|
|
@@ -250,7 +250,7 @@ await t('known id → NO private column leaks on the builder object', async () =
|
|
|
250
250
|
});
|
|
251
251
|
|
|
252
252
|
await t('known id → no secret sentinel leaks ANYWHERE in the response body', async () => {
|
|
253
|
-
const r = await get(port, '/api/
|
|
253
|
+
const r = await get(port, '/api/bongos/builders/95/profile');
|
|
254
254
|
assert.equal(r.status, 200);
|
|
255
255
|
for (const secret of [SECRET_EMAIL, SECRET_USER, SECRET_REASON]) {
|
|
256
256
|
assert.ok(!r.text.includes(secret), `response body must not contain the secret "${secret}"`);
|
|
@@ -258,13 +258,13 @@ await t('known id → no secret sentinel leaks ANYWHERE in the response body', a
|
|
|
258
258
|
});
|
|
259
259
|
|
|
260
260
|
await t('unknown id → 404 builder_not_found', async () => {
|
|
261
|
-
const r = await get(port, '/api/
|
|
261
|
+
const r = await get(port, '/api/bongos/builders/99999/profile');
|
|
262
262
|
assert.equal(r.status, 404);
|
|
263
263
|
assert.equal(r.json && r.json.error && r.json.error.code, 'builder_not_found');
|
|
264
264
|
});
|
|
265
265
|
|
|
266
266
|
await t('non-numeric id → 400 bad_builder_id', async () => {
|
|
267
|
-
const r = await get(port, '/api/
|
|
267
|
+
const r = await get(port, '/api/bongos/builders/not-a-number/profile');
|
|
268
268
|
assert.equal(r.status, 400);
|
|
269
269
|
assert.equal(r.json && r.json.error && r.json.error.code, 'bad_builder_id');
|
|
270
270
|
});
|
|
@@ -275,7 +275,7 @@ await t('non-numeric id → 400 bad_builder_id', async () => {
|
|
|
275
275
|
// hub posture for the tests below.
|
|
276
276
|
|
|
277
277
|
await t('hub port ABSENT → no cross_project key + the cacheable posture stays', async () => {
|
|
278
|
-
const r = await get(port, '/api/
|
|
278
|
+
const r = await get(port, '/api/bongos/builders/95/profile');
|
|
279
279
|
assert.equal(r.status, 200);
|
|
280
280
|
assert.ok(!('cross_project' in r.json), 'cross_project must be absent on a non-hub instance');
|
|
281
281
|
assert.equal(r.headers['cache-control'], 'public, max-age=60');
|
|
@@ -292,7 +292,7 @@ seams.registerProvider('platform-identity.hub', {
|
|
|
292
292
|
});
|
|
293
293
|
|
|
294
294
|
await t('hub port present → cross_project carries exactly the whitelist shape', async () => {
|
|
295
|
-
const r = await get(port, '/api/
|
|
295
|
+
const r = await get(port, '/api/bongos/builders/95/profile');
|
|
296
296
|
assert.equal(r.status, 200);
|
|
297
297
|
const cp = r.json.cross_project;
|
|
298
298
|
assert.ok(cp, 'cross_project attached when the port resolves');
|
|
@@ -302,7 +302,7 @@ await t('hub port present → cross_project carries exactly the whitelist shape'
|
|
|
302
302
|
});
|
|
303
303
|
|
|
304
304
|
await t('hub port present → module-internal sentinels leak NOWHERE in the response', async () => {
|
|
305
|
-
const r = await get(port, '/api/
|
|
305
|
+
const r = await get(port, '/api/bongos/builders/95/profile');
|
|
306
306
|
assert.equal(r.status, 200);
|
|
307
307
|
for (const secret of [SECRET_CLIENT_ID, SECRET_PROJECT_NAME, SECRET_EMAIL, SECRET_USER, SECRET_REASON]) {
|
|
308
308
|
assert.ok(!r.text.includes(secret), `response body must not contain "${secret}"`);
|
|
@@ -311,13 +311,13 @@ await t('hub port present → module-internal sentinels leak NOWHERE in the resp
|
|
|
311
311
|
});
|
|
312
312
|
|
|
313
313
|
await t('hub port present → hide must beat the cache: Cache-Control is no-store', async () => {
|
|
314
|
-
const r = await get(port, '/api/
|
|
314
|
+
const r = await get(port, '/api/bongos/builders/95/profile');
|
|
315
315
|
assert.equal(r.headers['cache-control'], 'no-store');
|
|
316
316
|
});
|
|
317
317
|
|
|
318
318
|
await t('hub port returns null (hidden/provisional/none) → cross_project: null, still no-store', async () => {
|
|
319
319
|
hubExtras = null;
|
|
320
|
-
const r = await get(port, '/api/
|
|
320
|
+
const r = await get(port, '/api/bongos/builders/95/profile');
|
|
321
321
|
assert.equal(r.status, 200);
|
|
322
322
|
assert.ok('cross_project' in r.json, 'the key is present on the hub');
|
|
323
323
|
assert.equal(r.json.cross_project, null, 'null — no partial numbers, indistinguishable from no-account');
|
|
@@ -472,7 +472,7 @@ const buildScoutingRoutes = require('../modules/platform-identity/routes/scoutin
|
|
|
472
472
|
hubPublicRead = (githubId) => pi.getPublicProfileExtras(githubId, { pool: engine });
|
|
473
473
|
|
|
474
474
|
await t('R06.1 a two-project builder\'s COMBINED record shows to another account', async () => {
|
|
475
|
-
const r = await get(port, '/api/
|
|
475
|
+
const r = await get(port, '/api/bongos/builders/95/profile');
|
|
476
476
|
assert.equal(r.status, 200);
|
|
477
477
|
const cp = r.json.cross_project;
|
|
478
478
|
assert.ok(cp, 'the record is served');
|
|
@@ -500,7 +500,7 @@ await t('R06.1 a name needs the map AND the Public door — every other shape is
|
|
|
500
500
|
try {
|
|
501
501
|
for (const [label, catalog] of Object.entries(unnamed)) {
|
|
502
502
|
other.catalog = catalog;
|
|
503
|
-
const r = await get(port, '/api/
|
|
503
|
+
const r = await get(port, '/api/bongos/builders/95/profile');
|
|
504
504
|
const cp = r.json.cross_project;
|
|
505
505
|
assert.deepEqual(cp.projects.map((p) => p.name), [pub.name], `${label}: only the Public project is named`);
|
|
506
506
|
assert.equal(cp.more_projects, 1, `${label}: counted`);
|
|
@@ -509,7 +509,7 @@ await t('R06.1 a name needs the map AND the Public door — every other shape is
|
|
|
509
509
|
// The positive control: the same row IS named once Public and on the map, so the
|
|
510
510
|
// refusals above are the door, not a fixture nothing could ever name.
|
|
511
511
|
other.catalog = { ...original, visibility: 'public' };
|
|
512
|
-
const r = await get(port, '/api/
|
|
512
|
+
const r = await get(port, '/api/bongos/builders/95/profile');
|
|
513
513
|
assert.deepEqual(r.json.cross_project.projects.map((p) => p.name), [pub.name, SECRET_PROJECT_NAME]);
|
|
514
514
|
} finally {
|
|
515
515
|
other.catalog = original;
|
|
@@ -517,8 +517,8 @@ await t('R06.1 a name needs the map AND the Public door — every other shape is
|
|
|
517
517
|
});
|
|
518
518
|
|
|
519
519
|
await t('R06.1 the record is viewer-INDEPENDENT — another account sees the same body', async () => {
|
|
520
|
-
const anon = await get(port, '/api/
|
|
521
|
-
const other = await get(port, '/api/
|
|
520
|
+
const anon = await get(port, '/api/bongos/builders/95/profile');
|
|
521
|
+
const other = await get(port, '/api/bongos/builders/95/profile', { Cookie: 'gds_session=some-other-builder' });
|
|
522
522
|
assert.equal(other.status, 200);
|
|
523
523
|
assert.equal(other.text, anon.text, 'one response shape for every reader (ADR 0171 D4)');
|
|
524
524
|
});
|
|
@@ -564,7 +564,7 @@ await t('R06.4 NEGATIVE CONTROL: the leak guard goes red when a private field is
|
|
|
564
564
|
try {
|
|
565
565
|
// (a) a module-internal key on the object itself.
|
|
566
566
|
hubPublicRead = async () => ({ ...(await rollup()), client_id: SECRET_CLIENT_ID });
|
|
567
|
-
let r = await get(port, '/api/
|
|
567
|
+
let r = await get(port, '/api/bongos/builders/95/profile');
|
|
568
568
|
assert.throws(() => assertCrossProjectContract(r.json.cross_project, r.text),
|
|
569
569
|
/cross_project keys are exactly the contract/, 'an extra top-level key must turn the guard red');
|
|
570
570
|
|
|
@@ -573,7 +573,7 @@ await t('R06.4 NEGATIVE CONTROL: the leak guard goes red when a private field is
|
|
|
573
573
|
const cp = await rollup();
|
|
574
574
|
return { ...cp, projects: cp.projects.map((p) => ({ ...p, is_public: true })) };
|
|
575
575
|
};
|
|
576
|
-
r = await get(port, '/api/
|
|
576
|
+
r = await get(port, '/api/bongos/builders/95/profile');
|
|
577
577
|
assert.throws(() => assertCrossProjectContract(r.json.cross_project, r.text),
|
|
578
578
|
/project rows carry only the whitelisted keys/, 'an extra project-row key must turn the guard red');
|
|
579
579
|
|
|
@@ -583,7 +583,7 @@ await t('R06.4 NEGATIVE CONTROL: the leak guard goes red when a private field is
|
|
|
583
583
|
const cp = await rollup();
|
|
584
584
|
return { ...cp, projects: [{ ...cp.projects[0], name: SECRET_PROJECT_NAME }] };
|
|
585
585
|
};
|
|
586
|
-
r = await get(port, '/api/
|
|
586
|
+
r = await get(port, '/api/bongos/builders/95/profile');
|
|
587
587
|
assert.deepEqual(Object.keys(r.json.cross_project).sort(), [...CP_PUBLIC_KEYS].sort(),
|
|
588
588
|
'the planted leak is shape-legal — only the sentinel scan can catch it');
|
|
589
589
|
assert.throws(() => assertCrossProjectContract(r.json.cross_project, r.text),
|
|
@@ -594,7 +594,7 @@ await t('R06.4 NEGATIVE CONTROL: the leak guard goes red when a private field is
|
|
|
594
594
|
});
|
|
595
595
|
|
|
596
596
|
await t('R06.2 hide_stats removes the record on the IMMEDIATE next load', async () => {
|
|
597
|
-
const before = await get(port, '/api/
|
|
597
|
+
const before = await get(port, '/api/bongos/builders/95/profile');
|
|
598
598
|
assert.ok(before.json.cross_project, 'public before the flip');
|
|
599
599
|
assert.equal(before.headers['cache-control'], 'no-store');
|
|
600
600
|
|
|
@@ -604,7 +604,7 @@ await t('R06.2 hide_stats removes the record on the IMMEDIATE next load', async
|
|
|
604
604
|
assert.equal(row.hide_stats, true, 'the write path set the flag');
|
|
605
605
|
|
|
606
606
|
// No sleep, no second server, no cache warm-up: the very next load.
|
|
607
|
-
const after = await get(port, '/api/
|
|
607
|
+
const after = await get(port, '/api/bongos/builders/95/profile');
|
|
608
608
|
assert.equal(after.status, 200);
|
|
609
609
|
assert.equal(after.json.cross_project, null, 'gone on the next load');
|
|
610
610
|
assert.equal(after.headers['cache-control'], 'no-store', 'hide must beat the cache (ADR 0171 D5)');
|
|
@@ -616,7 +616,7 @@ await t('R06.5 no PARTIAL numbers survive the hide (a count beside a hidden tota
|
|
|
616
616
|
// Set the flag here too, so this proof stands on its own rather than on
|
|
617
617
|
// whatever R06.2 left behind.
|
|
618
618
|
await pi.setAccountHideStats({ githubId: HUB_GITHUB_ID, githubLogin: 'jaxri', hide: true }, { pool: engine });
|
|
619
|
-
const r = await get(port, '/api/
|
|
619
|
+
const r = await get(port, '/api/bongos/builders/95/profile');
|
|
620
620
|
assert.equal(r.json.cross_project, null);
|
|
621
621
|
for (const key of ['totals', 'works_shipped', 'more_projects', 'last_active', 'rank_held']) {
|
|
622
622
|
assert.ok(!r.text.includes(key), `nothing derived from the rollup may survive — found "${key}"`);
|
|
@@ -632,12 +632,12 @@ await t('R06.5 no PARTIAL numbers survive the hide (a count beside a hidden tota
|
|
|
632
632
|
|
|
633
633
|
const scoutApp = express();
|
|
634
634
|
scoutApp.use(attachFail);
|
|
635
|
-
scoutApp.use('/api/
|
|
635
|
+
scoutApp.use('/api/bongos', buildScoutingRoutes());
|
|
636
636
|
const scout = await listen(scoutApp);
|
|
637
637
|
|
|
638
638
|
await t('R06.3 GET /scouting drops the hidden, the provisional AND the private builder', async () => {
|
|
639
639
|
engine.calls.length = 0;
|
|
640
|
-
const r = await get(scout.port, '/api/
|
|
640
|
+
const r = await get(scout.port, '/api/bongos/scouting');
|
|
641
641
|
assert.equal(r.status, 200);
|
|
642
642
|
const logins = r.json.builders.map((b) => b.github_login);
|
|
643
643
|
assert.deepEqual(logins, ['r06-visible'],
|
|
@@ -651,8 +651,19 @@ await t('R06.3 GET /scouting drops the hidden, the provisional AND the private b
|
|
|
651
651
|
await t('R06.3 the /people page reads /scouting and nothing else', async () => {
|
|
652
652
|
// /people is the hall's render of the same endpoint, so whatever /scouting
|
|
653
653
|
// drops, /people drops. Pin that it has no second data source.
|
|
654
|
-
|
|
655
|
-
|
|
654
|
+
//
|
|
655
|
+
// MATCHED AS AN EXPRESSION, NOT A STRING LITERAL. This used to capture the
|
|
656
|
+
// whole URL out of `fetch('…')` and compare it to one hardcoded string, which
|
|
657
|
+
// pinned the row limit as a side effect of pinning the ENDPOINT — so task
|
|
658
|
+
// 1003110 naming that limit (`DIRECTORY_LIMIT`, the one constant that feeds
|
|
659
|
+
// both the request and the "showing the first N" copy) failed a test about
|
|
660
|
+
// data sources. What is load-bearing here is the count and the destination:
|
|
661
|
+
// ONE read, and it goes to the scouting rollup. How the query string is
|
|
662
|
+
// assembled is the page's business.
|
|
663
|
+
const args = [...sourceOf('modules/hall-ui/public/people.js').matchAll(/\bfetch\(\s*([^,)]+)/g)].map((m) => m[1].trim());
|
|
664
|
+
assert.equal(args.length, 1, '/people has exactly one data source');
|
|
665
|
+
assert.match(args[0], /^'\/api\/bongos\/scouting\?limit='/,
|
|
666
|
+
'and it is the scouting rollup — nothing may re-enter through a second endpoint');
|
|
656
667
|
});
|
|
657
668
|
|
|
658
669
|
scout.server.close();
|