@bongos/core 1.19.417 → 1.19.418

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.417",
6
- "core_contract": "1.19.417",
7
- "source_commit": "2ea84040287b365331cb8eb9278615dc4a0e5642",
5
+ "core_version": "1.19.418",
6
+ "core_contract": "1.19.418",
7
+ "source_commit": "e87d7993f31b4954beb97e7d65573bc9410f1192",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-29T16:38:30.577Z",
9
+ "built_at": "2026-08-29T16:52:59.816Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
- "docs_redacted": 356,
12
+ "docs_redacted": 357,
13
13
  "agent_docs_stubbed": 23,
14
- "functional_verbatim": 1701,
14
+ "functional_verbatim": 1704,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2080,
20
- "tree_sha256": "4d8a309a1363b0a74ea89df369c37d35b0af343cd3ed08d4361f8951165933b3",
19
+ "file_count": 2084,
20
+ "tree_sha256": "8422d1d3312cf70f1ec09e532c01c0c368328ee68577712e0a6f98df0078f338",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -352,12 +352,12 @@
352
352
  {
353
353
  "path": "clients/bongos-client/README.md",
354
354
  "mode": "0000644",
355
- "sha256": "80d3bb0a3dc011b0c2b16558b20c1ea2adc3e5345418a8d08f13a7cd2ac5eb9a"
355
+ "sha256": "2d74abb8d89022d621f105f019b2d0e4c7cef399e14e54fab64fb443b0f49179"
356
356
  },
357
357
  {
358
358
  "path": "clients/bongos-client/bongos-client.global.js",
359
359
  "mode": "0000644",
360
- "sha256": "833fecb6824cb535eb990a8f3a516528c6d560eee856323f5d824afe5bcf7932"
360
+ "sha256": "02b57e81ee53386d1392c769fb39adda73440fe016699b0e6b3a3b01a554d060"
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": "bf528220a1990c760111ef291e6d6d6543b62440ab62c3962a8772e897218c69"
370
+ "sha256": "07dc39d1991ca379b67de839752cfc4578036172f113d15b96d4a39b0eb7f9b4"
371
371
  },
372
372
  {
373
373
  "path": "clients/bongos-client/index.d.ts",
374
374
  "mode": "0000644",
375
- "sha256": "84fce1d4d0fba5b586f1a965a7b4b6818ad7d27e7bbce68818e9def0db85724b"
375
+ "sha256": "41363423243953f69a663ec425c439e61149f139ad7f0a3c721fd1f7a02047ef"
376
376
  },
377
377
  {
378
378
  "path": "clients/bongos-client/index.mjs",
379
379
  "mode": "0000644",
380
- "sha256": "b07a09551c9af7e526aaf2416e5340b112f7b8b8ffc28c49afe252092a215f9c"
380
+ "sha256": "9cd5857fc781c8518d9cfedfe690af7487dde2da53fef0861bb6c6cded4d9b2e"
381
381
  },
382
382
  {
383
383
  "path": "clients/bongos-client/package.json",
@@ -1529,25 +1529,30 @@
1529
1529
  "mode": "0000644",
1530
1530
  "sha256": "dedf9285b041a0d4c54982a8372aca573e57eb3d89a5abf9381a922a8784ff7f"
1531
1531
  },
1532
+ {
1533
+ "path": "docs/adr/0206-own-scoped-session-revocation-no-absolute-ceiling.md",
1534
+ "mode": "0000644",
1535
+ "sha256": "634404f2eb0d29386460e0ea008a826069d80bfbd42721aa7b2cd4d74cb4fa86"
1536
+ },
1532
1537
  {
1533
1538
  "path": "docs/adr/README.md",
1534
1539
  "mode": "0000644",
1535
- "sha256": "3d9a57147d8a7e8db350df79ecc457cf57bdadd77d08b41749dfa658c733c07c"
1540
+ "sha256": "fb4ae772f6d0289385dc1caddd24a2271510d85f4b4eb6d039248310defc84d7"
1536
1541
  },
1537
1542
  {
1538
1543
  "path": "docs/api-reference.md",
1539
1544
  "mode": "0000644",
1540
- "sha256": "dc283ca75e6aa5b2dcede50879f7c7f7beb151ae953282c917621b4dd6d6b2e1"
1545
+ "sha256": "05d778a5f5d44ef620fc266add8626f344308ff44f031b16a65441a1de38d67c"
1541
1546
  },
1542
1547
  {
1543
1548
  "path": "docs/api/openapi.json",
1544
1549
  "mode": "0000644",
1545
- "sha256": "b0f8cab2aa5931ef9090bbee552ff00f3da37a86ce327225b11712f16cd25090"
1550
+ "sha256": "cc4545032233412b8938f8d399d24a3427789f5213dfdd29f33b3de1c4c3c3ac"
1546
1551
  },
1547
1552
  {
1548
1553
  "path": "docs/architecture.md",
1549
1554
  "mode": "0000644",
1550
- "sha256": "68d30a764a36e963d40e4271010c56b61a824b9e2fad61599fdc62ee1bc2fddf"
1555
+ "sha256": "d24b71683aab15bb19ffd110c3d214dade7f2be4c5cc81740153c92ea8ccc252"
1551
1556
  },
