@bongos/core 1.19.1015 → 1.19.1017
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 +159 -94
- package/.claude/skills/planning-session/SKILL.md +1 -1
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -0
- package/clients/bongos-client/index.cjs +4 -0
- package/clients/bongos-client/index.d.ts +7 -2
- package/clients/bongos-client/index.mjs +4 -0
- package/docs/adr/0331-the-sky-is-the-ideators-home.md +12 -0
- package/docs/adr/0334-the-recruiting-directory-lists-a-hidden-builder-by-name.md +47 -0
- package/docs/adr/0335-an-invite-is-a-pre-approved-row-on-the-projects-own-instance.md +113 -0
- package/docs/adr/0336-a-guilds-record-is-the-sum-of-what-its-members-already-show.md +201 -0
- package/docs/adr/README.md +3 -0
- package/docs/api/openapi.json +60 -5
- package/docs/api-reference.md +8 -2
- package/docs/architecture.md +4 -1
- package/docs/copy-inventory.md +5 -3
- package/docs/copy-registry.json +22 -4
- package/docs/file-map.md +1 -2
- package/docs/module-api-changelog.md +5 -1
- package/migrations/core_253_idea_filed_via.sql +59 -0
- package/modules/agents/pollers/event-router.js +2 -0
- package/modules/discord/discord-bugs.js +2 -0
- package/modules/discord/discord-inbound.js +3 -0
- package/modules/hall-ui/public/people.html +11 -0
- package/modules/hall-ui/public/people.js +77 -11
- package/modules/hall-ui/public/people.states.json +1 -1
- package/modules/hall-ui/public/sky-data.js +22 -8
- package/modules/hall-ui/public/sky-draw.js +3 -2
- package/modules/hall-ui/public/sky-panel.js +1 -1
- package/modules/ideas/born-of.js +55 -0
- package/modules/ideas/inbox.js +23 -8
- package/modules/ideas/projection.js +48 -0
- package/modules/ideas/routes/inbox.js +20 -0
- package/modules/lifecycle/sky.js +25 -4
- package/modules/platform-identity/account-visibility.js +33 -2
- package/modules/platform-identity/activity-snapshots.js +47 -0
- package/modules/platform-identity/builder-search.js +153 -0
- package/modules/platform-identity/migrations/platform_identity_022_activity_snapshots.sql +46 -0
- package/modules/platform-identity/migrations/platform_identity_023_builder_search_trgm.sql +40 -0
- package/modules/platform-identity/module.json +1 -1
- package/modules/platform-identity/platform-identity.js +13 -0
- package/modules/platform-identity/routes/community-search.js +120 -0
- package/modules/platform-identity/routes/scouting.js +14 -7
- package/modules/platform-identity/scouting-name-only.js +47 -0
- package/modules/platform-identity/tests/activity-snapshots.mjs +68 -0
- package/modules/platform-identity/tests/platform-identity.mjs +21 -0
- package/modules/public-landing/public/projects-floor.states.json +9 -2
- package/modules/public-landing/public/projects.html +69 -4
- package/modules/ui-design/skills/impeccable/SKILL.md +1 -1
- package/modules/ui-design/styles/expedition/mock.html +16 -3
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/capture.js +11 -0
- package/scripts/gds/run-unit-tests.js +4 -0
- package/src/module-api.js +1 -1
- package/tests/account_privacy_flags.mjs +4 -1
- package/tests/activity_snapshots_db.mjs +97 -0
- package/tests/card_apply_ui.mjs +297 -0
- package/tests/community_search_route.mjs +385 -0
- package/tests/discord_bugs.mjs +1 -0
- package/tests/discord_inbound.mjs +1 -0
- package/tests/effective_visibility_predicate.mjs +49 -1
- package/tests/goal_map_page.mjs +14 -0
- package/tests/guild_namespace_reserved.mjs +76 -0
- package/tests/hall_sky.mjs +33 -16
- package/tests/idea_filed_via.mjs +185 -0
- package/tests/landing_page.mjs +8 -0
- package/tests/people_name_only.mjs +262 -0
- package/tests/profile_rollup_consent.mjs +5 -1
- package/tests/profile_route.mjs +23 -5
- package/tests/project_admin_console_model.mjs +105 -0
- package/tests/recruiter_sliver_boundary.mjs +3 -0
- package/tests/scouting_owner_access_and_reach.mjs +112 -2
- package/tests/sky_read.mjs +19 -2
- package/tests/ui_design_skills.mjs +9 -0
- package/tests/ui_design_styles.mjs +13 -0
- package/modules/public-landing/public/world/assets/clouds/cloud-island-large.png +0 -0
- package/modules/public-landing/public/world/assets/clouds/cloud-puff.png +0 -0
- package/modules/public-landing/public/world/assets/creatures/creature-whale-cargo.png +0 -0
- package/modules/public-landing/public/world/assets/creatures/creature-whale-glide.png +0 -0
- package/modules/public-landing/public/world/assets/plates/plate-clouds-near.png +0 -0
- package/modules/public-landing/public/world/assets/plates/scene-establishing.png +0 -0
package/docs/api/openapi.json
CHANGED
|
@@ -89,6 +89,10 @@
|
|
|
89
89
|
"name": "closeness",
|
|
90
90
|
"description": "Endpoints under `/api/bongos/closeness`."
|
|
91
91
|
},
|
|
92
|
+
{
|
|
93
|
+
"name": "community",
|
|
94
|
+
"description": "Endpoints under `/api/bongos/community`."
|
|
95
|
+
},
|
|
92
96
|
{
|
|
93
97
|
"name": "connections",
|
|
94
98
|
"description": "Endpoints under `/api/bongos/connections`."
|
|
@@ -4391,6 +4395,34 @@
|
|
|
4391
4395
|
]
|
|
4392
4396
|
}
|
|
4393
4397
|
},
|
|
4398
|
+
"/community/search": {
|
|
4399
|
+
"get": {
|
|
4400
|
+
"operationId": "get_community_search",
|
|
4401
|
+
"tags": [
|
|
4402
|
+
"community"
|
|
4403
|
+
],
|
|
4404
|
+
"summary": "GET /community/search",
|
|
4405
|
+
"description": "a name/discipline search under EXACTLY the public-profile visibility rules (D4): a hide_stats account is findable by name but drops out of a discipline-filtered result (disciplines are rollup-derived, and the rollup is what hide_stats suppresses); private and provisional accounts never appear. no-store: the same hide-must-beat-the-cache posture as /profiles/:handle (ADR 0171 D5) — this reads the identical predicate and a flip must be reflected on the very next request.\n\n**Rank:** `public` — No authentication — any caller.",
|
|
4406
|
+
"x-rank": "public",
|
|
4407
|
+
"x-source": "modules/platform-identity/routes/community-search.js",
|
|
4408
|
+
"responses": {
|
|
4409
|
+
"200": {
|
|
4410
|
+
"description": "Success.",
|
|
4411
|
+
"content": {
|
|
4412
|
+
"application/json": {
|
|
4413
|
+
"schema": {
|
|
4414
|
+
"$ref": "#/components/schemas/GetCommunitySearchResponse"
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
},
|
|
4419
|
+
"400": {
|
|
4420
|
+
"$ref": "#/components/responses/BadRequest"
|
|
4421
|
+
}
|
|
4422
|
+
},
|
|
4423
|
+
"security": []
|
|
4424
|
+
}
|
|
4425
|
+
},
|
|
4394
4426
|
"/connections": {
|
|
4395
4427
|
"get": {
|
|
4396
4428
|
"operationId": "get_connections",
|
|
@@ -19808,6 +19840,23 @@
|
|
|
19808
19840
|
"why"
|
|
19809
19841
|
]
|
|
19810
19842
|
},
|
|
19843
|
+
"GetCommunitySearchResponse": {
|
|
19844
|
+
"type": "object",
|
|
19845
|
+
"properties": {
|
|
19846
|
+
"query": {},
|
|
19847
|
+
"discipline": {},
|
|
19848
|
+
"count": {},
|
|
19849
|
+
"results": {},
|
|
19850
|
+
"page": {}
|
|
19851
|
+
},
|
|
19852
|
+
"required": [
|
|
19853
|
+
"query",
|
|
19854
|
+
"discipline",
|
|
19855
|
+
"count",
|
|
19856
|
+
"results",
|
|
19857
|
+
"page"
|
|
19858
|
+
]
|
|
19859
|
+
},
|
|
19811
19860
|
"GetConnectionsDegreeHandleResponse": {
|
|
19812
19861
|
"type": "object",
|
|
19813
19862
|
"properties": {
|
|
@@ -20934,10 +20983,12 @@
|
|
|
20934
20983
|
"GetScoutingResponse": {
|
|
20935
20984
|
"type": "object",
|
|
20936
20985
|
"properties": {
|
|
20937
|
-
"builders": {}
|
|
20986
|
+
"builders": {},
|
|
20987
|
+
"name_only": {}
|
|
20938
20988
|
},
|
|
20939
20989
|
"required": [
|
|
20940
|
-
"builders"
|
|
20990
|
+
"builders",
|
|
20991
|
+
"name_only"
|
|
20941
20992
|
]
|
|
20942
20993
|
},
|
|
20943
20994
|
"GetSecurityDocsResponse": {
|
|
@@ -24157,6 +24208,10 @@
|
|
|
24157
24208
|
"recommendation": {
|
|
24158
24209
|
"type": "string",
|
|
24159
24210
|
"maxLength": 16
|
|
24211
|
+
},
|
|
24212
|
+
"filed_via": {
|
|
24213
|
+
"type": "string",
|
|
24214
|
+
"maxLength": 16
|
|
24160
24215
|
}
|
|
24161
24216
|
},
|
|
24162
24217
|
"required": [
|
|
@@ -26414,9 +26469,9 @@
|
|
|
26414
26469
|
"description": "A required dependency/feature is not configured or is temporarily down."
|
|
26415
26470
|
}
|
|
26416
26471
|
},
|
|
26417
|
-
"x-endpoint-count":
|
|
26418
|
-
"x-schema-count":
|
|
26472
|
+
"x-endpoint-count": 417,
|
|
26473
|
+
"x-schema-count": 454,
|
|
26419
26474
|
"x-undocumented-bodies": 9,
|
|
26420
|
-
"x-response-schemas":
|
|
26475
|
+
"x-response-schemas": 309,
|
|
26421
26476
|
"x-generated-by": "scripts/gds/gen-api-docs.js"
|
|
26422
26477
|
}
|
package/docs/api-reference.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Bongos API reference
|
|
4
4
|
|
|
5
|
-
> **Generated from the live route files** — the route file is authoritative.
|
|
5
|
+
> **Generated from the live route files** — the route file is authoritative. 417 endpoints across 71 route files.
|
|
6
6
|
> Machine-readable spec: [`docs/api/openapi.json`](api/openapi.json) (OpenAPI 3.1). Rendered docs site: **`/docs`** (e.g. `cloudbongos.com/docs`).
|
|
7
7
|
|
|
8
8
|
Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust-boundary-server-enforced-permissions.md)): `public` < `any-builder` < `metic+archon` < `archon`.
|
|
@@ -187,6 +187,12 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
|
|
|
187
187
|
| GET | `/api/bongos/closeness/weights` | `any-builder` | — | the MODEL is not private, and publishing it is the whole point of the weights being data. |
|
|
188
188
|
| GET | `/api/bongos/closeness/with/:builderId` | `any-builder` | — | One pair — "why am I close to THEM", asked directly rather than by paging the graph. |
|
|
189
189
|
|
|
190
|
+
## `community` (1)
|
|
191
|
+
|
|
192
|
+
| Method | Path | Rank | Body | Description |
|
|
193
|
+
|---|---|---|---|---|
|
|
194
|
+
| GET | `/api/bongos/community/search` | `public` | — | a name/discipline search under EXACTLY the public-profile visibility rules (D4): a hide_stats account is findable by name but drops out o… |
|
|
195
|
+
|
|
190
196
|
## `connections` (7)
|
|
191
197
|
|
|
192
198
|
| Method | Path | Rank | Body | Description |
|
|
@@ -347,7 +353,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
|
|
|
347
353
|
| Method | Path | Rank | Body | Description |
|
|
348
354
|
|---|---|---|---|---|
|
|
349
355
|
| GET | `/api/bongos/inbox` | `any-builder` | — | read open ideas; skill-friction rows filtered by viewer rank. |
|
|
350
|
-
| POST | `/api/bongos/inbox` | `any-builder` | `title`, `body_md`, `kind`, `suggested_version`, `suggested_priority`, `goal_id`, `grade`, `template_key`, `field_values`, `recommendation` | for ordinary ideas; metic+ for kind='criterion-proposal'. |
|
|
356
|
+
| POST | `/api/bongos/inbox` | `any-builder` | `title`, `body_md`, `kind`, `suggested_version`, `suggested_priority`, `goal_id`, `grade`, `template_key`, `field_values`, `recommendation`, `filed_via` | for ordinary ideas; metic+ for kind='criterion-proposal'. |
|
|
351
357
|
| GET | `/api/bongos/inbox/:id` | `any-builder` | — | single idea read for the "#NNN" deep-link resolver (task 1047). |
|
|
352
358
|
| PATCH | `/api/bongos/inbox/:id` | `metic+archon` | `status`, `kind`, `promoted_to_task_id`, `reviewed_at`, `merged_into_idea_id`, `triage_note` | |
|
|
353
359
|
| PATCH | `/api/bongos/inbox/:id/body` | `any-builder` | `body_md` | Metic+ (task 360 / ADR 0018): idea triage (promote/discard/merge) shapes the backlog. |
|
package/docs/architecture.md
CHANGED
|
@@ -166,9 +166,12 @@ idea_inbox(id, title, body_md, kind, suggested_version, suggested_priority, capt
|
|
|
166
166
|
merged_into_idea_id? FK idea_inbox, triage_note?, -- mig core_195 (task 1002447): what a
|
|
167
167
|
-- triage VERDICT decided -- which idea a merge folded into, and why a discard happened.
|
|
168
168
|
-- CHECK (merged_into_idea_id IS NULL OR status = 'merged') -- no merge chains.
|
|
169
|
-
suggested_goal_id? FK goals ON DELETE SET NULL
|
|
169
|
+
suggested_goal_id? FK goals ON DELETE SET NULL, -- mig core_222 (task 1003060, goal 1000071):
|
|
170
170
|
-- an ADVISORY routing hint for rows that did NOT convert (homeless/soft-landed/Discord).
|
|
171
171
|
-- The binding home for routed work is tasks.goal_id; nothing may gate on this column.
|
|
172
|
+
filed_via? text CHECK IN ('hall'|'cli'|'discord'|'planning'|'api')) -- mig core_253_idea_filed_via (task 1004233, sky part 5):
|
|
173
|
+
-- WHICH DOOR filed it, read off the SESSION (web=hall, cli=cli, else api; 'planning' by capture.js
|
|
174
|
+
-- --via planning; the Discord bot names 'discord'). NULL = older than the column; never backfilled.
|
|
172
175
|
done_when_criteria(id, version_id, criterion_id, criterion_md, satisfied bool, satisfied_by_task_id,
|
|
173
176
|
checked_at, sort_order, UNIQUE(version_id, criterion_id)) -- 13 seeded (V1 + PMS-V2)
|
|
174
177
|
task_criteria(task_id FK tasks, criterion_id FK done_when_criteria, source 'backfill'|'manual'|'planning',
|
package/docs/copy-inventory.md
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
> Deterministic on a given tree — regenerate and commit rather than hand-editing.
|
|
5
5
|
> Machine-readable twin: [`copy-registry.json`](copy-registry.json).
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**2037 strings** across 3 surfaces · 1738 distinct pieces of copy · 2037 certain, 0 heuristic · 212 shown in more than one place.
|
|
8
8
|
|
|
9
9
|
## Surfaces
|
|
10
10
|
|
|
11
11
|
| surface | lives at | files | strings |
|
|
12
12
|
|---|---|---|---|
|
|
13
|
-
| `builders-hall` | `modules/hall-ui/public` | 102 |
|
|
13
|
+
| `builders-hall` | `modules/hall-ui/public` | 102 | 1546 |
|
|
14
14
|
| `status-dashboard` | `modules/status-ui/public` | 4 | 100 |
|
|
15
15
|
| `landing` | `modules/public-landing/public` | 5 | 391 |
|
|
16
16
|
|
|
@@ -78,7 +78,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
78
78
|
| `7926e30955d2` | 3x | ← Back to goal | modules/hall-ui/public/goals.html:199, modules/hall-ui/public/goals.html:210, modules/hall-ui/public/goals.html:222 |
|
|
79
79
|
| … | | _172 more in the JSON registry_ | |
|
|
80
80
|
|
|
81
|
-
## `builders-hall` —
|
|
81
|
+
## `builders-hall` — 1546 strings
|
|
82
82
|
|
|
83
83
|
| id | conf | text | where |
|
|
84
84
|
|---|---|---|---|
|
|
@@ -618,6 +618,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
618
618
|
| `a53e0cde7ff7` | certain | Link first — your rank role is set the moment you join. | `modules/hall-ui/public/settings.js:840` |
|
|
619
619
|
| `64e85f1691f9` | certain | Link your Discord account and you're added to the {{worldName}} server with your rank role already set — no in | `modules/hall-ui/public/settings.html:61` |
|
|
620
620
|
| `9a31cf5fe05b` | certain | Linking opens Discord's consent screen; unlinking applies at once. | `modules/hall-ui/public/settings.html:66` |
|
|
621
|
+
| `8e067a257eed` | certain | Listed by name only | `modules/hall-ui/public/people.html:53` |
|
|
621
622
|
| `daf84bba41b1` | certain | Loading goals… | `modules/hall-ui/public/goals.html:44` |
|
|
622
623
|
| `93ceeaa4e226` | certain | Loading idea | `modules/hall-ui/public/idea.html:32` |
|
|
623
624
|
| `2b5826012538` | certain | Loading profile… | `modules/hall-ui/public/profile.html:34` |
|
|
@@ -1114,6 +1115,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
1114
1115
|
| `bcdbded93d4e` | certain | The work board failed to load. Reload to try again. | `modules/hall-ui/public/work.js:1492` |
|
|
1115
1116
|
| `25794c439321` | certain | Then copy the project onto your machine and run the one-shot bootstrap (it checks your setup, installs depende | `modules/hall-ui/public/hall-render.js:890` |
|
|
1116
1117
|
| `df316c3484de` | certain | Then drive it however you like: | `modules/hall-ui/public/hall-render.js:825` |
|
|
1118
|
+
| `48ace42c55ef` | certain | These builders are open to recruiting but keep their cross-project record private, so only who they are is sho | `modules/hall-ui/public/people.html:54` |
|
|
1117
1119
|
| `a0140098c0e0` | certain | They already have an invitation or a join-request pending on this goal. | `modules/hall-ui/public/goals-render.js:1303` |
|
|
1118
1120
|
| `5151cf635644` | certain | They have asked to connect. | `modules/hall-ui/public/profile-connections.js:203` |
|
|
1119
1121
|
| `10219b02433e` | certain | They join as a member once they accept — you can change that afterwards. Nobody is added until they do. | `modules/hall-ui/public/goals-render.js:1235` |
|
package/docs/copy-registry.json
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
"generator": "scripts/gds/copy-inventory.js",
|
|
4
4
|
"goal": 1000074,
|
|
5
5
|
"counts": {
|
|
6
|
-
"strings":
|
|
7
|
-
"unique_ids":
|
|
8
|
-
"certain":
|
|
6
|
+
"strings": 2037,
|
|
7
|
+
"unique_ids": 1738,
|
|
8
|
+
"certain": 2037,
|
|
9
9
|
"heuristic": 0,
|
|
10
10
|
"duplicate_ids": 212
|
|
11
11
|
},
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"builders-hall": {
|
|
14
14
|
"at": "modules/hall-ui/public",
|
|
15
15
|
"files": 102,
|
|
16
|
-
"strings":
|
|
16
|
+
"strings": 1546
|
|
17
17
|
},
|
|
18
18
|
"status-dashboard": {
|
|
19
19
|
"at": "modules/status-ui/public",
|
|
@@ -7071,6 +7071,15 @@
|
|
|
7071
7071
|
"origin": "html-text",
|
|
7072
7072
|
"confidence": "certain"
|
|
7073
7073
|
},
|
|
7074
|
+
{
|
|
7075
|
+
"id": "8e067a257eed",
|
|
7076
|
+
"surface": "builders-hall",
|
|
7077
|
+
"text": "Listed by name only",
|
|
7078
|
+
"file": "modules/hall-ui/public/people.html",
|
|
7079
|
+
"line": 53,
|
|
7080
|
+
"origin": "html-text",
|
|
7081
|
+
"confidence": "certain"
|
|
7082
|
+
},
|
|
7074
7083
|
{
|
|
7075
7084
|
"id": "daf84bba41b1",
|
|
7076
7085
|
"surface": "builders-hall",
|
|
@@ -11535,6 +11544,15 @@
|
|
|
11535
11544
|
"origin": "js-markup",
|
|
11536
11545
|
"confidence": "certain"
|
|
11537
11546
|
},
|
|
11547
|
+
{
|
|
11548
|
+
"id": "48ace42c55ef",
|
|
11549
|
+
"surface": "builders-hall",
|
|
11550
|
+
"text": "These builders are open to recruiting but keep their cross-project record private, so only who they are is shown. They are never ranked, and a role filter never finds them.",
|
|
11551
|
+
"file": "modules/hall-ui/public/people.html",
|
|
11552
|
+
"line": 54,
|
|
11553
|
+
"origin": "html-text",
|
|
11554
|
+
"confidence": "certain"
|
|
11555
|
+
},
|
|
11538
11556
|
{
|
|
11539
11557
|
"id": "a0140098c0e0",
|
|
11540
11558
|
"surface": "builders-hall",
|
package/docs/file-map.md
CHANGED
|
@@ -283,7 +283,6 @@
|
|
|
283
283
|
│ │ ├── world/ ← the six production plates + <file>.json provenance sidecars (embed-prompt.mjs --scan clean): r2-chrome-blobs-set.jpg, r2-chrome-blob-one.jpg, r2-bongos-chrome-b.jpg (black plates, dark mode, lighten) and their -cut.webp alpha twins (light mode); og-card.jpg (the social card, captured from the page)
|
|
284
284
|
│ │ ├── cosmos.css ← the HUB component sheet (task 1002696): contract tier synced to the pack, its own derived + dark tiers, the .btn/.chip/.strip/.proj/.tbl vocabulary; measured by tests/cosmos_token_contrast.mjs
|
|
285
285
|
│ │ └── verro-medallion.png ← the Verro Labs mark (apex-only block)
|
|
286
|
-
│ └── world/assets/ ← the RETIRED gate's clouds/creatures/plates (task 1002407; the gate folded into / in 1003233) — not the chrome plates
|
|
287
286
|
│
|
|
288
287
|
├── public/ ← example.com — the game (product)
|
|
289
288
|
│ ├── index.html
|
|
@@ -376,7 +375,7 @@ tests/
|
|
|
376
375
|
├── canonical_profile_url.mjs ← (task 1002804, goal 1000045 criterion C1, ADR 0214): the P08 proof — the canonical profile URL is resolved in ONE place, `handle` reaches `GET /builders/:id/profile` under the EXISTENCE predicate (so a hidden builder keeps their address), absent/provisional/private are BYTE-IDENTICAL so the numeric-id redirect cannot become an existence oracle, and the widened response still leaks no private column. Real core router over a WHERE-derived fake pool on a real socket; the real browser script in a vm above a stub DOM, run twice — as shipped (every surface dark, because `/u/<handle>` still 404s) and with the liveness constant flipped, so the three live behaviours are proved rather than deferred. Asserts its own DB-free lane membership. DB-free `unit` lane.
|
|
377
376
|
├── application_lifecycle.mjs ← (task 1002284, goal 1000045, ADR 0201 D1/D3.2/D4 + ADR 0208): the application lifecycle EXECUTED — apply, list-pending, approve, decline, RESCIND (`invited → dismissed`), idempotency on the state already held, and the two things that must never happen: a builder without `access_request.review` moving the queue, and a VOUCH buying admission. Real router, REAL `requirePermission` resolving against the real government catalog's rank seed, and the REAL `hasInvitedAccessRequest` from `src/bongos/db-kernel.js` pointed at the same in-memory `access_requests` table (a stubbed `./pool`), whose filter is built from each statement's own WHERE clause and which refuses a predicate it cannot read. The no-authority proof seeds a vouched row and asserts the approve path and the admission read are byte-identical to the unvouched twin, and that no statement on either path names `applicant_github_id`/`vouched_at`. DB-free `unit` lane.
|
|
378
377
|
├── application_echo.mjs ← (task 1002284, goal 1000045, ADR 0201 D1.3/D1.4): the hub's STATUSLESS echo of an application. Part A pins the domain functions — the read binds `APPLICATION_ECHO_WINDOW_DAYS` itself (one 30, not three), the write is an upsert on the pair, neither statement mentions a status, and a non-numeric principal is refused before any query (ADR 0171 D2). Part B runs the real `POST /my-projects/join` and `GET /my-projects` over an in-memory `platform_identity_application_echoes` table with the relay's answer scripted: only an accepted application leaves a trace — a standing membership, either door refusal, a members-only project and an unreachable project leave none — and best-effort is asserted in both directions (a failed echo write still reports the filed application; a failed echo read still lands the memberships listing). DB-free `unit` lane.
|
|
379
|
-
├── scouting_owner_access_and_reach.mjs ← (task 1002290, goal 1000045 C5, ADR 0210): the R14 proof — WHO may read the cross-project recruiting directory, and WHO appears in it. A project owner (an instance-asserted, hub-witnessed `membership_kind='owner'`) reads `GET /scouting` with no rank at all; a builder who is neither owner nor `project.curate` holder gets the unchanged 403; an un-witnessed owner claim is refused; a broken ownership read falls closed onto the permission gate rather than admitting or 500ing. And the recruiting opt-out finally bites: a public, unhidden, active account that opted out is absent from `/scouting` and the hall's `/people` (the same endpoint), returns when it opts back in, and the opt-out grants and denies no permission — an opted-out owner still reads the directory while being absent from their own copy of it. Fake pool filtered by each statement's own WHERE clause, real route, real `requirePermission` over the real government catalog, real HTTP. Mutation-checked
|
|
378
|
+
├── scouting_owner_access_and_reach.mjs ← (task 1002290, goal 1000045 C5, ADR 0210): the R14 proof — WHO may read the cross-project recruiting directory, and WHO appears in it. A project owner (an instance-asserted, hub-witnessed `membership_kind='owner'`) reads `GET /scouting` with no rank at all; a builder who is neither owner nor `project.curate` holder gets the unchanged 403; an un-witnessed owner claim is refused; a broken ownership read falls closed onto the permission gate rather than admitting or 500ing. And the recruiting opt-out finally bites: a public, unhidden, active account that opted out is absent from `/scouting` and the hall's `/people` (the same endpoint), returns when it opts back in, and the opt-out grants and denies no permission — an opted-out owner still reads the directory while being absent from their own copy of it. Since task 1002291 (R15, ADR 0334) it also proves the directory's NAME-ONLY tier: a hidden, reachable builder is listed by login, name and avatar alone, never ranked, and none of their record reaches the body. Fake pool filtered by each statement's own WHERE clause, real route, real `requirePermission` over the real government catalog, real HTTP. Mutation-checked five ways. DB-free `unit` lane. The page half is `people_name_only.mjs` (the real people.js + kit in a vm).
|
|
380
379
|
├── scouting_page_gate.mjs ← (task 1002290, goal 1000045 C5, ADR 0210 D3): the OTHER half of R14 — the `/builders/people` SHELL must admit exactly the two audiences `GET /scouting` admits. A project owner holding no permission reaches it; everyone the route refuses gets the byte-identical `?denied=project.curate` redirect; and each of six ways the `alsoAdmits` door can fail (module absent, function absent, throw, reject, non-boolean, prototype-only) fails CLOSED. Also pins that the other four page gates never consult the seam at all, and that exactly ONE gate declares a second door. Its own file because the route proof's ESM imports pull `auth.js` in before a session double could be installed — this one installs it first, the `government_page_gates.mjs` pattern. DB-free `unit` lane.
|
|
381
380
|
├── terms_acceptance.mjs ← (task 1002913, goal 1000045, ADR 0213): the platform T&C acceptance record. Proves the four properties that make it evidence rather than a claim — the /terms page carries the cross-project consent ADR 0141 §4 deferred to it and it is apex-only; acceptance is written ONLY by the hub's own /sso/authorize (driven over the real router with an attacker-controlled query string, so a forged timestamp/version/activate flag reaches nothing); it is written ONCE (the fake pool interprets the upsert's own column assignments, so COALESCE is executed, not string-matched, and an assignment shape it cannot read throws instead of reading as "unchanged"); and consent is two columns in the shared predicate, so an account the hub witnessed before the terms existed publishes nothing. Plus the projection rule — every read whose row a JS twin judges must select both consent columns, or the twin denies everyone silently. DB-free `unit` lane, asserts its own lane membership.
|
|
382
381
|
├── applicant_profile_boundary.mjs ← (task 1002972, goal 1000045 C4, privacy spec D7, ADR 0241): THE D7 BOUNDARY proof for the applicant view a reviewer sees. Whitelists read from the MODULES’ own exports — the private branch against `recruiter-sliver`’s `SLIVER_KEYS`/`SLIVER_KEYS_HIDDEN`, the public branch against the public-page payload’s key list — so a third shape (the “new disclosure class” D7 forbids) reds this file. Pins THE CONSENT SWAP in both directions on the same account: a private builder who never opted into recruiting gets an applicant view and gets `null` from `getRecruiterSliver`, so a refactor routing the port through the recruiter sliver cannot pass quietly. `hide_stats` beats the application in both branches; provisional and terms-never-accepted disclose nothing; planted sentinels asserted absent from the whole body with the avatar-embedded `github_id` pinned as the one honest exception. Plus the federated gate (ADR 0205): no echo → 404 and NO account row read (asserted on the statement log), the witness matched against the AUTHENTICATED client, an out-of-window echo stops working, and `Cache-Control: no-store`. Real router + real HTTP over in-memory tables filtered by each statement’s own WHERE clause. DB-free `unit` lane, asserted in the file itself.
|
|
@@ -2503,6 +2503,10 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2503
2503
|
1.19.1014 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2504
2504
|
landed since 1.19.1013 with no explicit bump. run 36076310606. (task 1002620)
|
|
2505
2505
|
1.19.1015 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2506
|
-
landed since 1.19.1014 with no explicit bump. run
|
|
2506
|
+
landed since 1.19.1014 with no explicit bump. run 36078624472. (task 1002620)
|
|
2507
|
+
1.19.1016 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2508
|
+
landed since 1.19.1015 with no explicit bump. run 36080388706. (task 1002620)
|
|
2509
|
+
1.19.1017 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2510
|
+
landed since 1.19.1016 with no explicit bump. run 36081753087. (task 1002620)
|
|
2507
2511
|
---------------------------------------------------------------------------
|
|
2508
2512
|
```
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
-- core_253_idea_filed_via.sql — WHERE AN IDEA WAS FILED FROM (task 1004233, sky part 5 of 5;
|
|
2
|
+
-- goal 1000095; ADR 0331 §4).
|
|
3
|
+
--
|
|
4
|
+
-- The sky draws a thought differently when a session filed it under a person's
|
|
5
|
+
-- name rather than the person filing it themselves (the "claude layer", off by
|
|
6
|
+
-- default). The prototype GUESSED that from the idea's text; this column makes
|
|
7
|
+
-- it a ledger fact, written by every door an idea can come through:
|
|
8
|
+
-- 'hall' — the hall's ideas form or the sky's composer (a web session)
|
|
9
|
+
-- 'cli' — scripts/gds/capture.js and spark.js (a CLI session)
|
|
10
|
+
-- 'discord' — the Discord inbound bot (#ideas, #bugs)
|
|
11
|
+
-- 'planning' — a planning session's captures (capture.js --via planning)
|
|
12
|
+
-- 'api' — anything else through POST /inbox, or a program on the port
|
|
13
|
+
-- The closed set lives in modules/ideas/projection.js (FILED_VIA_VALUES); the
|
|
14
|
+
-- CHECK mirrors it so a bad value is a named 400 at the route and never a
|
|
15
|
+
-- constraint-violation 500 (the ideas_grades recommendation precedent). The
|
|
16
|
+
-- server reads the value off the SESSION that filed (a web cookie is the hall,
|
|
17
|
+
-- a device-flow token is the CLI, anything else the API); the one value a body
|
|
18
|
+
-- may add is 'planning', which no session can tell from 'cli'.
|
|
19
|
+
--
|
|
20
|
+
-- NULLABLE, NO DEFAULT, NO BACKFILL — and that is the point. Every row that
|
|
21
|
+
-- predates this column was filed before the ledger recorded where, and the sky
|
|
22
|
+
-- says exactly that ("filed before the ledger recorded where"; such rows SHOW
|
|
23
|
+
-- by default — owner, 2026-09-24). NULL is not 'cli'. Guessing a value for
|
|
24
|
+
-- historic rows from their text would put the prototype's heuristic INTO the
|
|
25
|
+
-- ledger, which is precisely what this part exists to end.
|
|
26
|
+
--
|
|
27
|
+
-- CORE, not modules/ideas/migrations/: idea_inbox is a core table and a module
|
|
28
|
+
-- migration may touch only its own ideas_-prefixed objects (ADR 0083 §Decision
|
|
29
|
+
-- #5; the core_195 / core_222 precedent). Forward-safe by construction: an
|
|
30
|
+
-- additive nullable column every existing reader selects by explicit list
|
|
31
|
+
-- (tests/migrations_forward_safe.mjs).
|
|
32
|
+
|
|
33
|
+
BEGIN;
|
|
34
|
+
|
|
35
|
+
ALTER TABLE idea_inbox
|
|
36
|
+
ADD COLUMN IF NOT EXISTS filed_via text;
|
|
37
|
+
|
|
38
|
+
-- CHECK constraints have no ADD ... IF NOT EXISTS, so guard the add (the
|
|
39
|
+
-- migration 042 / core_195 idiom) and keep the file replay-safe.
|
|
40
|
+
DO $$
|
|
41
|
+
BEGIN
|
|
42
|
+
IF NOT EXISTS (
|
|
43
|
+
SELECT 1 FROM pg_constraint
|
|
44
|
+
WHERE conrelid = 'idea_inbox'::regclass
|
|
45
|
+
AND conname = 'idea_inbox_filed_via_known'
|
|
46
|
+
) THEN
|
|
47
|
+
ALTER TABLE idea_inbox
|
|
48
|
+
ADD CONSTRAINT idea_inbox_filed_via_known
|
|
49
|
+
CHECK (filed_via IS NULL OR filed_via IN ('hall', 'cli', 'discord', 'planning', 'api'));
|
|
50
|
+
END IF;
|
|
51
|
+
END $$;
|
|
52
|
+
|
|
53
|
+
COMMENT ON COLUMN idea_inbox.filed_via IS
|
|
54
|
+
'Which door filed this idea: hall | cli | discord | planning | api. NULL = filed before the column existed; never backfilled by guessing.';
|
|
55
|
+
|
|
56
|
+
INSERT INTO schema_migrations (version) VALUES ('core_253_idea_filed_via')
|
|
57
|
+
ON CONFLICT DO NOTHING;
|
|
58
|
+
|
|
59
|
+
COMMIT;
|
|
@@ -385,6 +385,8 @@ async function deliverToInbox(deliver, parts) {
|
|
|
385
385
|
title: parts.title,
|
|
386
386
|
bodyMd: `${parts.provenance}\n\n${parts.body}`,
|
|
387
387
|
capturedBy: null,
|
|
388
|
+
// task 1004233 (sky part 5): a poller is a program on the port — 'api'.
|
|
389
|
+
filedVia: 'api',
|
|
388
390
|
});
|
|
389
391
|
const id = idea && idea.id;
|
|
390
392
|
return id !== null && id !== undefined ? `idea:${id}` : null;
|
|
@@ -242,6 +242,8 @@ async function recordInboundBug({ discordUserId, content, imageUrls = [], messag
|
|
|
242
242
|
bodyMd: description,
|
|
243
243
|
kind: 'bug',
|
|
244
244
|
capturedBy: builder ? builder.id : null,
|
|
245
|
+
// task 1004233 (sky part 5): the door — a port caller names its own.
|
|
246
|
+
filedVia: 'discord',
|
|
245
247
|
filerRank,
|
|
246
248
|
goalId: goal ? goal.id : null,
|
|
247
249
|
suggestedPriority: 3,
|
|
@@ -175,6 +175,9 @@ async function recordInboundIdea({ discordUserId, content, imageUrls = [], messa
|
|
|
175
175
|
title: derived.title,
|
|
176
176
|
bodyMd,
|
|
177
177
|
capturedBy: builder ? builder.id : null,
|
|
178
|
+
// task 1004233 (sky part 5): the door, named here because this path calls
|
|
179
|
+
// the port, not the route — there is no session to read it off.
|
|
180
|
+
filedVia: 'discord',
|
|
178
181
|
// kind left null → inbox.captureIdea auto-classifies. We never pass
|
|
179
182
|
// 'criterion-proposal' (the rank-gated kind) from this path.
|
|
180
183
|
// suggestedGoalId/suggestedVersion are ADVISORY-ONLY columns — this call
|
|
@@ -44,6 +44,17 @@
|
|
|
44
44
|
rollup offers; people.js writes it. -->
|
|
45
45
|
<p class="ledger__note" id="people-foot" hidden></p>
|
|
46
46
|
</section>
|
|
47
|
+
|
|
48
|
+
<!-- task 1002291 (ADR 0334): builders who hid their cross-project record
|
|
49
|
+
but are still open to recruiting, listed by name alone (privacy spec
|
|
50
|
+
D2). Its own panel because they are never ranked; hidden unless the
|
|
51
|
+
response carries a row, and people.js writes it. -->
|
|
52
|
+
<section class="scroll" id="people-named-scroll" aria-labelledby="people-named-h" hidden>
|
|
53
|
+
<h2 id="people-named-h" class="scroll__h">Listed by name only<span class="count-pill" id="people-named-count" hidden></span></h2>
|
|
54
|
+
<p class="scroll__lede" id="people-named-lede">These builders are open to recruiting but keep their cross-project record private, so only who they are is shown. They are never ranked, and a role filter never finds them.</p>
|
|
55
|
+
<div id="people-named-body"></div>
|
|
56
|
+
<p class="ledger__note" id="people-named-foot" hidden></p>
|
|
57
|
+
</section>
|
|
47
58
|
</main>
|
|
48
59
|
|
|
49
60
|
<footer class="app-foot" id="app-foot"></footer>
|
|
@@ -16,6 +16,13 @@
|
|
|
16
16
|
// exists); then the four figures in tabular columns, the crafts as chips, and
|
|
17
17
|
// when they were last seen. Search, a sort and a craft lens are the kit's
|
|
18
18
|
// filter bar; the rows page through the kit's pager.
|
|
19
|
+
//
|
|
20
|
+
// THE SECOND TIER (task 1002291, ADR 0334). The response also carries
|
|
21
|
+
// `name_only`: builders who hid their cross-project record but are still open to
|
|
22
|
+
// recruiting, which privacy spec D2 lists "name-only". They get their own panel
|
|
23
|
+
// of identity cells because they have no record to rank, sort or filter by role:
|
|
24
|
+
// the lenses below leave them alphabetical, a search reads their name, and a
|
|
25
|
+
// role filter never matches them.
|
|
19
26
|
// All network values render through OTBKit.esc — never raw innerHTML.
|
|
20
27
|
(function () {
|
|
21
28
|
'use strict';
|
|
@@ -26,6 +33,10 @@
|
|
|
26
33
|
var sub = document.getElementById('people-sub');
|
|
27
34
|
var countPill = document.getElementById('people-count');
|
|
28
35
|
var foot = document.getElementById('people-foot');
|
|
36
|
+
var namedScroll = document.getElementById('people-named-scroll');
|
|
37
|
+
var namedCount = document.getElementById('people-named-count');
|
|
38
|
+
var namedBody = document.getElementById('people-named-body');
|
|
39
|
+
var namedFoot = document.getElementById('people-named-foot');
|
|
29
40
|
|
|
30
41
|
function fmtRecency(iso) {
|
|
31
42
|
if (!iso) return '—';
|
|
@@ -97,6 +108,7 @@
|
|
|
97
108
|
var DIRECTORY_LIMIT = 500;
|
|
98
109
|
|
|
99
110
|
var all = [];
|
|
111
|
+
var named = [];
|
|
100
112
|
var query = '';
|
|
101
113
|
var craft = '';
|
|
102
114
|
var sortBy = '';
|
|
@@ -108,6 +120,17 @@
|
|
|
108
120
|
(Array.isArray(b.disciplines) ? b.disciplines.join(' ') : '')).toLowerCase();
|
|
109
121
|
return hay.indexOf(query) !== -1;
|
|
110
122
|
}
|
|
123
|
+
// A name-only row carries no roles (they are part of the hidden record), so a
|
|
124
|
+
// role filter never matches it; a search reads the name.
|
|
125
|
+
function matchesNamed(b) {
|
|
126
|
+
if (craft) return false;
|
|
127
|
+
if (!query) return true;
|
|
128
|
+
return ((b.github_login || '') + ' ' + (b.display_name || '')).toLowerCase().indexOf(query) !== -1;
|
|
129
|
+
}
|
|
130
|
+
// A count that cannot overstate a clipped read: a full response means "at least".
|
|
131
|
+
function countText(n) {
|
|
132
|
+
return n >= DIRECTORY_LIMIT ? fmtInt(DIRECTORY_LIMIT) + '+' : fmtInt(n);
|
|
133
|
+
}
|
|
111
134
|
// The server's order IS "track record" (ADR 0141 §4 ranks the rollup); the
|
|
112
135
|
// other lenses re-order the loaded set client-side, ties keeping that order.
|
|
113
136
|
var SORTS = {
|
|
@@ -128,6 +151,8 @@
|
|
|
128
151
|
|
|
129
152
|
var pager = kit.makePager('people', 'hall-people-per-page');
|
|
130
153
|
var ledger = null;
|
|
154
|
+
var namedPager = kit.makePager('people-named', 'hall-people-named-per-page');
|
|
155
|
+
var namedLedger = null;
|
|
131
156
|
|
|
132
157
|
function rowHtml(b) {
|
|
133
158
|
return '<tr>' +
|
|
@@ -141,12 +166,45 @@
|
|
|
141
166
|
'</tr>';
|
|
142
167
|
}
|
|
143
168
|
|
|
169
|
+
// The identity cell and nothing else: every other column is the record hidden.
|
|
170
|
+
function namedRowHtml(b) {
|
|
171
|
+
return '<tr><td>' + whoHtml(b) + '</td></tr>';
|
|
172
|
+
}
|
|
173
|
+
|
|
144
174
|
function rerender() {
|
|
145
175
|
if (ledger) ledger.render(ordered(all.filter(matches)));
|
|
176
|
+
if (namedLedger) namedLedger.render(named.filter(matchesNamed));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function mountNamed() {
|
|
180
|
+
if (!namedScroll || !named.length) return;
|
|
181
|
+
namedScroll.hidden = false;
|
|
182
|
+
if (namedCount) {
|
|
183
|
+
namedCount.textContent = countText(named.length);
|
|
184
|
+
namedCount.hidden = false;
|
|
185
|
+
}
|
|
186
|
+
// The ranked footnote's rule, for the one lens that reads this list: a full
|
|
187
|
+
// response is clipped, and a search cannot find a builder it never loaded.
|
|
188
|
+
var namedClipped = named.length >= DIRECTORY_LIMIT;
|
|
189
|
+
if (namedFoot) {
|
|
190
|
+
namedFoot.textContent = namedClipped
|
|
191
|
+
? 'This list is clipped at the first ' + fmtInt(DIRECTORY_LIMIT) + ' by login. Search only reads what this page loaded, so a builder past that ' + fmtInt(DIRECTORY_LIMIT) + ' will not appear in it.'
|
|
192
|
+
: '';
|
|
193
|
+
namedFoot.hidden = !namedClipped;
|
|
194
|
+
}
|
|
195
|
+
namedLedger = kit.makeLedger({
|
|
196
|
+
mount: namedBody,
|
|
197
|
+
columns: ['Builder'],
|
|
198
|
+
rowHtml: namedRowHtml,
|
|
199
|
+
emptyMsg: 'No builder listed by name only matches these filters.',
|
|
200
|
+
pager: namedPager,
|
|
201
|
+
});
|
|
146
202
|
}
|
|
147
203
|
|
|
148
|
-
function mountDirectory(builders) {
|
|
204
|
+
function mountDirectory(builders, nameOnly) {
|
|
149
205
|
all = Array.isArray(builders) ? builders : [];
|
|
206
|
+
named = Array.isArray(nameOnly) ? nameOnly : [];
|
|
207
|
+
if (namedScroll) namedScroll.hidden = true;
|
|
150
208
|
// An EMPTY directory is almost never missing data — it is ADR 0171's
|
|
151
209
|
// fail-closed consent boundary doing exactly what it was ratified to do.
|
|
152
210
|
// `listScoutingRollup` filters on `profile_state = 'active'`, every account
|
|
@@ -160,7 +218,7 @@
|
|
|
160
218
|
// leaderboard; the accounts simply are not publicly listed — and that wrong
|
|
161
219
|
// explanation is why an empty directory got filed as a bug (task 1002937).
|
|
162
220
|
// Say which of the two it is, so the page is never mistaken for broken.
|
|
163
|
-
if (!all.length) {
|
|
221
|
+
if (!all.length && !named.length) {
|
|
164
222
|
notice('Nobody is publicly listed here yet. A builder joins this directory once they have '
|
|
165
223
|
+ 'signed in on the main platform site at least once — accounts stay unlisted until then, '
|
|
166
224
|
+ 'and none are listed without it. Their work still shows on each project’s own ledger.');
|
|
@@ -169,16 +227,21 @@
|
|
|
169
227
|
// A FULL PAGE IS A CLIPPED PAGE. Every sentence below has to hedge when this
|
|
170
228
|
// is true, because each of them otherwise states a total the page cannot know.
|
|
171
229
|
var clipped = all.length >= DIRECTORY_LIMIT;
|
|
230
|
+
// The second tier, named in the same sentence so the ranked count is never
|
|
231
|
+
// read as everyone the directory lists.
|
|
232
|
+
var namedPart = named.length ? '; ' + countText(named.length) + ' more by name only' : '';
|
|
172
233
|
if (countPill) {
|
|
173
234
|
countPill.textContent = clipped ? fmtInt(DIRECTORY_LIMIT) + '+' : String(all.length);
|
|
174
235
|
countPill.hidden = false;
|
|
175
236
|
}
|
|
176
237
|
if (sub) {
|
|
177
|
-
|
|
178
|
-
? 'Showing the first ' + fmtInt(DIRECTORY_LIMIT) + ' builders across hosted projects, ranked by track record — there are more than this page can load
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
238
|
+
var listed = clipped
|
|
239
|
+
? 'Showing the first ' + fmtInt(DIRECTORY_LIMIT) + ' builders across hosted projects, ranked by track record — there are more than this page can load' + namedPart
|
|
240
|
+
: all.length
|
|
241
|
+
? fmtInt(all.length) + ' builder' + (all.length === 1 ? '' : 's') + ' listed across hosted projects, ranked by track record' + namedPart
|
|
242
|
+
// Nobody ranked yet: "0 builders ranked; 2 more by name only" would read as a count of nothing.
|
|
243
|
+
: countText(named.length) + ' builder' + (named.length === 1 ? '' : 's') + ' listed across hosted projects by name only';
|
|
244
|
+
sub.textContent = listed + '. Descriptive only — cross-project numbers never carry authority here.';
|
|
182
245
|
}
|
|
183
246
|
// THE LENSES LIE HARDER THAN THE COUNT DOES, and that is worth its own
|
|
184
247
|
// sentence. Search, sort and the craft filter all re-order the set this page
|
|
@@ -214,6 +277,7 @@
|
|
|
214
277
|
craft = String(vals['people-craft'] || '');
|
|
215
278
|
sortBy = String(vals['people-sort'] || '');
|
|
216
279
|
pager.page = 1;
|
|
280
|
+
namedPager.page = 1;
|
|
217
281
|
rerender();
|
|
218
282
|
},
|
|
219
283
|
});
|
|
@@ -224,11 +288,13 @@
|
|
|
224
288
|
columns: ['Builder', { label: 'Projects', class: 'ledger__num' }, { label: 'Ships', class: 'ledger__num' },
|
|
225
289
|
{ label: currencyLabel(), class: 'ledger__num' }, { label: 'Karma', class: 'ledger__num' }, `${window.OTB.roleAxisLabel()}s`, 'Last active'],
|
|
226
290
|
rowHtml: rowHtml,
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
|
|
291
|
+
// With ranked builders, reached only when the lenses excluded them all;
|
|
292
|
+
// without, the directory lists hidden builders alone (the panel below) —
|
|
293
|
+
// the fully empty case returns above with its own explanation.
|
|
294
|
+
emptyMsg: all.length ? 'No listed builder matches that search.' : 'No builder with a public track record is listed yet.',
|
|
230
295
|
pager: pager,
|
|
231
296
|
});
|
|
297
|
+
mountNamed();
|
|
232
298
|
rerender();
|
|
233
299
|
}
|
|
234
300
|
|
|
@@ -244,7 +310,7 @@
|
|
|
244
310
|
if (!r.ok) throw new Error('http ' + r.status);
|
|
245
311
|
return r.json();
|
|
246
312
|
})
|
|
247
|
-
.then(function (data) { mountDirectory(data && data.builders); })
|
|
313
|
+
.then(function (data) { mountDirectory(data && data.builders, data && data.name_only); })
|
|
248
314
|
.catch(function (e) {
|
|
249
315
|
// The floor is the pack's label for metic, never a hardcoded rank name —
|
|
250
316
|
// and the page used to say "Archon-only", which was wrong since ADR 0210.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_": "People — the cross-project directory behind requireProjectCuratePage (metic+ via project.curate, or a project owner). Descriptive only; it grants no authority, and the page says so above the fold. Written with the re-skin (task 1003307); the redesign (task 1003470) added the identity cell, the sort and craft lenses, and the `search` state. Its legacy door /scouting was deleted in task 1003440; the URL 301s here. Populated look: the hall-preview harness (scripts/hall-preview/fixtures/scouting.json carries five builders, three with avatars).",
|
|
2
|
+
"_": "People — the cross-project directory behind requireProjectCuratePage (metic+ via project.curate, or a project owner). Descriptive only; it grants no authority, and the page says so above the fold. Written with the re-skin (task 1003307); the redesign (task 1003470) added the identity cell, the sort and craft lenses, and the `search` state. Its legacy door /scouting was deleted in task 1003440; the URL 301s here. Populated look: the hall-preview harness (scripts/hall-preview/fixtures/scouting.json carries five builders, three with avatars, plus two listed by name only — task 1002291 added that second panel; the `search` state shows its empty case).",
|
|
3
3
|
"page": "/builders/people",
|
|
4
4
|
"surface": "hall-ui",
|
|
5
5
|
"stub": {
|