@bongos/core 1.19.1018 → 1.19.1020

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.bongos-core.json CHANGED
@@ -2,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.1018",
6
- "core_contract": "1.19.1018",
7
- "source_commit": "49eb795ed8e84657315241e6cfa652f3491e6edc",
5
+ "core_version": "1.19.1020",
6
+ "core_contract": "1.19.1020",
7
+ "source_commit": "d31eeb45f8ff5f9e1108cf1810f36f78b246b079",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-25T01:46:13.363Z",
9
+ "built_at": "2026-09-25T02:15:05.520Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
- "docs_redacted": 527,
12
+ "docs_redacted": 528,
13
13
  "agent_docs_stubbed": 26,
14
- "functional_verbatim": 2440,
14
+ "functional_verbatim": 2443,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2993,
20
- "tree_sha256": "ed9091f7fa992b1a3cb4b1d473869986f200827a94ca69cc0a8c86770b571b71",
19
+ "file_count": 2997,
20
+ "tree_sha256": "531cd8739de683d6403b1bd357af7f3a1449eb4301816b1b153e116f210ba978",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -352,12 +352,12 @@
352
352
  {
353
353
  "path": "clients/bongos-client/README.md",
354
354
  "mode": "0000644",
355
- "sha256": "1d0f3cc30c66a4eb757e297d95406a42c55cedc073ee29671827926f06c0d260"
355
+ "sha256": "6a43336383cc459524609f6dee6fa8dc5af5f53393869ca09964ab37bda2a9f9"
356
356
  },
357
357
  {
358
358
  "path": "clients/bongos-client/bongos-client.global.js",
359
359
  "mode": "0000644",
360
- "sha256": "c913bb0302165f718776d8f5f6f5d3bdde73ee152a9eabbe3fc4f92d23e6b0e3"
360
+ "sha256": "b63d94b37df14283ea04512e281163c1f027995c08b5f052aa2f062d7632a686"
361
361
  },