1552
1557
  {
1553
1558
  "path": "docs/branding-contract.md",
@@ -1567,12 +1572,12 @@
1567
1572
  {
1568
1573
  "path": "docs/copy-inventory.md",
1569
1574
  "mode": "0000644",
1570
- "sha256": "c462f7be788726d0bbe0186dcaa7288fa81b23550057ce9f579bc5b3d6096f44"
1575
+ "sha256": "3e70beb03c93a71f5068c407467deeeca3c69d5271372de04399a9094fa32dd9"
1571
1576
  },
1572
1577
  {
1573
1578
  "path": "docs/copy-registry.json",
1574
1579
  "mode": "0000644",
1575
- "sha256": "429fbb99f080c8bb80c8b8fc9539eb632dab1a4f6bb0534170667c76e055001d"
1580
+ "sha256": "ae4ffbc86e556ea5463b8289754b70e9465a581e93a40f8713204c34d433e00a"
1576
1581
  },
1577
1582
  {
1578
1583
  "path": "docs/design/apex-pass-2-direction.md",
@@ -1907,7 +1912,7 @@
1907
1912
  {
1908
1913
  "path": "docs/file-map.md",
1909
1914
  "mode": "0000644",
1910
- "sha256": "76febc87dd3d8b6641673093df2efa7c699a33428e6a53bc1cf595e0f3daf558"
1915
+ "sha256": "2464bcb60656e263ef00002c928a2e6f0d4b812b7a7b8183c634535f1192e58d"
1911
1916
  },
1912
1917
  {
1913
1918
  "path": "docs/handoff-template.md",
@@ -1917,7 +1922,7 @@
1917
1922
  {
1918
1923
  "path": "docs/module-api-changelog.md",
1919
1924
  "mode": "0000644",
1920
- "sha256": "402fcd99a486b88a9c7e0acccd85d95effbbe263dec1f917f7bfc1e07349f46c"
1925
+ "sha256": "11919db7fb659bf498544aa14b39cef44166ed3ad596784281e8f6cd7efd6fa5"
1921
1926
  },
1922
1927
  {
1923
1928
  "path": "docs/modules-contract.md",
@@ -3227,7 +3232,7 @@
3227
3232
  {
3228
3233
  "path": "modules/dev-box/app/src/vendor/bongos-client.cjs",
3229
3234
  "mode": "0000644",
3230
- "sha256": "bf528220a1990c760111ef291e6d6d6543b62440ab62c3962a8772e897218c69"
3235
+ "sha256": "07dc39d1991ca379b67de839752cfc4578036172f113d15b96d4a39b0eb7f9b4"
3231
3236
  },
3232
3237
  {
3233
3238
  "path": "modules/dev-box/app/test/unit.mjs",
@@ -4144,15 +4149,20 @@
4144
4149
  "mode": "0000644",
4145
4150
  "sha256": "e99046a2c0e2cb6ce9f05711ddbe6ed9d2e7536086dca16496ef0e5e8cb9a041"
4146
4151
  },
4152
+ {
4153
+ "path": "modules/hall-ui/public/settings-sessions.js",
4154
+ "mode": "0000644",
4155
+ "sha256": "c0c207769631f027ca35d219105e2189e2d259e7b45bfeaa1245fd749d7e91e5"
4156
+ },
4147
4157
  {
4148
4158
  "path": "modules/hall-ui/public/settings.css",
4149
4159
  "mode": "0000644",
4150
- "sha256": "4ff2a298547ad65d92b2511b0ea98c4adfe8f173833585eb34197f29255086b7"
4160
+ "sha256": "d50ac6727dde53e91aef8daa5ce759edcc69924e43b786103800892d9b87fe4d"
4151
4161
  },
4152
4162
  {
4153
4163
  "path": "modules/hall-ui/public/settings.html",
4154
4164
  "mode": "0000644",
4155
- "sha256": "852ce177610c5bc49145333b157700d8c3566565d106dcf26c476d32b7105609"
4165
+ "sha256": "88886109031ceb77f4a4efbe133af5a2ac8e26ec5dd55e2e71a0bbe5152887bc"
4156
4166
  },
4157
4167
  {
4158
4168
  "path": "modules/hall-ui/public/settings.js",
@@ -5432,12 +5442,12 @@
5432
5442
  {
5433
5443
  "path": "package-lock.json",
5434
5444
  "mode": "0000644",
5435
- "sha256": "64e99f4a6c987054fa4827a376eace859767c9f5edc34676a60b5e0e19cb1581"
5445
+ "sha256": "022b69ded283068fcd60758e3dbb676e64fd660c782700e3d52da3f7c00b8b1e"
5436
5446
  },
5437
5447
  {
5438
5448
  "path": "package.json",
5439
5449
  "mode": "0000644",
5440
- "sha256": "29aa7a9418a4755ab9c7e20a439d84f12ea48407851c6634ff9669c809940332"
5450
+ "sha256": "39ac5d6d5119b7473a0247f6296c83d175af6323f9d6b3c23ad83f6ff89f332e"
5441
5451
  },
5442
5452
  {
5443
5453
  "path": "public-docs/index.html",
@@ -7317,7 +7327,7 @@
7317
7327
  {
7318
7328
  "path": "src/bongos/db.js",
7319
7329
  "mode": "0000644",
7320
- "sha256": "3c34327680f89ae6a442bf6d75c1ac3ba570e0a08df2efa063d55b84727ba9ea"
7330
+ "sha256": "feff54089b6bae5845558d262c07ec30d9532419d42c04f1f113bc2fc95d9173"
7321
7331
  },
7322
7332
  {
7323
7333
  "path": "src/bongos/github-repos.js",
@@ -7417,12 +7427,12 @@
7417
7427
  {
7418
7428
  "path": "src/bongos/route-rank-check.js",
7419
7429
  "mode": "0000644",
7420
- "sha256": "ebdf0cb4cb521fbf5aa7809c39e89ad4f776e77b194c734042fae72226511674"
7430
+ "sha256": "96b884a69b7b709a256d220c97a972f9827d74a36a864c216a7b37db3fdf66be"
7421
7431
  },
7422
7432
  {
7423
7433
  "path": "src/bongos/routes.js",
7424
7434
  "mode": "0000644",
7425
- "sha256": "ede634ea623ec035a5ebab896f1f97e67e142c882cade5afaeb3e3c253fc28f8"
7435
+ "sha256": "074cded727667d864b1849d8d5505736895a69a6bdf7132375fd358d66c2e6fd"
7426
7436
  },
7427
7437
  {
7428
7438
  "path": "src/bongos/routes/CLAUDE.md",
@@ -7489,6 +7499,11 @@
7489
7499
  "mode": "0000644",
7490
7500
  "sha256": "54a42c37d4afd403a13aa5e9fda6a7d95cb0fca70dfe3cb4daef318c099a5f00"
7491
7501
  },
7502
+ {
7503
+ "path": "src/bongos/routes/my-sessions.js",
7504
+ "mode": "0000644",
7505
+ "sha256": "0e002104e2fedea2bb322826560aadf18b9885f21e214592c63b304b3ba9fd1e"
7506
+ },
7492
7507
  {
7493
7508
  "path": "src/bongos/routes/optimizer.js",
7494
7509
  "mode": "0000644",
@@ -7552,7 +7567,7 @@
7552
7567
  {
7553
7568
  "path": "src/module-api.js",
7554
7569
  "mode": "0000644",
7555
- "sha256": "0dce01d81ecb3f709416d11f10605ac834dbdb5ed9faea3f9459ddfceaa8389d"
7570
+ "sha256": "a0b22f52d4549706a689fe6e4c328f03f36d0ad340e90fc4a8bf6c555ea94b54"
7556
7571
  },
7557
7572
  {
7558
7573
  "path": "src/module-loader/catalog.js",
@@ -9462,7 +9477,12 @@
9462
9477
  {
9463
9478
  "path": "tests/own_scope_guard.mjs",
9464
9479
  "mode": "0000644",
9465
- "sha256": "694002ebe39755fd01bdba57bf70e7077201b81a6938df92fff09b44027a29cc"
9480
+ "sha256": "da02342121dbf5be0cc31c009316f0473d010e592f1a06fc9276a184a3be2846"
9481
+ },
9482
+ {
9483
+ "path": "tests/own_session_revoke.mjs",
9484
+ "mode": "0000644",
9485
+ "sha256": "756920320b74c9094d1be12108efb8ddbb39341b8d122afd9454ffd084a2efe5"
9466
9486
  },
9467
9487
  {
9468
9488
  "path": "tests/package_core.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
- - 327 operations across 52 resource groups
8
+ - 330 operations across 52 resource groups
9
9
 
10
10
  ## Install
11
11
 
@@ -523,6 +523,12 @@ function createClient(opts = {}) {
523
523
  getMeRender: (args) => request("GET", "/me/render", { hasBody: false }, args),
524
524
  // PATCH /me/render — rank: any-builder — PATCH /me/render
525
525
  patchMeRender: (args) => request("PATCH", "/me/render", { hasBody: true }, args),
526
+ // GET /me/sessions — rank: any-builder — GET /me/sessions
527
+ getMeSessions: (args) => request("GET", "/me/sessions", { hasBody: false }, args),
528
+ // POST /me/sessions/revoke — rank: any-builder — POST /me/sessions/revoke
529
+ postMeSessionsRevoke: (args) => request("POST", "/me/sessions/revoke", { hasBody: true }, args),
530
+ // POST /me/sessions/revoke-all — rank: any-builder — POST /me/sessions/revoke-all
531
+ postMeSessionsRevokeAll: (args) => request("POST", "/me/sessions/revoke-all", { hasBody: true }, args),
526
532
  // GET /me/skill-prefs — rank: any-builder — GET /me/skill-prefs
527
533
  getMeSkillPrefs: (args) => request("GET", "/me/skill-prefs", { hasBody: false }, args),
528
534
  // PATCH /me/skill-prefs — rank: any-builder — PATCH /me/skill-prefs
@@ -522,6 +522,12 @@ function createClient(opts = {}) {
522
522
  getMeRender: (args) => request("GET", "/me/render", { hasBody: false }, args),
523
523
  // PATCH /me/render — rank: any-builder — PATCH /me/render
524
524
  patchMeRender: (args) => request("PATCH", "/me/render", { hasBody: true }, args),
525
+ // GET /me/sessions — rank: any-builder — GET /me/sessions
526
+ getMeSessions: (args) => request("GET", "/me/sessions", { hasBody: false }, args),
527
+ // POST /me/sessions/revoke — rank: any-builder — POST /me/sessions/revoke
528
+ postMeSessionsRevoke: (args) => request("POST", "/me/sessions/revoke", { hasBody: true }, args),
529
+ // POST /me/sessions/revoke-all — rank: any-builder — POST /me/sessions/revoke-all
530
+ postMeSessionsRevokeAll: (args) => request("POST", "/me/sessions/revoke-all", { hasBody: true }, args),
525
531
  // GET /me/skill-prefs — rank: any-builder — GET /me/skill-prefs
526
532
  getMeSkillPrefs: (args) => request("GET", "/me/skill-prefs", { hasBody: false }, args),
527
533
  // PATCH /me/skill-prefs — rank: any-builder — PATCH /me/skill-prefs
@@ -68,6 +68,7 @@ export interface GetMeCrossProjectResponse { hide_stats: boolean; profile_state:
68
68
  export interface GetMeDisplayNameResponse { display_name: unknown; max_length: unknown }
69
69
  export interface GetMeEventSoundPrefsResponse { prefs: unknown; effective: unknown; effective_wav: unknown; events: unknown }
70
70
  export interface GetMeRecentShipsResponse { ships: unknown }
71
+ export interface GetMeSessionsResponse { sessions: unknown }
71
72
  export interface GetMeSkillPrefsResponse { prefs: unknown; effective: unknown; defaults: unknown; known_skills: unknown; allowed_models: unknown }
72
73
  export interface GetMeSoundPrefsResponse { prefs: unknown; effective: unknown; effective_map: unknown; volume: unknown; defaults: unknown; known_sounds: unknown }
73
74
  export interface GetMemoryBfgBuildersBuilderIdFileResponse { builder_id: unknown; bfg_read: boolean; path: unknown; content: unknown; sha256: unknown; byte_len: unknown; mtime: unknown; updated_at: unknown; created_at: unknown }
@@ -283,6 +284,9 @@ export interface PostLlmCacheStoreRequest { spec?: unknown; result_stdout?: unkn
283
284
  export interface PostLlmCacheStoreResponse { stored: boolean; result_cost_usd?: unknown }
284
285
  export interface PostMeAnthropicLinkRequest { anthropic_email: string; anthropic_user_id: string }
285
286
  export interface PostMeAnthropicLinkResponse { linked: unknown }
287
+ export interface PostMeSessionsRevokeAllResponse { ok: boolean; revoked: unknown }
288
+ export interface PostMeSessionsRevokeRequest { session_id?: StringifiableId }
289
+ export interface PostMeSessionsRevokeResponse { ok: boolean; revoked: unknown }
286
290
  export interface PostMemoryBuildersBuilderIdBfgWriteRequest { kind?: string; slug?: string; title?: string; body_md?: string; rationale?: string; karma_delta?: unknown }
287
291
  export interface PostMemoryBuildersBuilderIdBfgWriteResponse { ok: boolean; builder_id: unknown; path: unknown; kind: unknown; author: unknown; audit_id: unknown; karma_id: unknown; sha256: unknown; byte_len: unknown; updated_at: unknown }
288
292
  export interface PostMemorySyncRequest { files?: unknown[] }
@@ -817,6 +821,12 @@ export interface BongosClient {
817
821
  getMeRender(args?: RequestArgs): Promise<ApiResponse>;
818
822
  /** PATCH /me/render — rank: any-builder */
819
823
  patchMeRender(args?: RequestArgs): Promise<ApiResponse>;
824
+ /** GET /me/sessions — rank: any-builder */
825
+ getMeSessions(args?: RequestArgs): Promise<GetMeSessionsResponse>;
826
+ /** POST /me/sessions/revoke — rank: any-builder */
827
+ postMeSessionsRevoke(args?: RequestArgs & { body?: PostMeSessionsRevokeRequest }): Promise<PostMeSessionsRevokeResponse>;
828
+ /** POST /me/sessions/revoke-all — rank: any-builder */
829
+ postMeSessionsRevokeAll(args?: RequestArgs): Promise<PostMeSessionsRevokeAllResponse>;
820
830
  /** GET /me/skill-prefs — rank: any-builder */
821
831
  getMeSkillPrefs(args?: RequestArgs): Promise<GetMeSkillPrefsResponse>;
822
832
  /** PATCH /me/skill-prefs — rank: any-builder */
@@ -519,6 +519,12 @@ export function createClient(opts = {}) {
519
519
  getMeRender: (args) => request("GET", "/me/render", { hasBody: false }, args),
520
520
  // PATCH /me/render — rank: any-builder — PATCH /me/render
521
521
  patchMeRender: (args) => request("PATCH", "/me/render", { hasBody: true }, args),
522
+ // GET /me/sessions — rank: any-builder — GET /me/sessions
523
+ getMeSessions: (args) => request("GET", "/me/sessions", { hasBody: false }, args),
524
+ // POST /me/sessions/revoke — rank: any-builder — POST /me/sessions/revoke
525
+ postMeSessionsRevoke: (args) => request("POST", "/me/sessions/revoke", { hasBody: true }, args),
526
+ // POST /me/sessions/revoke-all — rank: any-builder — POST /me/sessions/revoke-all
527
+ postMeSessionsRevokeAll: (args) => request("POST", "/me/sessions/revoke-all", { hasBody: true }, args),
522
528
  // GET /me/skill-prefs — rank: any-builder — GET /me/skill-prefs
523
529
  getMeSkillPrefs: (args) => request("GET", "/me/skill-prefs", { hasBody: false }, args),
524
530
  // PATCH /me/skill-prefs — rank: any-builder — PATCH /me/skill-prefs
@@ -0,0 +1,59 @@
1
+ # 0206 — A leaked bearer's remedy is the account holder's own revoke, not an absolute session ceiling
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-08-29
5
+ - **Deciders:** Claude, building the own-scoped half of the session self-service. The ceiling question is the one the task named as "worth a decision either way".
6
+ - **Task:** task 1003341 (goal 1000045 — public builder profiles + account security, criterion `i-control-my-own-access-and-exposure`)
7
+ - **Interacts with:** [ADR 0016](<redacted>.md) (the DB enforces; a session is checked fresh per request, never cached), [ADR 0053](0053-scoped-dev-box-session.md) / [ADR 0151](<redacted>.md) §3 (the session-scope registry — scope is capability on the token, orthogonal to rank), migration `<redacted>.sql` (the 24h TTL + refresh-on-use it introduced, and the deliberate backfill grace that came with it).
8
+
9
+ ## Context
10
+
11
+ `db-kernel.js lookupSession` rewrites `expires_at = now() + 24h` on every authenticated request. That is refresh-on-use, and for a session in daily use it means the TTL never arrives: a CLI bearer sitting in `~/.config/otb/gds-session.json` is, in practice, immortal. If it leaks — committed to a repo, left on a shared dev box, copied off a stolen laptop — the holder keeps it alive simply by using it.
12
+
13
+ Until this task the account holder had no remedy of their own. `POST /auth/logout` revokes only the token presented on that same request, which is useless when the token you want dead is the one you no longer hold. The two endpoints that can kill somebody else's session — `POST /auth/revoke` and `POST /auth/revoke-all` — are gated on the admin `auth.session.revoke` atom, so the victim had to find an Archon and ask. On a self-hosted single-owner instance there may be no second Archon at all, which makes the remedy not merely slow but absent.
14
+
15
+ Two different fixes were on the table, and it matters that they are not the same fix:
16
+
17
+ 1. **Give the holder the remedy.** List your own live sessions, kill one, or sign out everywhere — no rank, no permission, no intermediary.
18
+ 2. **Cap the sliding window.** Stop `expires_at` at some absolute distance from `created_at`, so every session dies eventually whether anybody notices or not.
19
+
20
+ ## Decision
21
+
22
+ **Ship (1). Do not ship (2) — for now, and for stated reasons rather than by omission.**
23
+
24
+ The own-scoped routes land as `GET /me/sessions`, `POST /me/sessions/revoke` and `POST /me/sessions/revoke-all`, in their own file (`src/bongos/routes/my-sessions.js`), gated on `requireBuilder` alone. Every statement they issue carries `builder_id = <the caller's own id>`, taken from the session the caller presented and never from the query string, the body or the path. `revoke-all` deliberately includes the session making the request: this is the button somebody presses when they believe a token has leaked, and a sweep that spared one session would leave them wondering which. The token value is never selected, never logged and never returned.
25
+
26
+ **Why no absolute ceiling.** A ceiling and a revoke are aimed at different things, and only one of them was the gap:
27
+
28
+ - A ceiling is **unconditional**: it signs out every builder, every dev box, every long-running autonomous session on a timer, whether or not anything has gone wrong. Migration 035 already met this problem once, in the small — it backfilled a 24h grace *specifically* so the migration would not kill the session running it — and a ceiling reintroduces that class of breakage permanently rather than once.
29
+ - A ceiling is **slow where it matters**: any cap loose enough not to be a daily nuisance (30 days, 90 days) leaves a leaked token live for weeks. The victim who knows their token leaked does not want it to expire eventually; they want it dead now. That is exactly what (1) gives them, in one request.
30
+ - A ceiling has **no owner-visible upside** once (1) exists. Its remaining value is against a leak nobody ever notices — real, but strictly weaker than the visibility this task also ships: the list surfaces `created_at` beside `last_used_at`, so a session that has been alive for three months is now something a person can *see* and act on, which is the compounding half of the fix.
31
+ - Rank changes already do the sweeping a ceiling would do. `setBuilderRank` calls `revokeAllSessionsForBuilder` on any demotion, and `requireBuilder` denies a deactivated builder on the hot path regardless of a live token, so the authority-drift case a ceiling is often justified by is already closed by other means.
32
+
33
+ The ceiling stays deliberately unbuilt, not forgotten. What would change the answer is stated below.
34
+
35
+ ### Rejected
36
+
37
+ - **An absolute `created_at + N` cap now.** Breaks every long-lived box and autonomous session on a timer for a threat the revoke answers faster; and any N large enough to be tolerable is too large to be a remedy.
38
+ - **A shorter TTL.** Same objection, worse: it punishes ordinary use continuously and still does nothing the moment a token is in someone else's hands.
39
+ - **Three more routes inside `routes/auth.js`.** That file is the largest in `src/bongos/routes/` and its admin pair legitimately reads a caller-named target from the request body. Keeping the own-scoped half separate is what lets `tests/own_session_revoke.mjs` assert the no-caller-named-owner rule over a whole file, with no carve-out for a neighbour to hide behind.
40
+ - **A `keep_current` flag on revoke-all.** Configurability nobody asked for: somebody who wants to keep one session revokes the others individually — the list marks which one is current.
41
+ - **404 on a session id that is not yours.** It would confirm which ids exist. A miss answers `revoked: 0`, identically to an id that never existed.
42
+ - **Lowering the `auth.session.revoke` floor instead.** That widens an admin atom to reach *other people's* sessions, which is the opposite of what a self-serve remedy needs.
43
+
44
+ ## Consequences
45
+
46
+ - A builder at any rank — `xenos` included — can see and kill their own sessions without an Archon. The cross-builder admin pair is untouched and still permission-gated; this is an addition, not a replacement.
47
+ - A session in continuous use still never expires on its own. That is now a *visible* property (the list shows how old each session is) with a one-click remedy, rather than an invisible one with no remedy.
48
+ - The three routes are pinned to `any-builder` in `EXPECTED_RANKS`, so both a later rank gate *and* a later widening to public fail the route-rank auditor loudly.
49
+ - The hall's Settings page grows an "Active sessions" panel with a per-session revoke and a "Sign out everywhere" button. Signing out everywhere from the browser ends that browser session too, so the page sends the builder back to sign-in.
50
+
51
+ ## When to revisit
52
+
53
+ Add the ceiling if any of these become true:
54
+
55
+ - Instances start issuing sessions to principals that cannot be asked to notice a leak (machine identities, embedded devices) — those want a cap, because nobody is watching the list.
56
+ - A compliance or hosting requirement names a maximum session age.
57
+ - The session list shows, in practice, that long-lived sessions accumulate faster than people prune them — i.e. the visibility half of this decision turns out not to work.
58
+
59
+ If it is added, it belongs on `created_at` (a true ceiling), not on the TTL, and it needs the same grace treatment migration 035 gave the 24h TTL, or it will sign out the session that deploys it.
@@ -293,5 +293,6 @@ This keeps the decision history honest and traceable.
293
293
  | 0203 | [**The doorway changelog leaves `src/module-api.js` — the CI auto-patch carrier appends to a markdown ledger instead** ([task 1003335](https://cloudbongos.com/builders#/task/1003335)). Amends [ADR 0161](<redacted>.md) §2. The `CORE_VERSION` release history lived as a comment block inside the doorway file, and ADR 0161's carrier appends two lines to it on EVERY merge-triggered release, pushed by `github-actions[bot]` straight to `main` — bypassing the `unit` gate it breaks. Release 1.19.407 took the file to 1501 lines, one over the `oversized_file_count` ratchet: `unit` went red on `main` and every open PR inherited it, self-inflicting and monotonic. Decision: the ledger moves to `docs/module-api-changelog.md` — markdown, which `fitness-ratchets.js` (`git ls-files '*.js'`) structurally never counts, so it can grow forever. All 1222 lines / 448 versions moved verbatim (only the `// ` marker stripped, kept in a fenced block so the hanging indent survives); the `const CORE_VERSION` literal and its line shape do NOT move, so every reader that greps it is untouched. `bump-version.js` transforms both files before writing either and refuses a half-applied bump; `publish.yml` stages both, pinned by `tests/bump_version.mjs`; the file is registered in `PUBLISH_ALLOWLIST` so the history keeps shipping inside `@bongos/core`; `src/module-api.js` keeps a pointer doc-comment where the block was. Rejected: trimming entries in place (buys one release), raising the baseline (out of policy per `config/fitness-baselines.json`'s own `_comment`), exempting the file from the ratchet.](<redacted>.md) | build pipeline / distribution |
294
294
  | 0204 | [**The apex is dark only; light is the builders' hall's** ([task 1003336](https://cloudbongos.com/builders#/task/1003336), goal 1000080). Supersedes in part [ADR 0196](<redacted>.md) (the light-mode half of the landing build) and the landing direction's 2026-08-27 pick of "light rebuilt to the dark standard". The owner, reviewing the planets: "only black or only white is a bit bland… leaning towards only dark mode for this page and keep the dark/light mode to be solely for the builder hall", confirmed as the WHOLE apex. Decision: `/`, `/projects`, `/privacy`, `/terms` and `/contact` pin `data-mode="dark"` on `<html>` — no pre-paint script, no `cb-color-mode`, no `?mode=`, no OS-preference read; the moon/sun glyph and its wiring leave both bars; the hall's own `hall-theme` switch is the platform's only light/dark control. The light tier of `world.css` / `cosmos.css` and the dark-twins rule STAY until the tier's own removal (the light-pair contrast tests keep measuring). The kit learns `"modes": ["dark"]` in a states file (validated; `render.js` takes it as the default). Rejected: a dark default with the toggle kept; deleting the light tier in the same diff; `--modes dark` on every kit call instead of the page's contract.](<redacted>.md) | apex / design system |
295
295
  | 0205 | [**A federated check-in may not speak for a person the hub never saw sign in: the membership carries the hub's own witness, and identity is never re-synced from client credentials** ([task 1003340](https://cloudbongos.com/builders#/task/1003340), goal 1000045 — closes security report 1000027, high, LIVE on the hub). `POST /sso/membership/check-in` authenticated the CALLER (`client_id` + `client_secret`) and took the SUBJECT (`github_id`, `github_login`, `avatar_url`) from the request body — client credentials say which PROJECT is calling, never who the person is. So any registered SSO client could (1) rewrite a real person's public `github_login` and `avatar_url`, which `upsertAccount`'s `ON CONFLICT` re-synced unconditionally and stored with no scheme check, and (2) mint the `<redacted>` row that [ADR 0171](<redacted>.md) D7's `membership_required` precheck reads — so the anti-impersonation gate on `POST /sso/activity/rollup` was satisfiable by the very call it was meant to gate, and forged credits/karma/rank reached a public profile. Decisions: (1) migration `<redacted>` adds `signin_verified_at`, written ONLY by `recordFederatedSignin` from the two moments the HUB itself authenticated a human for a client — `/sso/token` (a code minted under a live hub session, bound to the pair) and `/sso/device/poll` (a GitHub approval, audience bound from the typed origin) — so no request-body field reaches the statement and the column is evidence a caller structurally cannot write; (2) `upsertMembership` becomes `touchMembership`, an UPDATE gated on that column — a check-in can no longer CREATE a membership, answering `409 signin_required` and writing nothing, while signing in accepts a standing `pending` invite and leaves any other coarse kind to the instance to report; (3) a federated client gets insert-only `ensureAccount` (`DO NOTHING`), and `upsertAccount`'s identity re-sync belongs to hub-authenticated callers only — nothing is lost, because both of those already re-sync on every sign-in from GitHub's own answer; (4) the rollup gate reads `membershipBacksRollup` (non-pending AND witnessed), not row-existence; (5) `safeAvatarUrl` admits absolute https only, applied inside both account writers rather than at each render; (6) NO backfill — no predicate separates a legitimate check-in row from a forged one, and the first sign-in after deploy heals a real membership because the witness is stamped BEFORE the check-in and rollup that follow it, so a profile goes stale, never blank. No wire-format change: the request schemas are byte-identical and the ADR 0177 `membership_kind` window is untouched, so no federated instance upgrades. Proof `tests/sso_checkin_identity_spoof.mjs` (DB-free, in the default unit gate; 10 of 14 assertions fail against the pre-fix code, 2 controls prove the gate is not a wall). Rejected: carrying the hub assertion in the check-in body (strict validation would 400 every un-upgraded instance); ADR 0156-style source-guarding (answers "did the user set this", the wrong question when the writer is a third party); backfilling `signin_verified_at = joined_at`; a silent 200; retro-filtering `listMembershipsForAccount`.](<redacted>.md) | platform identity / security |
296
+ | 0206 | [**A leaked bearer's remedy is the account holder's own revoke, not an absolute session ceiling** ([task 1003341](https://cloudbongos.com/builders#/task/1003341), goal 1000045 — criterion `i-control-my-own-access-and-exposure`). `lookupSession` rewrites `expires_at = now() + 24h` on every authenticated request, so a CLI bearer in daily use never expires; if it leaks, the holder keeps it alive simply by using it. `POST /auth/logout` kills only the token presented on that same request — useless when the token you want dead is the one you no longer hold — and the two endpoints that can kill another session are gated on the admin `auth.session.revoke` atom, so the victim had to find an Archon, and a single-owner self-hosted instance may have none. Decision: ship the OWN-scoped remedy (`GET /me/sessions`, `POST /me/sessions/revoke`, `POST /me/sessions/revoke-all` on `requireBuilder` alone, every statement keyed on the caller's own id from the presented session, the token value never selected or returned, `revoke-all` deliberately including the requesting session) and deliberately NOT an absolute ceiling on the sliding refresh: a ceiling is unconditional (migration 035 already had to backfill a 24h grace so it would not kill the session running it), any cap loose enough not to be a daily nuisance leaves a leaked token live for weeks, and the visibility this task ships — `created_at` beside `last_used_at` in the list — is the compounding half of the fix. A demotion already sweeps sessions, and a deactivated builder is denied on the hot path, so the authority-drift case is closed by other means. Rejected: a `created_at + N` cap now; a shorter TTL; three more routes inside the already-largest `routes/auth.js` (a separate file is what lets the no-caller-named-owner rule be asserted over a WHOLE file); a `keep_current` flag; 404 on a session id that is not yours (it would confirm which ids exist); lowering the `auth.session.revoke` floor. Revisit for machine principals, a compliance maximum, or evidence that people do not prune what the list shows them.](<redacted>.md) | auth / account security |
296
297
 
297
298
  > ⚠️ **Numbering collisions are now machine-enforced, not narrated.** Twenty numbers are shared by two ADRs each — assigned in parallel sessions before anything checked. The files keep their filenames (renumbering would break every existing citation, and a number once assigned is never reused), and this table disambiguates each pair as `NNNN-a` / `NNNN-b`. The authoritative list is `LEGACY_DUPLICATE_ADRS` in [`scripts/gds/adr-namespace.js`](../../scripts/gds/adr-namespace.js), frozen by exact filename: a **new** collision — or a third file joining a legacy number, or a rename of either half — hard-fails the `unit` CI gate, as does an index row that points at no file or an ADR with no row. The list may only shrink. This paragraph used to narrate the collisions one by one and had fallen eight behind reality; see [ADR 0195](<redacted>.md) for why the check exists and why the pairs are grandfathered rather than renumbered.
@@ -8688,6 +8688,143 @@
8688
8688
  ]
8689
8689
  }
8690
8690
  },
8691
+ "/me/sessions": {
8692
+ "get": {
8693
+ "operationId": "get_me_sessions",
8694
+ "tags": [
8695
+ "me"
8696
+ ],
8697
+ "summary": "GET /me/sessions",
8698
+ "description": "GET /api/bongos/me/sessions — every live session on the CALLER's account. rank: any-builder (own identity only) — a builder owns their own sessions; the account is req.builder.id, never a caller-named one, so there is nothing here for a rank gate to protect.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
8699
+ "x-rank": "any-builder",
8700
+ "x-source": "src/bongos/routes/my-sessions.js",
8701
+ "responses": {
8702
+ "200": {
8703
+ "description": "Success.",
8704
+ "content": {
8705
+ "application/json": {
8706
+ "schema": {
8707
+ "$ref": "#/components/schemas/GetMeSessionsResponse"
8708
+ }
8709
+ }
8710
+ }
8711
+ },
8712
+ "400": {
8713
+ "$ref": "#/components/responses/BadRequest"
8714
+ },
8715
+ "401": {
8716
+ "$ref": "#/components/responses/Unauthorized"
8717
+ },
8718
+ "403": {
8719
+ "$ref": "#/components/responses/Forbidden"
8720
+ }
8721
+ },
8722
+ "security": [
8723
+ {
8724
+ "builderSession": []
8725
+ }
8726
+ ]
8727
+ }
8728
+ },
8729
+ "/me/sessions/revoke": {
8730
+ "post": {
8731
+ "operationId": "post_me_sessions_revoke",
8732
+ "tags": [
8733
+ "me"
8734
+ ],
8735
+ "summary": "POST /me/sessions/revoke",
8736
+ "description": "POST /api/bongos/me/sessions/revoke — kill ONE of the caller's own sessions. Body: { session_id }. Response: { ok, revoked: 0|1 }. Revoking the session that made the request is allowed and immediate: the response is written from a request that already authenticated, and the next call with that token fails. `revoked: 0` covers \"already gone\", \"never existed\" and \"not yours\" alike — the caller must not be able to probe for other people's session ids. rank: any-builder (own identity only) — the WHERE clause is the boundary.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
8737
+ "x-rank": "any-builder",
8738
+ "x-source": "src/bongos/routes/my-sessions.js",
8739
+ "requestBody": {
8740
+ "required": false,
8741
+ "content": {
8742
+ "application/json": {
8743
+ "schema": {
8744
+ "$ref": "#/components/schemas/PostMeSessionsRevokeRequest"
8745
+ }
8746
+ }
8747
+ },
8748
+ "x-validated": true
8749
+ },
8750
+ "responses": {
8751
+ "200": {
8752
+ "description": "Success.",
8753
+ "content": {
8754
+ "application/json": {
8755
+ "schema": {
8756
+ "$ref": "#/components/schemas/PostMeSessionsRevokeResponse"
8757
+ }
8758
+ }
8759
+ }
8760
+ },
8761
+ "400": {
8762
+ "$ref": "#/components/responses/ValidationFailed"
8763
+ },
8764
+ "401": {
8765
+ "$ref": "#/components/responses/Unauthorized"
8766
+ },
8767
+ "403": {
8768
+ "$ref": "#/components/responses/Forbidden"
8769
+ }
8770
+ },
8771
+ "security": [
8772
+ {
8773
+ "builderSession": []
8774
+ }
8775
+ ]
8776
+ }
8777
+ },
8778
+ "/me/sessions/revoke-all": {
8779
+ "post": {
8780
+ "operationId": "post_me_sessions_revoke_all",
8781
+ "tags": [
8782
+ "me"
8783
+ ],
8784
+ "summary": "POST /me/sessions/revoke-all",
8785
+ "description": "POST /api/bongos/me/sessions/revoke-all — sign out everywhere. Kills EVERY live session on the caller's account, the one making the request included. That total sweep is the point: this is the button somebody presses when they believe a token has leaked, and a remedy that spared one session would leave them guessing which. They sign in again afterwards. Response: { ok, revoked }. rank: any-builder (own identity only) — the sweep is keyed on req.builder.id.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
8786
+ "x-rank": "any-builder",
8787
+ "x-source": "src/bongos/routes/my-sessions.js",
8788
+ "requestBody": {
8789
+ "required": false,
8790
+ "content": {
8791
+ "application/json": {
8792
+ "schema": {
8793
+ "type": "object",
8794
+ "additionalProperties": true
8795
+ }
8796
+ }
8797
+ },
8798
+ "x-validated": true
8799
+ },
8800
+ "responses": {
8801
+ "200": {
8802
+ "description": "Success.",
8803
+ "content": {
8804
+ "application/json": {
8805
+ "schema": {
8806
+ "$ref": "#/components/schemas/PostMeSessionsRevokeAllResponse"
8807
+ }
8808
+ }
8809
+ }
8810
+ },
8811
+ "400": {
8812
+ "$ref": "#/components/responses/ValidationFailed"
8813
+ },
8814
+ "401": {
8815
+ "$ref": "#/components/responses/Unauthorized"
8816
+ },
8817
+ "403": {
8818
+ "$ref": "#/components/responses/Forbidden"
8819
+ }
8820
+ },
8821
+ "security": [
8822
+ {
8823
+ "builderSession": []
8824
+ }
8825
+ ]
8826
+ }
8827
+ },
8691
8828
  "/me/skill-prefs": {
8692
8829
  "get": {
8693
8830
  "operationId": "get_me_skill_prefs",
@@ -15701,6 +15838,15 @@
15701
15838
  "ships"
15702
15839
  ]
15703
15840
  },
15841
+ "GetMeSessionsResponse": {
15842
+ "type": "object",
15843
+ "properties": {
15844
+ "sessions": {}
15845
+ },
15846
+ "required": [
15847
+ "sessions"
15848
+ ]
15849
+ },
15704
15850
  "GetMeSkillPrefsResponse": {
15705
15851
  "type": "object",
15706
15852
  "properties": {
@@ -18997,6 +19143,41 @@
18997
19143
  "linked"
18998
19144
  ]
18999
19145
  },
19146
+ "PostMeSessionsRevokeAllResponse": {
19147
+ "type": "object",
19148
+ "properties": {
19149
+ "ok": {
19150
+ "type": "boolean"
19151
+ },
19152
+ "revoked": {}
19153
+ },
19154
+ "required": [
19155
+ "ok",
19156
+ "revoked"
19157
+ ]
19158
+ },
19159
+ "PostMeSessionsRevokeRequest": {
19160
+ "type": "object",
19161
+ "properties": {
19162
+ "session_id": {
19163
+ "$ref": "#/components/schemas/StringifiableId"
19164
+ }
19165
+ },
19166
+ "additionalProperties": false
19167
+ },
19168
+ "PostMeSessionsRevokeResponse": {
19169
+ "type": "object",
19170
+ "properties": {
19171
+ "ok": {
19172
+ "type": "boolean"
19173
+ },
19174
+ "revoked": {}
19175
+ },
19176
+ "required": [
19177
+ "ok",
19178
+ "revoked"
19179
+ ]
19180
+ },
19000
19181
  "PostMemoryBuildersBuilderIdBfgWriteRequest": {
19001
19182
  "type": "object",
19002
19183
  "properties": {
@@ -20666,9 +20847,9 @@
20666
20847
  "description": "A required dependency/feature is not configured or is temporarily down."
20667
20848
  }
20668
20849
  },
20669
- "x-endpoint-count": 327,
20670
- "x-schema-count": 361,
20850
+ "x-endpoint-count": 330,
20851
+ "x-schema-count": 365,
20671
20852
  "x-undocumented-bodies": 9,
20672
- "x-response-schemas": 244,
20853
+ "x-response-schemas": 247,
20673
20854
  "x-generated-by": "scripts/gds/gen-api-docs.js"
20674
20855
  }