362
362
  {
363
363
  "path": "clients/bongos-client/examples/hello-world.mjs",
@@ -367,17 +367,17 @@
367
367
  {
368
368
  "path": "clients/bongos-client/index.cjs",
369
369
  "mode": "0000644",
370
- "sha256": "48051577351ecb7f13d874668d2ed3171b1cad370969ba5e8b4124b20584c839"
370
+ "sha256": "01e49d4c9dd29cd7c312b637e013dcbb69e9bb0f12aee3ff6b5aaf5caeb1539a"
371
371
  },
372
372
  {
373
373
  "path": "clients/bongos-client/index.d.ts",
374
374
  "mode": "0000644",
375
- "sha256": "ffbc111669a6fb27297d1819c35d02f732c3d69b87411613a88b348b0fa897c6"
375
+ "sha256": "0ccdf87aa5ce77f34dde4ed9d7a3f9ab38aee7be4c3d81f6ab64311cc68feefc"
376
376
  },
377
377
  {
378
378
  "path": "clients/bongos-client/index.mjs",
379
379
  "mode": "0000644",
380
- "sha256": "81da43a07fc9e2c89899334f58440d07c8f36df0efc5a1f3a9512a20f2a90047"
380
+ "sha256": "29f5d849eb791886672584d5f258b306a237c774b83b33b68e7ad4990cb68ead"
381
381
  },
382
382
  {
383
383
  "path": "clients/bongos-client/package.json",
@@ -2154,6 +2154,11 @@
2154
2154
  "mode": "0000644",
2155
2155
  "sha256": "659b73c16da06bb150434de1eae1bcc1dfe4f6acf61ac0b2d787f3b52f617187"
2156
2156
  },
2157
+ {
2158
+ "path": "docs/adr/0332-offboarding-secret-scrub-semantics.md",
2159
+ "mode": "0000644",
2160
+ "sha256": "0c11917e4e24c9cd462917fd82d79f4e9da43384cc04fc3d493374c23f887bb2"
2161
+ },
2157
2162
  {
2158
2163
  "path": "docs/adr/0333-the-application-vouch-is-a-third-token-spent-once-and-bound-to-its-login.md",
2159
2164
  "mode": "0000644",
@@ -2177,17 +2182,17 @@
2177
2182
  {
2178
2183
  "path": "docs/adr/README.md",
2179
2184
  "mode": "0000644",
2180
- "sha256": "8227e2293df9bc8364bc66ecd8890341e7a1410b9edc7906c38abae37ce7eccd"
2185
+ "sha256": "e221ffd25568bcc9ba803b80538d83b270be7f60e19e301aa1f630a28cf55c74"
2181
2186
  },
2182
2187
  {
2183
2188
  "path": "docs/api-reference.md",
2184
2189
  "mode": "0000644",
2185
- "sha256": "89d96b4b83075fea2954bbbfda5d815a74f12a06f46cb11da3d83c3d7951d65b"
2190
+ "sha256": "cedc612f059cc0923f438b9836d7b90e249aca8245b8864dbda094752df258a0"
2186
2191
  },
2187
2192
  {
2188
2193
  "path": "docs/api/openapi.json",
2189
2194
  "mode": "0000644",
2190
- "sha256": "0e679eec9a95a36a04a91c2d37a34dcba1dec397b0eff30eff6b0b1c5e267627"
2195
+ "sha256": "3f65db9aa5ef67ceed4ecca08e083208a44413e7e69363f26ca60ea48b905005"
2191
2196
  },
2192
2197
  {
2193
2198
  "path": "docs/architecture.md",
@@ -2212,12 +2217,12 @@
2212
2217
  {
2213
2218
  "path": "docs/copy-inventory.md",
2214
2219
  "mode": "0000644",
2215
- "sha256": "7824524ca9e6c4eed55c42542e5bca4904650f4476218a22012bf8e13d1e9ac3"
2220
+ "sha256": "5226255c04e9a3207de9e4cf9712cdcaf2a3fde799d76d8ed768497a271ad91b"
2216
2221
  },
2217
2222
  {
2218
2223
  "path": "docs/copy-registry.json",
2219
2224
  "mode": "0000644",
2220
- "sha256": "72b54a0726ff452ddc7c1e6938b6274c1251e2a471592d8083e2530aeef10778"
2225
+ "sha256": "bf7384ef44efc40d16be639400157640c5f1a4dc746bf32bb1f80b43004ceed9"
2221
2226
  },
2222
2227
  {
2223
2228
  "path": "docs/design/apex-pass-2-direction.md",
@@ -2747,12 +2752,12 @@
2747
2752
  {
2748
2753
  "path": "docs/module-api-changelog.md",
2749
2754
  "mode": "0000644",
2750
- "sha256": "a0b015494e2c8eaf772b17de3bd6c38737a4ad939921a395021172271b445e3f"
2755
+ "sha256": "cd03ff3474e354b9286e63370d31eb4e952ec05eaf35660c158947cdeef669f6"
2751
2756
  },
2752
2757
  {
2753
2758
  "path": "docs/modules-contract.md",
2754
2759
  "mode": "0000644",
2755
- "sha256": "e7e260c0dbd767070a9d40513eec4d6de47a6c3b363ee43d888fdb366a57be80"
2760
+ "sha256": "2d354cb1b436f21c6dc7c1b4aed26d8aa0bc071db30dd9545c3301a5486e903d"
2756
2761
  },
2757
2762
  {
2758
2763
  "path": "docs/onboarding/browser-terminal-guide.md",
@@ -5242,7 +5247,7 @@
5242
5247
  {
5243
5248
  "path": "modules/hall-ui/public/profile-connections.js",
5244
5249
  "mode": "0000644",
5245
- "sha256": "1f52e92aea4583273d719538c453d4d11313f15d583536316c75ef4d625a8265"
5250
+ "sha256": "32d227e81b459e067b06ac67c89a53ce0aa6b549749c39ac3adc4e8fb31a9375"
5246
5251
  },
5247
5252
  {
5248
5253
  "path": "modules/hall-ui/public/profile-nudge.js",
@@ -6459,10 +6464,15 @@
6459
6464
  "mode": "0000644",
6460
6465
  "sha256": "2aa433ba293545a313972f88b1143d74551cb6ec4e06747906ff7e3c365f4e6a"
6461
6466
  },
6467
+ {
6468
+ "path": "modules/platform-identity/connection-suggestions.js",
6469
+ "mode": "0000644",
6470
+ "sha256": "73beb598013b1ddd6364b2c97f1b346505c9c909934d8b3b9fc3b3117ad09786"
6471
+ },
6462
6472
  {
6463
6473
  "path": "modules/platform-identity/connections.js",
6464
6474
  "mode": "0000644",
6465
- "sha256": "ed671aad4b6af46e56e29d5427ac2d5bdfc3ed414dbb981a27fce5097946e7ae"
6475
+ "sha256": "b58280a24ff5eb364aa95bf97bb80b75c8a4fe0a4d0cc224072663cb8687edd0"
6466
6476
  },
6467
6477
  {
6468
6478
  "path": "modules/platform-identity/hub-device.js",
@@ -6657,7 +6667,7 @@
6657
6667
  {
6658
6668
  "path": "modules/platform-identity/routes/connections.js",
6659
6669
  "mode": "0000644",
6660
- "sha256": "d8f7404fc30052a4f815e735dff7981da56d00ccb902ace5fac132a35f59e6de"
6670
+ "sha256": "6132e21ae24783f11796b5084067622742dd5c05b17a2c74780d734e337f8dfc"
6661
6671
  },
6662
6672
  {
6663
6673
  "path": "modules/platform-identity/routes/my-projects.js",
@@ -8297,12 +8307,12 @@
8297
8307
  {
8298
8308
  "path": "package-lock.json",
8299
8309
  "mode": "0000644",
8300
- "sha256": "c5cc97b40c78fcf1e0eb46f141d801315cb7cdc9afb99ee7435707df4a067f04"
8310
+ "sha256": "1c72dca3250f526c8fc32ddb9f4a2c534ab8c0860fc23ba1d7182d7774a4d09b"
8301
8311
  },
8302
8312
  {
8303
8313
  "path": "package.json",
8304
8314
  "mode": "0000644",
8305
- "sha256": "3f6fc02ebfabdea595b601e08e7bb9aa2004f576bc4497ecf1d36482889b8efb"
8315
+ "sha256": "8362f7bf9f4b97d5a3ced96f6e17727365cd34c279970c0c5fb7d2f4721f6a86"
8306
8316
  },
8307
8317
  {
8308
8318
  "path": "public-docs/index.html",
@@ -10087,7 +10097,7 @@
10087
10097
  {
10088
10098
  "path": "src/bongos/db.js",
10089
10099
  "mode": "0000644",
10090
- "sha256": "def84475a0470dafb894e7ca3f96f9db27cea78964f90ac3e81ed748fe9efa48"
10100
+ "sha256": "f89fa7199401add96ec042169c5c2657fcff120385e36b899da604d011b696d8"
10091
10101
  },
10092
10102
  {
10093
10103
  "path": "src/bongos/github-repos.js",
@@ -10347,7 +10357,7 @@
10347
10357
  {
10348
10358
  "path": "src/module-api.js",
10349
10359
  "mode": "0000644",
10350
- "sha256": "2bc85f866eb82befe5dd49e4e5b3c1ed1195f33eb2d00a8f5c8f952d82c99843"
10360
+ "sha256": "e05d44a98764b7e9b50e984e4abf9ac0626dc4327e7ae708abda3f15bfc3e85c"
10351
10361
  },
10352
10362
  {
10353
10363
  "path": "src/module-loader/catalog.js",
@@ -11234,10 +11244,15 @@
11234
11244
  "mode": "0000644",
11235
11245
  "sha256": "da8182c4235c7735bdee351cfa7dc1146f7ded33b9d594dc0ba498a3b5206d83"
11236
11246
  },
11247
+ {
11248
+ "path": "tests/connection_suggestions.mjs",
11249
+ "mode": "0000644",
11250
+ "sha256": "c85a26355952c1d201838dbe569727c54d81d79d62d9e9d784182bc60fb743f7"
11251
+ },
11237
11252
  {
11238
11253
  "path": "tests/connections_api.mjs",
11239
11254
  "mode": "0000644",
11240
- "sha256": "61af2ef3905b31a796e80168f1e267b608792d7195cf2e48d6a1ca85eb76ef5e"
11255
+ "sha256": "377a1cb8cc9eaeeb86bd033a02a8f7ecfd499ceb0d09718533a3a7bb4ed014ec"
11241
11256
  },
11242
11257
  {
11243
11258
  "path": "tests/connections_ownership.mjs",
@@ -13184,6 +13199,11 @@
13184
13199
  "mode": "0000644",
13185
13200
  "sha256": "41d390570f7ecc8c902f2e4d3d75ca9e4077d565150b2491500108d618456ae3"
13186
13201
  },
13202
+ {
13203
+ "path": "tests/offboarding_secret_scrub.mjs",
13204
+ "mode": "0000644",
13205
+ "sha256": "7458dfd09a8c6f62633bb51d38f8c3d97a185a89b927ccc34ab2d9535f42c307"
13206
+ },
13187
13207
  {
13188
13208
  "path": "tests/onboard.mjs",
13189
13209
  "mode": "0000644",
@@ -13382,7 +13402,7 @@
13382
13402
  {
13383
13403
  "path": "tests/profile_connections_ui.mjs",
13384
13404
  "mode": "0000644",
13385
- "sha256": "6a5e6bd1fa52d8d757436efcfca19ffb5c2a7470f1a6583f26802a24689b4eb8"
13405
+ "sha256": "d2c415317244457f575c985fff3ba2645fc5da97253357eaecd373dba33b14f9"
13386
13406
  },
13387
13407
  {
13388
13408
  "path": "tests/profile_fields.mjs",
@@ -5,7 +5,7 @@ A **generated**, zero-dependency typed client for the Bongos API — produced fr
5
5
  by hand; it regenerates when the spec changes, so it can never drift from the routes.
6
6
 
7
7
  - API version: **v1** (served at `/api/bongos/v1`)
8
- - 417 operations across 63 resource groups
8
+ - 418 operations across 63 resource groups
9
9
 
10
10
  ## Use it from your project
11
11
 
@@ -327,6 +327,8 @@ function createClient(opts = {}) {
327
327
  getConnectionsDegreeHandle: (args) => request("GET", "/connections/degree/{handle}", { hasBody: false }, args),
328
328
  // GET /connections/requests — rank: any-builder — GET /connections/requests
329
329
  getConnectionsRequests: (args) => request("GET", "/connections/requests", { hasBody: false }, args),
330
+ // GET /connections/suggestions — rank: any-builder — GET /connections/suggestions
331
+ getConnectionsSuggestions: (args) => request("GET", "/connections/suggestions", { hasBody: false }, args),
330
332
  },
331
333
  "copyDesk": {
332
334
  // POST /copy-desk/flags — rank: any-builder — POST /copy-desk/flags
@@ -326,6 +326,8 @@ function createClient(opts = {}) {
326
326
  getConnectionsDegreeHandle: (args) => request("GET", "/connections/degree/{handle}", { hasBody: false }, args),
327
327
  // GET /connections/requests — rank: any-builder — GET /connections/requests
328
328
  getConnectionsRequests: (args) => request("GET", "/connections/requests", { hasBody: false }, args),
329
+ // GET /connections/suggestions — rank: any-builder — GET /connections/suggestions
330
+ getConnectionsSuggestions: (args) => request("GET", "/connections/suggestions", { hasBody: false }, args),
329
331
  },
330
332
  "copyDesk": {
331
333
  // POST /copy-desk/flags — rank: any-builder — POST /copy-desk/flags
@@ -53,6 +53,7 @@ export interface GetCommunitySearchResponse { query: unknown; discipline: unknow
53
53
  export interface GetConnectionsDegreeHandleResponse { handle: unknown; degree: unknown; mutuals: unknown; mutual_count: unknown; truncated: unknown }
54
54
  export interface GetConnectionsRequestsResponse { incoming: unknown; outgoing: unknown; incoming_count: unknown; outgoing_count: unknown }
55
55
  export interface GetConnectionsResponse { connections: unknown; count: unknown }
56
+ export interface GetConnectionsSuggestionsResponse { suggestions: unknown; count: unknown }
56
57
  export interface GetCoreUpdateResponse { ok: boolean; update: unknown }
57
58
  export interface GetDiscordChannelsPlanResponse { ok: boolean; guild_id: unknown; op_count: unknown; ops: unknown; warnings: unknown }
58
59
  export interface GetDiscordChannelsSnapshotResponse { ok: boolean; guild_id: unknown; config: unknown; warnings: unknown }
@@ -714,6 +715,8 @@ export interface BongosClient {
714
715
  getConnectionsDegreeHandle(args?: RequestArgs): Promise<GetConnectionsDegreeHandleResponse>;
715
716
  /** GET /connections/requests — rank: any-builder */
716
717
  getConnectionsRequests(args?: RequestArgs): Promise<GetConnectionsRequestsResponse>;
718
+ /** GET /connections/suggestions — rank: any-builder */
719
+ getConnectionsSuggestions(args?: RequestArgs): Promise<GetConnectionsSuggestionsResponse>;
717
720
  };
718
721
  "copyDesk": {
719
722
  /** POST /copy-desk/flags — rank: any-builder */
@@ -323,6 +323,8 @@ export function createClient(opts = {}) {
323
323
  getConnectionsDegreeHandle: (args) => request("GET", "/connections/degree/{handle}", { hasBody: false }, args),
324
324
  // GET /connections/requests — rank: any-builder — GET /connections/requests
325
325
  getConnectionsRequests: (args) => request("GET", "/connections/requests", { hasBody: false }, args),
326
+ // GET /connections/suggestions — rank: any-builder — GET /connections/suggestions
327
+ getConnectionsSuggestions: (args) => request("GET", "/connections/suggestions", { hasBody: false }, args),
326
328
  },
327
329
  "copyDesk": {
328
330
  // POST /copy-desk/flags — rank: any-builder — POST /copy-desk/flags
@@ -0,0 +1,59 @@
1
+ # 0332 — Offboarding scrubs stored secrets: an event, not a direct write, and reactivation restores neither
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-09-24
5
+ - **Deciders:** spike task [1003338](https://cloudbongos.com/builders#/task/1003338), goal 1000110
6
+
7
+ ## Context
8
+
9
+ `deactivateBuilder` (`src/bongos/db.js`, the transaction behind `/builder-exit`) is the graceful-offboarding path: it releases active claims, drops rank to `xenos`, and — since the `#871` fix (security report task 1000004) — revokes every live `builder_sessions` row in the same transaction. It is explicitly designed to be **lossless**: credits, `credit_log`, shipped tasks, and session history all survive untouched.
10
+
11
+ It does not, however, touch two third-party credentials the platform holds **on the builder's behalf**:
12
+
13
+ 1. **The GitHub OAuth repo token** (`github_oauth_tokens`, one row per builder, written by `upsertGithubToken` at sign-in for the onboarding wizard's repo picker). `deleteGithubToken` already exists (`src/bongos/db.js`) and is called from `routes/github.js` on a 401/invalidation — a *separate* inline `DELETE` inside `getGithubToken` lazily sweeps an expired row on read — but `deleteGithubToken` itself is never called from offboarding. The table's own migration comment (`migrations/core_192_github_oauth_tokens.sql`) calls this "the auth system's OWN handshake token," i.e. core-owned. Its `ON DELETE CASCADE` never fires because offboarding never deletes the `builders` row — it only flips `status`.
14
+ 2. **The per-builder Gemini BYOK key** (`builders.gemini_key_enc` / `gemini_key_last4` / `gemini_key_set_at`, AES-256-GCM via `src/bongos/secret-box.js`, migration `122`). Both secrets are encrypted at rest with a server-env-only master key, so a DB dump alone cannot read either — the gap is **latent** for takeover, but a real data-retention/data-minimization defect: an offboarded person's third-party credential is retained indefinitely with no purge path.
15
+
16
+ The Gemini key is a special case: **its CRUD moved out of core into the `art-pipeline` module under ADR 0102**, specifically to stop core importing domain logic (`setBuilderGeminiKey`/`clearBuilderGeminiKey`/`getBuilderGeminiKeyRow` were three of the seven kernel→domain leaks that ADR closed). Core resolves the read side today through the `art.getBuilderGeminiKeyRow` port (`routes/me.js`), degrading to `{ enc: null, last4: null, set_at: null }` when the module isn't installed — which it isn't in this checkout (`config/modules.neutral.json` ships `art-pipeline: false` by default, and `modules/art-pipeline/` does not exist in this repo). The column lives in core's `builders` table (core-owned schema), but the module owns the logic that writes it — the exact split ADR 0102 chose.
17
+
18
+ Two decisions the task description left open, and the deliverable of this spike:
19
+
20
+ 1. **Does a module own the key store, and if so, how does core reach across the boundary to scrub it?**
21
+ 2. **What does reactivation restore?**
22
+
23
+ ## Decision
24
+
25
+ **1. The GitHub token is deleted synchronously, in-transaction — the Gemini key is scrubbed via a new best-effort event, not a direct write.**
26
+
27
+ - In `deactivateBuilder`'s existing `withTx` block, call `deleteGithubToken({ builderId, p: client })` alongside the existing claim-release and session-revocation steps. `github_oauth_tokens` is core's own table; this is no different from the writes already there.
28
+ - Post-commit, alongside the existing `seams.emitAsync('builder.box-reconcile-needed', …)` call, emit a new event: **`builder.secrets-scrub-needed`**, payload `{ builderId, actor }`. This is the teardown port for any module that stores a builder-keyed secret against a core column or its own table — today, that's `art-pipeline`'s Gemini key. `art-pipeline` (when installed) registers `api.on('builder.secrets-scrub-needed', …)` and nulls `gemini_key_enc`/`gemini_key_last4`/`gemini_key_set_at` itself, mirroring exactly how `dev-box` already listens for `builder.box-reconcile-needed` (`modules/dev-box/routes/box.js`).
29
+ - Core must **not** `UPDATE builders SET gemini_key_enc = NULL, …` directly. That column is schema-owned by core but logic-owned by the module by deliberate design (ADR 0102) — reaching in to null it from `db.js` re-opens the exact kernel→domain leak that ADR closed, and it hard-codes one module's column names into core, which breaks the moment a different or additional module owns a different builder-keyed secret.
30
+ - On a vanilla instance (the default: every optional module off), the event has no listener and is a no-op — matching the graceful-degradation posture the `art.getBuilderGeminiKeyRow` port already uses. The GitHub-token delete still runs unconditionally, since it is core's own table regardless of which modules are installed.
31
+
32
+ **2. Reactivation restores neither secret.**
33
+
34
+ `reactivateBuilder` flips `status` back to `active` and explicitly leaves `rank` at `xenos` — "an Archon re-promotes deliberately." The same posture applies to both stored secrets: a returning builder is **not** silently handed back their old GitHub token or Gemini key. They re-enter their own BYOK key from Settings exactly as a first-time builder would, and the GitHub token is re-obtained the next time they run the sign-in flow that requests it. `reactivateBuilder` already never touched either store, so there is no functional change here — this decision adds an explicit comment there (`src/bongos/db.js`) so a future edit doesn't "helpfully" restore a stored credential.
35
+
36
+ Rationale for the deny-by-default reactivation stance:
37
+
38
+ - Secrets held on a builder's behalf are not "work" — the lossless guarantee (credits, shipped tasks, session history) is about *earned* state. A third-party credential is closer to a session token than to a credit ledger, and sessions are already revoked (not preserved) on exit.
39
+ - A gap between deactivation and reactivation is an elevated-risk window by definition (unknown reason for departure; possible compromise). Silently reviving a dormant credential on reactivation reopens exactly the exposure the scrub exists to close, with no signal to the returning builder that anything was restored.
40
+ - Matches `secret-box.js`'s own fail-closed ethos ("a malformed secret fails CLOSED — set/decrypt is disabled, never weak").
41
+
42
+ ## Alternatives considered
43
+
44
+ - **Null the Gemini-key columns directly with raw SQL inside `deactivateBuilder`'s transaction.** Rejected: re-opens the kernel→domain leak ADR 0102 deliberately closed, and hard-codes `art-pipeline`'s column names into core. Also stops working the moment any *other* module wants to store a builder-keyed secret — the event scales, a hard-coded `UPDATE` does not.
45
+ - **Do nothing until the server-side memory store (criterion `#54`) lands.** Rejected: unrelated scope — #54 is about the server holding another builder's session/memory corpus, not third-party credential hygiene — and the gap is real today, independent of whether #54 ever ships.
46
+ - **Delete the `builders` row on offboarding (let `ON DELETE CASCADE` clear `github_oauth_tokens` for free).** Rejected: directly contradicts the explicit lossless design of graceful exit (`/builder-exit`'s own doc: "nothing the builder earned or shipped is ever deleted") — credits, `credit_log`, and attribution on shipped tasks all depend on the row surviving.
47
+ - **A cron/offline sweep that scrubs secrets for builders who have been inactive N days.** Rejected: introduces a whole new mechanism (a scheduled sweep) where a synchronous delete + an event mirroring the already-idiomatic `builder.box-reconcile-needed` pattern does the job with no new moving parts, and it closes the window immediately at offboarding time rather than leaving one open until the next sweep.
48
+ - **Auto-restore the Gemini key on reactivation** (treat it like credits — lossless). Rejected per the rationale above: a stored third-party credential is not earned state, and silently reviving it on reactivation defeats the point of scrubbing it in the first place.
49
+
50
+ ## Consequences
51
+
52
+ - `deactivateBuilder` grows one direct call (`deleteGithubToken`) and one new best-effort `emitAsync`, both inside the pattern already established by the box-reconcile teardown — no new mechanism, no new dependency. This decision lands that change directly in this task (task 1003338): the in-transaction GitHub-token delete, the new event emission, a `docs/modules-contract.md` row for `builder.secrets-scrub-needed`, and tests (`tests/offboarding_secret_scrub.mjs`) covering the delete, the event payload, a no-listener instance, and that reactivation triggers neither.
53
+ - On this checkout, `art-pipeline` isn't vendored (`modules/art-pipeline/` doesn't exist here), so the module-side listener that would actually clear `gemini_key_enc`/`last4`/`set_at` cannot be implemented in this repo. Follow-up task 1004239 is narrowed to that listener, for whichever repo/host carries `modules/art-pipeline/`.
54
+ - **The Gemini-key scrub is single-shot and best-effort, honestly.** A crash between commit and the `emitAsync` call, an absent listener, or a listener error leaves `gemini_key_enc` in place with no automatic repair — and `deactivateBuilder` throws `ALREADY_INACTIVE` on a second run, so `/builder-exit` cannot be re-invoked to retry it. This is the same reliability profile the existing `builder.box-reconcile-needed` event already has in this codebase (its own repair path, `box-dormant-reclaim`, is cadence-less — "run by hand" per `scripts/gds/fitness.js`'s cron-cadence check), so this decision does not regress the bar, but it does not raise it either. Task 1004239 additionally scopes a periodic reconcile check (any `status='inactive'` builder whose module-owned secret is still set gets it cleared) as the durable backstop — which doubles as the one-time backfill for builders already offboarded before this change ships, so no separate backfill script is needed.
55
+ - Reactivation semantics for secrets are now explicit and symmetric with rank: neither is auto-restored. This is a (minor, and correct) behavior narrowing for any builder who reactivates within the GitHub token's 1h TTL — before this decision that token might have still been live if reactivation raced the TTL; after, it is always gone.
56
+
57
+ ## Follow-up
58
+
59
+ Task [1004239](https://cloudbongos.com/builders#/task/1004239) — narrowed to the `art-pipeline` module-side listener for `builder.secrets-scrub-needed` (clearing `gemini_key_enc`/`last4`/`set_at`) plus the periodic reconcile backstop described above. The core-side change (GitHub-token delete, event emission, contract doc, tests) shipped with this task.
@@ -423,6 +423,7 @@ This keeps the decision history honest and traceable.
423
423
  | 0329 | [**The rendered fit check is a kit floor and an advisory ship-check row, not a grader worker** ([task 1004203](https://cloudbongos.com/builders#/task/1004203), goal 1000095 — UI quality). Nothing in the pipeline rendered a page, so titles clipped by character count ran past their cards (task 1003480, task 1004204) and a second `.mSec` child would have landed on the first (task 1003078), each found only in a browser. Two new kit floors, `text-fit` (text measured word by word past its box; SVG text past its card rect) and `sibling-overlap` (in-flow, block-level, text-bearing siblings), run in every `render.js` render; `scripts/gds/render-check.js` renders the pages a branch changed at 1280 + 390 and `/ship-check` shows them as an ADVISORY row that never flips its exit (ADR 0162). Not a grader worker: that would put a browser, fixtures and a harness on the grader host for signal the builder already saw; the artist gate (ADR 0241) stays the human look before deploy.](0329-the-rendered-fit-check-is-a-kit-floor-and-an-advisory-ship-check-row.md) | ui-design / kit + ship-check |
424
424
  | 0330 | [**An ask is answered where it was asked** ([task 1004216](https://cloudbongos.com/builders#/task/1004216), goal 1000095 — BONGOS-V2), amending [ADR 0187](0187-collab-four-decisions.md) §4a. `lifecycle_004` let a builder ASK for help and let the **asker** mark their own row answered; the person being **asked** had no write at all, so the answer happened somewhere Bongos cannot see and the row sat open until its author remembered it — six asks addressed to the owner had been open between four and thirteen days on 2026-09-23, every one of them answerable. That was not an oversight: §4a deferred conversation to the Agora and a test named *"there is deliberately NO reply thread"* enforced it. **The deferral lost its addressee** — task 1002946 is ABANDONED and goal 1000064 ARCHIVED (verified against the live DB), so the rule had stopped meaning "wait for the Agora" and started meaning "an ask stays unanswerable forever to keep a promise to something that does not exist". **Decisions:** (1) an ask carries a thread of replies, APPEND-ONLY in the SCHEMA rather than by convention — no `updated_at`, `edited_by`, `deleted_at` or `parent_reply_id`, no reactions, no mentions, and a trigger that refuses UPDATE and DELETE outright, because a thing you can edit, nest, react to and address at people is a chat and that is what §4a declined; any builder may reply, since the board read is already project-wide and the good case is somebody you did not ask knowing the answer; (2) the **ADDRESSEE** may close an ask (`answered`), widening an asker-only UPDATE that was coherent only while nothing could be said on the row — but `withdrawn` does NOT widen, since "I no longer need this" is the asker's sentence alone and widening it would be a way to make an ask vanish unanswered; `settled_by` records which of the two closed it, and ADR 0187 §3's consent posture is untouched — closing ends a question, it does not accept work; (3) `/collab-review` posts the reply and **leaves the ask open** (owner decision), since an answer the reader has not read is exactly what an unattended run must halt on. **Consequences:** a reply cannot be taken back, by design; the FK **RESTRICTS rather than cascades** — the first draft cascaded, which reads right and was wrong, because a cascade is a plain DELETE the trigger refuses, so deleting an answered ask failed outright (caught by applying the migration to a real Postgres, not by reading it); the old test was REPLACED, not deleted, so the record that the restraint was deliberate survives; and replies inherit the pull-not-push posture, so nothing still tells an asker they were answered. Rejected: a single `answer` column (cheapest and unambiguously not a chat, but a follow-up would have nowhere to go except a second ask, splitting one conversation across two rows); waiting for the Agora (abandoned — waiting means forever); letting anyone close any ask (an open question is somebody's, and a stranger ending it should not be one click away).](0330-an-ask-is-answered-where-it-was-asked.md) | collab / lifecycle / schema |
425
425
  | 0331 | [**The sky is the ideator's home, and the project sky takes the goal map's page** ([task 1004229](https://cloudbongos.com/builders#/task/1004229), goal 1000095 — BONGOS-V2), amending [ADR 0227](0227-a-task-network-map-is-refused-the-goal-graph-is-the-map.md) and decision 11 of `docs/specs/<redacted>.md`. The owner reviewed an interactive prototype of the ideator sky and the project sky over five rounds on 2026-09-24 and adopted it AS IS — copy, layout, colours, tour text, toggles, interactions — which contradicts two things already written down, so they are rewritten rather than leaving the build graded against a contract nobody means any more. **Decisions:** (1) the sky is the ideator's HOME surface — it replaces *Your thinking*, and the project sky replaces the goal map page — so "never load-bearing" and "arrives last" are DROPPED as a sequencing hedge from before there was a design to look at; the rule that survives at full force is the accessibility half, that the sky is never the ONLY path to anything it shows (the list drawer — the room — is one key away, and every drawn thing is a row in it); (2) ADR 0227 is AMENDED, not reversed — its measured refusal of a whole-corpus task map stands, and the project sky is §1's own prescription rather than its refusal: the goal graph is the entry point, tasks are drawn only INSIDE one goal past a zoom threshold (the per-goal drill-down §10.5 measured readable at 85.2px pitch worst case), below that they are dust density on the rings with NO identities, and the goal map's table survives as the sky's ledger drawer (§6's table equivalent kept); the nav slot the goal map was deliberately denied is GRANTED to the project sky, under Goals (owner, 2026-09-24), so part 4 changes the page and the test asserting no slot together; (3) the five states and the two channels are UNTOUCHED — criterion `ideator-sky-tells-the-truth` names a model, not a picture, and this amendment changes the picture's standing only; (4) the two HEURISTICS in the prototype — an idea's origin guessed from its text, and "born of a thought" found by scanning text for idea ids — must not ship as ledger facts: parts 3 and 4 hide both layers, part 5 replaces them with a `filed_via` column and a real born-of read. **Consequences:** `docs/design/mocks/sky/sky.template.html` is the PIXEL CONTRACT for parts 2–5, and every deviation a gate or the file ratchet forces is listed in that part's ship notes; no snapshot JSON or built page is ever committed (it carries real task and idea titles and this repo is public); ADR 0264's deleted goal "Living project sky" is superseded by this work under Working area 3. Rejected: shipping the sky as an extra and keeping "never load-bearing" (makes the adopted home surface unroutable); reversing ADR 0227 (its refusal is measured and still correct — the sky needs its drill-down shape, not its repeal); shipping the heuristics behind a disclaimer (a labelled guess is still a guess in a picture whose promise is that it cannot be faked).](0331-the-sky-is-the-ideators-home.md) | design / ideator / sky |
426
+ | 0332 | [**Offboarding scrubs stored secrets: an event, not a direct write, and reactivation restores neither** (spike [task 1003338](https://cloudbongos.com/builders#/task/1003338), goal 1000110 — BONGOS-V2). `deactivateBuilder` revokes sessions but never scrubs the two third-party credentials the platform holds on a departing builder's behalf: the `github_oauth_tokens` row and the per-builder Gemini BYOK key (`gemini_key_enc`/`last4`/`set_at`, module-owned by `art-pipeline` per [ADR 0102](0102-finish-bounding-the-kernel.md)). **Decisions:** (1) `deleteGithubToken` is called synchronously inside `deactivateBuilder`'s existing transaction — the table is core's own; (2) a new best-effort event, `builder.secrets-scrub-needed`, is emitted post-commit (mirroring the existing `builder.box-reconcile-needed` pattern) so `art-pipeline` — or any future module storing a builder-keyed secret — can clear its own store without core reaching across the module boundary ADR 0102 drew; (3) reactivation restores NEITHER secret, symmetric with rank ("not auto-restored… re-promotes deliberately") — a returning builder re-enters their own key and re-derives a fresh GitHub token. **Rejected:** a direct `UPDATE` nulling the Gemini-key columns from core (re-opens the ADR 0102 leak); deleting the `builders` row to get `ON DELETE CASCADE` for free (breaks the explicit lossless design of graceful exit); a cron sweep (a new mechanism where an event already fits); auto-restoring the key on reactivation (a stored credential is not earned state). The core-side change (GitHub-token delete, event emission, contract doc, tests) shipped with this task; follow-up [task 1004239](https://cloudbongos.com/builders#/task/1004239) is narrowed to the `art-pipeline` module-side listener plus a periodic reconcile backstop.](0332-offboarding-secret-scrub-semantics.md) | security / privacy / modules |
426
427
  | 0333 | [**The application vouch is a third token kind, spent once, bound to the login it names, and carries a handle only past the consent floor** ([task 1002285](https://cloudbongos.com/builders#/task/1002285), goal 1000045 — criterion C4), resolving [ADR 0208](0208-application-lifecycle-the-handle-waits-and-the-vouch-has-no-minter.md) D1 + D2. The hub's join relay now carries a signed application vouch in `<redacted>`, so the ADR 0260 live-record path is no longer dormant. `mintHubApplicationVouch` / `<redacted>` share one envelope with the login and logout tokens, and each verifier demands what the other two lack; `verifyHubAssertion` now refuses any `purpose`, closing the device-login replay. The jti is a chosen, separate single-use store; a vouch must carry a jti and an audience, and must name the login the request names, or the row is written unvouched (never refused); a vouched re-application takes over an unvouched pending row for its login. The vouch carries its login as `applicant_login`, so even an un-upgraded core refuses it as a login. `applicant_handle` (`core_254`) is identity, not record, and rides only past the consent floor.](0333-the-application-vouch-is-a-third-token-spent-once-and-bound-to-its-login.md) |
427
428
  | 0334 | [**The recruiting directory lists a hidden builder by name, and hide changes a row's shape, not whether it exists** ([task 1002291](https://cloudbongos.com/builders#/task/1002291), R15 — goal 1000110). Privacy spec D2 (owner-signed) lists an active, public, hidden account on the recruiter surface "name-only (if not opted out)"; the directory dropped it outright, making hide a second opt-out while Settings told that builder recruiters would see their name and picture. `GET /scouting` now answers two tiers: `builders` (ranked, `scoutingListableSql`, unchanged) and `name_only` (`scoutingNameOnlySql` = the same predicate with the hide term flipped, so the tiers partition the directory and only the opt-out removes anyone), projected to `github_login`, `display_name`, `avatar_url` by `scouting-name-only.js`. The existence floor stays (a private account is the archon-floor D3 sliver's, never the directory's); presence never depends on activity; the only order is the login; the predicate is pinned to that one reader and its three columns. The hall's `/people` renders the tier as its own panel, never ranked, found by name, never by a role filter. Rejected: fixing only the copy; trailing rows in the ranked ledger; listing private discoverable accounts by name.](0334-the-recruiting-directory-lists-a-hidden-builder-by-name.md) | privacy / recruiting / hall |
428
429
  | 0335 | [**An invite is a pre-approved row on the project's own instance, and `project.recruit` says who may send one** ([task 1002813](https://cloudbongos.com/builders#/task/1002813), R1 of the project admin console wave, goal 1000110 — BONGOS-V2). Supersedes ADR 0141 §5's "Invitations (deferred slice)" paragraph only. Formalises spike task 1002786's three decisions and reconciles them with six weeks of shipping: the instance invite route (task 1003044), the PATCH rescind (ADR 0201 D4.2) and the composed join door (ADR 0247) already exist, and the hub invite's dead end is now reachable from the creation wizard. **Decisions:** (D1) authority lives on the project's own instance, and the instance IS the project; the hub is identity, relay, echo, notification and links, and never writes a project's admission row; (D2) a recruit invite is an `access_requests` row with `status='invited'`, so signing in is the acceptance act and the gate is untouched; the row gains `kind ∈ (application, invite)` and `created_by` (the inviter, which a rescind would otherwise overwrite), reuses `note`, backfills exactly (`created_at = resolved_at`), and neither column ever enters an admission read; (D3) one new grantable atom, `project.recruit` (`system:false`, floor archon, widened per instance through the existing governance tab), guards the invite, the outstanding list, the invite rescind and the queue read, while `access_request.review` keeps the verdict; because inviting is admitting, a recruiter without the review atom may not approve or re-admit an APPLICANT through the invite route (409 to the queue); the hub sliver moves onto the atom, and the hub directory and hub invite KEEP `project.curate`, revising ADR 0210/0215/0248, since swapping the directory would raise a gate on a viewing surface (ADR 0217). **Task map:** R2 (task 1002814) = the migration, the invite writes and the sender list, still on `access_request.review`; R3 (task 1002815) = mint the atom, move the gates, the 409 clause, the sliver swap and the pins, and it lands after R2; R5 (task 1002817) is already delivered, so close it into task 1002331; task 1003362 folds into R3. Rejected: a new invitations table; the hub pending row as the invite; widening `access_request.review` instead of minting an atom; a per-project settings row for the floor; letting a recruiter's invite approve applications; `system:true`; swapping `project.curate` on the hub directory.](0335-an-invite-is-a-pre-approved-row-on-the-projects-own-instance.md) | governance / onboarding / platform-identity |
@@ -4598,6 +4598,44 @@
4598
4598
  ]
4599
4599
  }
4600
4600
  },
4601
+ "/connections/suggestions": {
4602
+ "get": {
4603
+ "operationId": "get_connections_suggestions",
4604
+ "tags": [
4605
+ "connections"
4606
+ ],
4607
+ "summary": "GET /connections/suggestions",
4608
+ "description": "\"builders you may know\" (R21, task 1002297; ADR 0202 D5). Own-scope by construction: the candidate pool is the SESSION's own frontier, and every candidate already clears the same visibility floor the rest of the feature does (D4), so nothing here can reveal a hidden builder. count is the array's own length, same rule as every list above.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
4609
+ "x-rank": "any-builder",
4610
+ "x-source": "modules/platform-identity/routes/connections.js",
4611
+ "responses": {
4612
+ "200": {
4613
+ "description": "Success.",
4614
+ "content": {
4615
+ "application/json": {
4616
+ "schema": {
4617
+ "$ref": "#/components/schemas/GetConnectionsSuggestionsResponse"
4618
+ }
4619
+ }
4620
+ }
4621
+ },
4622
+ "400": {
4623
+ "$ref": "#/components/responses/BadRequest"
4624
+ },
4625
+ "401": {
4626
+ "$ref": "#/components/responses/Unauthorized"
4627
+ },
4628
+ "403": {
4629
+ "$ref": "#/components/responses/Forbidden"
4630
+ }
4631
+ },
4632
+ "security": [
4633
+ {
4634
+ "builderSession": []
4635
+ }
4636
+ ]
4637
+ }
4638
+ },
4601
4639
  "/connections/{id}": {
4602
4640
  "delete": {
4603
4641
  "operationId": "delete_connections_id",
@@ -19900,6 +19938,17 @@
19900
19938
  "count"
19901
19939
  ]
19902
19940
  },
19941
+ "GetConnectionsSuggestionsResponse": {
19942
+ "type": "object",
19943
+ "properties": {
19944
+ "suggestions": {},
19945
+ "count": {}
19946
+ },
19947
+ "required": [
19948
+ "suggestions",
19949
+ "count"
19950
+ ]
19951
+ },
19903
19952
  "GetCoreUpdateResponse": {
19904
19953
  "type": "object",
19905
19954
  "properties": {
@@ -26469,9 +26518,9 @@
26469
26518
  "description": "A required dependency/feature is not configured or is temporarily down."
26470
26519
  }
26471
26520
  },
26472
- "x-endpoint-count": 417,
26473
- "x-schema-count": 454,
26521
+ "x-endpoint-count": 418,
26522
+ "x-schema-count": 455,
26474
26523
  "x-undocumented-bodies": 9,
26475
- "x-response-schemas": 309,
26524
+ "x-response-schemas": 310,
26476
26525
  "x-generated-by": "scripts/gds/gen-api-docs.js"
26477
26526
  }
@@ -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. 417 endpoints across 71 route files.
5
+ > **Generated from the live route files** — the route file is authoritative. 418 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`.
@@ -193,7 +193,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
193
193
  |---|---|---|---|---|
194
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
195
 
196
- ## `connections` (7)
196
+ ## `connections` (8)
197
197
 
198
198
  | Method | Path | Rank | Body | Description |
199
199
  |---|---|---|---|---|
@@ -204,6 +204,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
204
204
  | POST | `/api/bongos/connections/:id/decline` | `any-builder` | — | decline a request addressed to ME. |
205
205
  | GET | `/api/bongos/connections/degree/:handle` | `any-builder` | — | the degree label between the CALLER and one handle, plus the mutuals it was computed from (own-scope read, D4 §4). |
206
206
  | GET | `/api/bongos/connections/requests` | `any-builder` | — | MY pending requests, both directions (own data). |
207
+ | GET | `/api/bongos/connections/suggestions` | `any-builder` | — | "builders you may know" (R21, task 1002297; ADR 0202 D5). |
207
208
 
208
209
  ## `copy-desk` (5)
209
210
 
@@ -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
- **2037 strings** across 3 surfaces · 1738 distinct pieces of copy · 2037 certain, 0 heuristic · 212 shown in more than one place.
7
+ **2038 strings** across 3 surfaces · 1739 distinct pieces of copy · 2038 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 | 1546 |
13
+ | `builders-hall` | `modules/hall-ui/public` | 102 | 1547 |
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` — 1546 strings
81
+ ## `builders-hall` — 1547 strings
82
82
 
83
83
  | id | conf | text | where |
84
84
  |---|---|---|---|
@@ -218,6 +218,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
218
218
  | `1596210cea76` | certain | Build from your own machine — clone the repository, run the platform locally, and sign in from the terminal. | `modules/hall-ui/public/hall-render.js:864` |
219
219
  | `29bd516ad17e` | certain | Builder directory | `modules/hall-ui/public/people.html:37` |
220
220
  | `08bfcc9198ba` | certain | Builder id | `modules/hall-ui/public/government.html:109` |
221
+ | `306bc316184d` | certain | Builders you may know | `modules/hall-ui/public/profile-connections.js:158` |
221
222
  | `60b248791dc3` | certain | Builders — {{worldName}} | `modules/hall-ui/public/roster.html:6` |
222
223
  | `60b248791dc3` | certain | Builders — {{worldName}} | `modules/hall-ui/public/roster.html:6` |
223
224
  | `a78477a24a48` | certain | Builders' Hall | `modules/hall-ui/public/index.html:12` |
@@ -892,8 +893,8 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
892
893
  | `e6629398d47c` | certain | Report {…}. | `modules/hall-ui/public/watch.js:574` |
893
894
  | `ab456f09c0f6` | certain | Request access | `modules/hall-ui/public/hall-render.js:294` |
894
895
  | `ab456f09c0f6` | certain | Request access | `modules/hall-ui/public/hall-render.js:470` |
895
- | `820f47ce5f16` | certain | Request sent | `modules/hall-ui/public/profile-connections.js:86` |
896
- | `820f47ce5f16` | certain | Request sent | `modules/hall-ui/public/profile-connections.js:134` |
896
+ | `820f47ce5f16` | certain | Request sent | `modules/hall-ui/public/profile-connections.js:95` |
897
+ | `820f47ce5f16` | certain | Request sent | `modules/hall-ui/public/profile-connections.js:143` |
897
898
  | `e86e43684bce` | certain | Request sent — the owner or a manager will review it. | `modules/hall-ui/public/goals-render.js:1384` |
898
899
  | `280f20e7ddf5` | certain | Request withdrawn. | `modules/hall-ui/public/goals-render.js:1400` |
899
900
  | `a030881168b5` | certain | Reset to default | `modules/hall-ui/public/government.js:267` |
@@ -974,7 +975,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
974
975
  | `9bea6404e6e1` | certain | Sign in | `modules/hall-ui/public/settings.html:42` |
975
976
  | `2f12d46f96ca` | certain | Sign in from the app. | `modules/hall-ui/public/hall-render.js:825` |
976
977
  | `19ab56cc3932` | certain | Sign in to filter to your goals | `modules/hall-ui/public/goals-page.js:881` |
977
- | `969f95f183ca` | certain | Sign in to the platform once to activate your profile before connecting. | `modules/hall-ui/public/profile-connections.js:220` |
978
+ | `969f95f183ca` | certain | Sign in to the platform once to activate your profile before connecting. | `modules/hall-ui/public/profile-connections.js:253` |
978
979
  | `b057dfbf49c5` | certain | Sign in when Claude prompts you. | `modules/hall-ui/public/hall-render.js:825` |
979
980
  | `e57ec24e13ef` | certain | Sign in with GitHub | `modules/hall-ui/public/hall-render.js:275` |
980
981
  | `e57ec24e13ef` | certain | Sign in with GitHub | `modules/hall-ui/public/hall-render.js:367` |
@@ -992,7 +993,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
992
993
  | `e7b592185803` | certain | Someone is holding a claim on this task. It has to be released before the task can be retired. | `modules/hall-ui/public/task.js:753` |
993
994
  | `867df58c9aaa` | certain | Something else | `modules/hall-ui/public/copy-desk.html:95` |
994
995
  | `e11c18877155` | certain | Something is here. | `modules/hall-ui/public/sky-interaction.js:36` |
995
- | `c377d8ed744b` | certain | Something went wrong. Try again. | `modules/hall-ui/public/profile-connections.js:275` |
996
+ | `c377d8ed744b` | certain | Something went wrong. Try again. | `modules/hall-ui/public/profile-connections.js:319` |
996
997
  | `e0767e80206c` | certain | Sound for {…} | `modules/hall-ui/public/settings.js:309` |
997
998
  | `7f4f7b25c61f` | certain | Sound preference saved. | `modules/hall-ui/public/settings.js:326` |
998
999
  | `d03aa6752970` | certain | Specialities this project recommends. Nothing here is switched on — read what each one adds to your sessions, | `modules/hall-ui/public/settings-specialities.js:137` |
@@ -1038,7 +1039,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1038
1039
  | `545c59c1deb7` | certain | That is the session you are using right now. Ending it signs you out of this page. | `modules/hall-ui/public/settings-sessions.js:74` |
1039
1040
  | `282c3fa2d456` | certain | That is the wording it already has. Change something, or file a flag instead if you only want to mark it. | `modules/hall-ui/public/copy-desk.js:438` |
1040
1041
  | `c0e28f30524d` | certain | That project's own standing, as reported by that project. | `modules/hall-ui/public/profile.js:314` |
1041
- | `e99d4a811e92` | certain | That request is no longer available. | `modules/hall-ui/public/profile-connections.js:270` |
1042
+ | `e99d4a811e92` | certain | That request is no longer available. | `modules/hall-ui/public/profile-connections.js:314` |
1042
1043
  | `34017119ce22` | certain | That work is genuinely gone | `modules/hall-ui/public/goal-inbox.js:374` |
1043
1044
  | `6e5a324b2972` | certain | That's you | `modules/hall-ui/public/watch.js:803` |
1044
1045
  | `50198c465225` | certain | The 16-value rank enum in three bands, which ranks are live, and the permissions each rank holds. | `modules/hall-ui/public/diagrams.html:59` |
@@ -1117,7 +1118,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1117
1118
  | `df316c3484de` | certain | Then drive it however you like: | `modules/hall-ui/public/hall-render.js:825` |
1118
1119
  | `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` |
1119
1120
  | `a0140098c0e0` | certain | They already have an invitation or a join-request pending on this goal. | `modules/hall-ui/public/goals-render.js:1303` |
1120
- | `5151cf635644` | certain | They have asked to connect. | `modules/hall-ui/public/profile-connections.js:203` |
1121
+ | `5151cf635644` | certain | They have asked to connect. | `modules/hall-ui/public/profile-connections.js:236` |
1121
1122
  | `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` |
1122
1123
  | `ccd0ccb80fae` | certain | They joined this goal already. | `modules/hall-ui/public/goals-render.js:1304` |
1123
1124
  | `27f77408aed0` | certain | Things stuck on the owner — a credential, a purchase, a product call only they can make. Resolving one un-stic | `modules/hall-ui/public/blockers.html:26` |
@@ -1534,7 +1535,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1534
1535
  | `a65d371b5cb3` | certain | {…} and above can enable or disable a module | `modules/hall-ui/public/modules.js:130` |
1535
1536
  | `98b1c674fc37` | certain | {…} and up | `modules/hall-ui/public/sessions.js:187` |
1536
1537
  | `111b71a9acaf` | certain | {…} and up. | `modules/hall-ui/public/sessions.js:185` |
1537
- | `0da0e247ff0f` | certain | {…} asking to connect | `modules/hall-ui/public/profile-connections.js:136` |
1538
+ | `0da0e247ff0f` | certain | {…} asking to connect | `modules/hall-ui/public/profile-connections.js:145` |
1538
1539
  | `788ff11f1135` | certain | {…} awarded | `modules/hall-ui/public/task.js:478` |
1539
1540
  | `9d160147b34a` | certain | {…} criteria · since {…} | `modules/hall-ui/public/roadmap.js:153` |
1540
1541
  | `9d160147b34a` | certain | {…} criteria · since {…} | `modules/hall-ui/public/roadmap.js:176` |
@@ -1563,7 +1564,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1563
1564
  | `955499559f0a` | certain | {…} tokens | `modules/hall-ui/public/sessions.js:170` |
1564
1565
  | `8e28c14b106d` | certain | {…} total | `modules/hall-ui/public/roadmap.js:120` |
1565
1566
  | `1a8a6267e490` | certain | {…} unfinished task{…} — say what happens to each | `modules/hall-ui/public/goals-render.js:1036` |
1566
- | `3ad8f32dd43b` | certain | {…} you have sent | `modules/hall-ui/public/profile-connections.js:137` |
1567
+ | `3ad8f32dd43b` | certain | {…} you have sent | `modules/hall-ui/public/profile-connections.js:146` |
1567
1568
  | `8186b562553a` | certain | {…} {…} waiting | `modules/hall-ui/public/work.js:416` |
1568
1569
  | `0d4016a5531c` | certain | {…} {…}{…} | `modules/hall-ui/public/watch.js:474` |
1569
1570
  | `0d4016a5531c` | certain | {…} {…}{…} | `modules/hall-ui/public/work.js:249` |