@bongos/core 1.19.791 → 1.19.793

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.791",
6
- "core_contract": "1.19.791",
7
- "source_commit": "2b7568a881869e3462f9acdb7822b108767a5dd7",
5
+ "core_version": "1.19.793",
6
+ "core_contract": "1.19.793",
7
+ "source_commit": "877a1e0befbf245c4ac50b28d0bc72c785dc9d27",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-17T00:05:42.468Z",
9
+ "built_at": "2026-09-17T00:41:15.216Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
- "docs_redacted": 485,
12
+ "docs_redacted": 486,
13
13
  "agent_docs_stubbed": 25,
14
- "functional_verbatim": 2177,
14
+ "functional_verbatim": 2179,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2687,
20
- "tree_sha256": "18d8b5f0f1aa03a232e5f87711bf4b134cacbb7e4d35e8ee36755ae7f538fec4",
19
+ "file_count": 2690,
20
+ "tree_sha256": "17b680447915370ad9005373e97e6a90d25f03ebdf58f52e2902501c26fe403a",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -77,7 +77,7 @@
77
77
  {
78
78
  "path": ".claude/skills/builder-reauth/SKILL.md",
79
79
  "mode": "0000644",
80
- "sha256": "3b95c9f28e9b455758d6d6696c0442b893747227adf6e4036409f82fae97317f"
80
+ "sha256": "f54f3355d1f034fe99735a73ce62936d7d1582e751754bbe12023a4bf8a2d17c"
81
81
  },
82
82
  {
83
83
  "path": ".claude/skills/builder-redteam/SKILL.md",
@@ -337,12 +337,12 @@
337
337
  {
338
338
  "path": "README.md",
339
339
  "mode": "0000644",
340
- "sha256": "f0358ea7bfffc17c7f1e4ead3bb223117787ba6f88c1ae357150e4f6a59e70a8"
340
+ "sha256": "39267c71403ae6fa77551c88773e6ff744032b6ae2d9bfa002573c0c520a148a"
341
341
  },
342
342
  {
343
343
  "path": "bin/bongos.js",
344
344
  "mode": "0000644",
345
- "sha256": "d585fc874c0a45f5d02d5753025d4761ca9b817ae9e5c556891b5c5c444cffde"
345
+ "sha256": "36d11612cace3d69bd2e98c8f7b5b7d737d8bba5fd8fe1a0554a85e3cb1973ca"
346
346
  },
347
347
  {
348
348
  "path": "clients/bongos-client/README.md",
@@ -1522,7 +1522,7 @@
1522
1522
  {
1523
1523
  "path": "docs/adr/0206-own-scoped-session-revocation-no-absolute-ceiling.md",
1524
1524
  "mode": "0000644",
1525
- "sha256": "f7b8f73c7afe76ff48ef40c821b3d474d45737101e5baf07f2c535cd710146e0"
1525
+ "sha256": "f8654e155205d9566aab8a103b6fbdb6100e23ddba3c74bb417955cef997e0f0"
1526
1526
  },
1527
1527
  {
1528
1528
  "path": "docs/adr/0207-connections-implementation-departures-and-honest-limits.md",
@@ -1954,10 +1954,15 @@
1954
1954
  "mode": "0000644",
1955
1955
  "sha256": "fb72a972f472cfea4c214dc73c7c5e811670d4eb2bbbc8d397495c8756daa625"
1956
1956
  },
1957
+ {
1958
+ "path": "docs/adr/0294-cli-sessions-do-not-expire.md",
1959
+ "mode": "0000644",
1960
+ "sha256": "32d1b4184b6ba494bacb915990f0ba2770535e25c5646c86a2d91d9cc05c93ed"
1961
+ },
1957
1962
  {
1958
1963
  "path": "docs/adr/README.md",
1959
1964
  "mode": "0000644",
1960
- "sha256": "4b61b76ac117cd1f3086db73d9badb4046285ecf7cd1d1dd6e5c4d389d3c68af"
1965
+ "sha256": "5659dd04d00f3ac4b3087ec41d24d7eea6f9d3ad349cd68b4405b612c8238feb"
1961
1966
  },
1962
1967
  {
1963
1968
  "path": "docs/api-reference.md",
@@ -1967,7 +1972,7 @@
1967
1972
  {
1968
1973
  "path": "docs/api/openapi.json",
1969
1974
  "mode": "0000644",
1970
- "sha256": "00f6e4757e5337e7f7cf3ae1d0afeccfcabe9ef1896304004b855931f8b9c423"
1975
+ "sha256": "1442772c691bb7447cb13e4e63b062dd41913d8bb06555cee814069434556ae5"
1971
1976
  },
1972
1977
  {
1973
1978
  "path": "docs/architecture.md",
@@ -1992,12 +1997,12 @@
1992
1997
  {
1993
1998
  "path": "docs/copy-inventory.md",
1994
1999
  "mode": "0000644",
1995
- "sha256": "52659f95e4b114864d9a4ea8db3ae619aedcfabef05554863b5056bb43dd8ce3"
2000
+ "sha256": "ec93785011f13f27444bd8ccfe9fe24da8cd125cd1b440bb974b78ebe1b28533"
1996
2001
  },
1997
2002
  {
1998
2003
  "path": "docs/copy-registry.json",
1999
2004
  "mode": "0000644",
2000
- "sha256": "ae2ebe3f9e261c1c5cef1e72949bfb764c6f8a9519342934dd21f03ccc96fd73"
2005
+ "sha256": "76dfd0c0caaedc010f832b44a3acef437f7352e86090cc46f7ce1edb15b6f192"
2001
2006
  },
2002
2007
  {
2003
2008
  "path": "docs/design/apex-pass-2-direction.md",
@@ -2522,7 +2527,7 @@
2522
2527
  {
2523
2528
  "path": "docs/module-api-changelog.md",
2524
2529
  "mode": "0000644",
2525
- "sha256": "b54a97983163fa6cbab194bf7cb8ea9acba3fa2fd56624d3e75542d380d4263b"
2530
+ "sha256": "5a46ed6753d0b49d3a65f6f58972a5159a115ac6fa0022d628942cb48b80be95"
2526
2531
  },
2527
2532
  {
2528
2533
  "path": "docs/modules-contract.md",
@@ -3604,6 +3609,11 @@
3604
3609
  "mode": "0000644",
3605
3610
  "sha256": "e52821124b99bd844c8323f2de0221db3fafd92c0c6e2227838cadf7d987676a"
3606
3611
  },
3612
+ {
3613
+ "path": "migrations/core_243_cli_sessions_never_expire.sql",
3614
+ "mode": "0000644",
3615
+ "sha256": "8e3e2db2ab3b7775bddf12f3a7ec3ed74477334b6388487ecd919cd61a62b0ba"
3616
+ },
3607
3617
  {
3608
3618
  "path": "modules/agents/CLAUDE.md",
3609
3619
  "mode": "0000644",
@@ -5227,7 +5237,7 @@
5227
5237
  {
5228
5238
  "path": "modules/hall-ui/public/settings.js",
5229
5239
  "mode": "0000644",
5230
- "sha256": "cd505485ab79c11ea447ec519df16f3d6715f4e2d6f14f25f7e449718551aa07"
5240
+ "sha256": "fd553ac7c0ca9c74526647388e42afc18fc7c1b9f2cfc70c493f78343ba7c393"
5231
5241
  },
5232
5242
  {
5233
5243
  "path": "modules/hall-ui/public/settings.states.json",
@@ -7682,12 +7692,12 @@
7682
7692
  {
7683
7693
  "path": "package-lock.json",
7684
7694
  "mode": "0000644",
7685
- "sha256": "6b7e2486f80c0e131af94902f68a989be32f1550dbd4cccbdb2fa6c487d4a6f9"
7695
+ "sha256": "88ee2ff930be2d84d19733724ddfeee1842cba1b890422713c9012466fa0739a"
7686
7696
  },
7687
7697
  {
7688
7698
  "path": "package.json",
7689
7699
  "mode": "0000644",
7690
- "sha256": "bde90c1e255f5fc6d43b51033a3c2746a58ffc810369f404d2a5209250bbec45"
7700
+ "sha256": "f7f66eb317b78a0b652addc0c31a67500a80ed2109807a89898128d6a15a481b"
7691
7701
  },
7692
7702
  {
7693
7703
  "path": "public-docs/index.html",
@@ -7897,7 +7907,7 @@
7897
7907
  {
7898
7908
  "path": "scripts/gds/box-auth-check.js",
7899
7909
  "mode": "0000644",
7900
- "sha256": "f9625da97ac72eb74857aa210f8263cd439312f71ca4084e09beed8c5faeb70d"
7910
+ "sha256": "c24c66d306b2904692bddfad0ea3cb023f8532beb3d674a2a81e6fed0eeb9bd5"
7901
7911
  },
7902
7912
  {
7903
7913
  "path": "scripts/gds/box-connect-lib.js",
@@ -7992,7 +8002,7 @@
7992
8002
  {
7993
8003
  "path": "scripts/gds/cli-lib.js",
7994
8004
  "mode": "0000644",
7995
- "sha256": "b15d7eecec2d11cc7676308256159121c56378fbc652a08af08762fd8e928df1"
8005
+ "sha256": "88b8d2359743238922d73797807dd2f545b1a68c731d8a4766d7210196ad48fc"
7996
8006
  },
7997
8007
  {
7998
8008
  "path": "scripts/gds/client-baseurl-guard.js",
@@ -8522,7 +8532,7 @@
8522
8532
  {
8523
8533
  "path": "scripts/gds/paste-token.js",
8524
8534
  "mode": "0000644",
8525
- "sha256": "7f112d061f459781559778639c0c11b9fbc2a5acc06d87464a40def06c3e4cf7"
8535
+ "sha256": "423b356f7ff60913af72ce2042fb8dd2fcefa26375864dbb428cb4e3b527bb82"
8526
8536
  },
8527
8537
  {
8528
8538
  "path": "scripts/gds/portability-smoke-art.sh",
@@ -8627,7 +8637,7 @@
8627
8637
  {
8628
8638
  "path": "scripts/gds/reauth.js",
8629
8639
  "mode": "0000644",
8630
- "sha256": "52b550c2d24a59c9b83a53c84a16674bcfa23715d92288f519ab82584c362ea8"
8640
+ "sha256": "ce20f84be283f2a97df7d410b56b40d1e0cd14be8349acc418aa645d702f4f45"
8631
8641
  },
8632
8642
  {
8633
8643
  "path": "scripts/gds/recall.js",
@@ -8702,7 +8712,7 @@
8702
8712
  {
8703
8713
  "path": "scripts/gds/run-unit-tests.js",
8704
8714
  "mode": "0000644",
8705
- "sha256": "4ba19aaa0106bafafd6369b761460e0a1fccac3045f191c3f7835bc250623f40"
8715
+ "sha256": "c7ea33433208b0835808028fb81853509d97331b919a70314e1a02718e8ca0c0"
8706
8716
  },
8707
8717
  {
8708
8718
  "path": "scripts/gds/sandbox-stage.js",
@@ -9287,7 +9297,7 @@
9287
9297
  {
9288
9298
  "path": "src/bongos/auth-github.js",
9289
9299
  "mode": "0000644",
9290
- "sha256": "3edda18b71a5b0745b41d38b1d7b4597d8b9c1d5ec71ca65b37b3e93e7440cd1"
9300
+ "sha256": "e2afb2d5f5459ed307be4e6e2d57cbd283b97794f8d1a99f4df5673000270681"
9291
9301
  },
9292
9302
  {
9293
9303
  "path": "src/bongos/auth-idp.js",
@@ -9312,12 +9322,12 @@
9312
9322
  {
9313
9323
  "path": "src/bongos/db-kernel.js",
9314
9324
  "mode": "0000644",
9315
- "sha256": "f31dd847ed641bf49013c345b1f08f9e2794a875dad777de10daeda2a4ae1cf3"
9325
+ "sha256": "e9a566e63b95225e3ee15331424af831875f75b5ea977c73f077bb50f46c4c6b"
9316
9326
  },
9317
9327
  {
9318
9328
  "path": "src/bongos/db.js",
9319
9329
  "mode": "0000644",
9320
- "sha256": "b205304524629b0e867200e43a083bdf23b9121a6dbc2463c1e80e0e3fca8b8f"
9330
+ "sha256": "7ebe731cacb9d7cb72af9e8310ccefc9d902b9e613c45839bc1555b5bb71cd5a"
9321
9331
  },
9322
9332
  {
9323
9333
  "path": "src/bongos/github-repos.js",
@@ -9457,7 +9467,7 @@
9457
9467
  {
9458
9468
  "path": "src/bongos/routes/auth.js",
9459
9469
  "mode": "0000644",
9460
- "sha256": "67a9c54d3d86c6120c1db68684dc8ef3db090cd97fed6f161a17031506a5d58e"
9470
+ "sha256": "a45868b46ab96fdedab3e7359259527e2d75e65b86a916865e5fff358f88a3b3"
9461
9471
  },
9462
9472
  {
9463
9473
  "path": "src/bongos/routes/backup.js",
@@ -9567,7 +9577,7 @@
9567
9577
  {
9568
9578
  "path": "src/module-api.js",
9569
9579
  "mode": "0000644",
9570
- "sha256": "e3469d6e43b84d894563b989c1b97524cbf25bb5c646a1bf0e412b51e2542af8"
9580
+ "sha256": "7db202b3ac70a0404e7cc0067828bb9d41eed6bfddf760e325773f629cd09dfc"
9571
9581
  },
9572
9582
  {
9573
9583
  "path": "src/module-loader/catalog.js",
@@ -9899,10 +9909,15 @@
9899
9909
  "mode": "0000644",
9900
9910
  "sha256": "acbce017f7111b67c556db645de7b88fb5f39829755fa6c9378f300c8fc62a15"
9901
9911
  },
9912
+ {
9913
+ "path": "tests/auth_session_expiry_db.mjs",
9914
+ "mode": "0000644",
9915
+ "sha256": "880cbfa0304ab037db980f22471a2b46bcdbad0e7dca57af359402dbb068d6a4"
9916
+ },
9902
9917
  {
9903
9918
  "path": "tests/auth_session_ttl.mjs",
9904
9919
  "mode": "0000644",
9905
- "sha256": "92b7033ab16d9d4472fcca4695d9df47938a45bdc62feda87b8771f5a2d59ff2"
9920
+ "sha256": "e4c3309248873ac816ecf9111cc08e4c9e3e6c318a2e925434f1cc490c71b734"
9906
9921
  },
9907
9922
  {
9908
9923
  "path": "tests/auth_web_recovery.mjs",
@@ -9997,7 +10012,7 @@
9997
10012
  {
9998
10013
  "path": "tests/bongos_cli.mjs",
9999
10014
  "mode": "0000644",
10000
- "sha256": "20d7d86da8e03ede2004bdc3dda589670977fca0240d56cd3677fc08fbb2de03"
10015
+ "sha256": "ef01af16d62c55325a3b12cc7968a9d797994d2f46364149668bf4e2839cb0e0"
10001
10016
  },
10002
10017
  {
10003
10018
  "path": "tests/bongos_dispatch.mjs",
@@ -10277,7 +10292,7 @@
10277
10292
  {
10278
10293
  "path": "tests/cli_token_reissue.mjs",
10279
10294
  "mode": "0000644",
10280
- "sha256": "66717847c1f67d4bedd9a2d9d5ed989e238039118e439eb2a3c67d43079172a4"
10295
+ "sha256": "a82544c109464d884e44731cbdd73e9e141d31d8e18a9241222d6a26223e19c2"
10281
10296
  },
10282
10297
  {
10283
10298
  "path": "tests/client_baseurl_guard.mjs",
@@ -4,11 +4,13 @@ description: >-
4
4
  Refresh an expired Bongos CLI session the UI-first way — Settings, copy the re-issue one-liner, this runs it. Triggers: "/builder-reauth", "re-auth", "refresh my session", "my Bongos session expired", "re-issue my CLI token", or any /builder-* failing with "Bongos CLI session is no longer valid".
5
5
  ---
6
6
 
7
- You are refreshing the current builder's Bongos CLI session. CLI bearer tokens carry a 24h idle TTL (they refresh on every use), so an actively-used session stays alive — but an idle one expires, and a revoked one dies immediately. This skill is the **fast path back in**: browser clicks plus a single copy/paste, instead of the slower terminal Device Flow.
7
+ You are refreshing the current builder's Bongos CLI session. CLI bearer tokens **do not expire** — since [ADR 0294](../../../docs/adr/0294-cli-sessions-do-not-expire.md) a session lasts until it is revoked. So a rejected token means something killed it: the builder (or an Archon) revoked it, a demotion swept it, or an auth-changing deploy invalidated it. This skill is the **fast path back in**: browser clicks plus a single copy/paste, instead of the slower terminal Device Flow.
8
8
 
9
- ## Why expiry exists (so you can explain it if asked)
9
+ ## Why there is no expiry (so you can explain it if asked)
10
10
 
11
- The idle TTL is auth-token hygiene (GDS-V3 criterion **C9**): the CLI token lives in a plaintext file (`~/.config/otb/gds-session.json`, mode 0600), so bounding the lifetime of a leaked or abandoned token matters. It is **not** what enforces rank changes — demotion calls `revokeAllSessionsForBuilder` synchronously and rank is read fresh from the DB on every request ([ADR 0016](../../../docs/adr/0016-trust-boundary-server-enforced-permissions.md)). So expiry stays; the friction is what this skill removes.
11
+ There used to be a 24h idle TTL, for auth-token hygiene (GDS-V3 criterion **C9**): the CLI token sits in a plaintext file (`~/.config/cloudbongos/bongos-session.json`, mode 0600), so bounding a leaked or abandoned token seemed worth the friction. It wasn't. Because the TTL refreshed on every use, a thief's own traffic kept a stolen token alive indefinitely — the only person a timer ever signed out was the legitimate holder, who had stepped away. [ADR 0294](../../../docs/adr/0294-cli-sessions-do-not-expire.md) removed it.
12
+
13
+ What actually bounds a leaked token is **revocation**, and it is faster than any clock: the builder kills it themselves from Settings → Active sessions or "Sign out everywhere" ([ADR 0206](../../../docs/adr/0206-own-scoped-session-revocation-no-absolute-ceiling.md)). Rank changes were never the TTL's job either — demotion calls `revokeAllSessionsForBuilder` synchronously and rank is read fresh from the DB on every request ([ADR 0016](../../../docs/adr/0016-trust-boundary-server-enforced-permissions.md)).
12
14
 
13
15
  ## The flow (guided paste)
14
16
 
@@ -23,10 +25,10 @@ The idle TTL is auth-token hygiene (GDS-V3 criterion **C9**): the CLI token live
23
25
  2. **Relay the click-path verbatim.** The helper prints the exact steps; pass them to the user clearly (don't paraphrase the URL):
24
26
  - Open `https://example.com/builders/settings` (the helper tries to open it automatically; if not, give them the link).
25
27
  - They are already signed in there via the browser cookie. Under the **"CLI Access"** section, click **"Re-issue CLI token"**.
26
- - Click **"Copy"** — it copies a one-line command of the form `node scripts/gds/paste-token.js <token>`.
28
+ - Click **"Copy"** — it copies a one-line command of the form `bongos paste-token <token>`.
27
29
  - Ask them to **paste that one line back into the chat**.
28
30
 
29
- 3. **Run the pasted command.** When the user pastes `node scripts/gds/paste-token.js <token>`, run it exactly as given. `paste-token.js` verifies the token against `/api/gds/me` **before** touching the session file (a bad paste leaves the existing session intact) and preserves any `gemini_api_key`. On success it prints the builder login, rank, and confirms the session file was written.
31
+ 3. **Run the pasted command.** When the user pastes `bongos paste-token <token>`, run it exactly as given. `paste-token.js` verifies the token against `/api/gds/me` **before** touching the session file (a bad paste leaves the existing session intact) and preserves any `gemini_api_key`. On success it prints the builder login, rank, and confirms the session file was written.
30
32
 
31
33
  4. **Confirm.** Relay the authenticated identity + rank back to the user, and tell them they can now re-run whatever `/builder-*` command they were trying.
32
34
 
@@ -39,13 +41,13 @@ The idle TTL is auth-token hygiene (GDS-V3 criterion **C9**): the CLI token live
39
41
 
40
42
  ## Constraints
41
43
 
42
- - **The token briefly transits the chat — that's the known cost of guided-paste.** The user pastes `node scripts/gds/paste-token.js <token>` into the conversation, so the bearer appears in the chat (and any log/context that captures it). That's the accepted trade-off Lars chose; the deferred zero-paste loopback flow removes it. So: treat the session log as sensitive, **never echo the token beyond running that one pasted command**, and never write it to any file other than `~/.config/otb/gds-session.json` (`paste-token.js` is the only thing that should write it — let it do the write). The token rotates whenever the builder clicks Re-issue again, so re-running this flow invalidates an exposed paste's usefulness for an attacker who lacks the browser cookie.
44
+ - **The token briefly transits the chat — that's the known cost of guided-paste.** The user pastes `bongos paste-token <token>` into the conversation, so the bearer appears in the chat (and any log/context that captures it). That's the accepted trade-off Lars chose; the deferred zero-paste loopback flow removes it. So: treat the session log as sensitive, **never echo the token beyond running that one pasted command**, and never write it to any file other than `~/.config/otb/gds-session.json` (`paste-token.js` is the only thing that should write it — let it do the write). The token rotates whenever the builder clicks Re-issue again, so re-running this flow invalidates an exposed paste's usefulness for an attacker who lacks the browser cookie.
43
45
  - **Don't re-auth a valid session.** If `reauth.js` exits 0, stop.
44
46
  - **`GDS_API_BASE`** overrides the endpoint (default `https://example.com`); for local dev `GDS_API_BASE=http://localhost:3000`.
45
47
 
46
48
  ## Files this skill touches
47
49
 
48
50
  - Runs: `node scripts/gds/reauth.js` (detect + open browser + print click-path)
49
- - Runs: `node scripts/gds/paste-token.js <token>` (verify + write the new session)
51
+ - Runs: `bongos paste-token <token>` (verify + write the new session)
50
52
  - Reads/Writes (via paste-token.js): `~/.config/otb/gds-session.json`
51
53
  - Calls: `GET /api/gds/me`; the Settings page (CLI Access) calls `POST /api/gds/auth/cli-token/issue`
package/README.md CHANGED
@@ -73,7 +73,7 @@ We keep secrets (`.env` files, API keys, private keys) out of the repo with two
73
73
 
74
74
  ## If your CLI starts 401'ing
75
75
 
76
- GDS CLI calls (`node scripts/gds/start.js`, `claim.js`, `ship.js`, etc.) authenticate against your instance's GDS host using a session token stored under `~/.config/<configDir>/gds-session.json` (the neutral core uses `cloudbongos`; an instance may pin its own). Sessions carry a rolling TTL — every authenticated request resets it, so an active session never silently expires — but after a long idle, a token revocation, or an auth-changing deploy, the local token can stop working.
76
+ GDS CLI calls (`node scripts/gds/start.js`, `claim.js`, `ship.js`, etc.) authenticate against your instance's GDS host using a session token stored under `~/.config/<configDir>/` (the neutral core uses `cloudbongos`; an instance may pin its own). CLI sessions do not expire — they last until revoked ([ADR 0294](docs/adr/0294-cli-sessions-do-not-expire.md)) — so a 401 means the token was revoked (by you from Settings → Active sessions, or by an Archon), swept by a rank demotion, or invalidated by an auth-changing deploy.
77
77
 
78
78
  `apiCall` in `scripts/gds/cli-lib.js` detects HTTP 401 and exits with a clear recovery message rather than letting the failure cascade. When you see it:
79
79
 
package/bin/bongos.js CHANGED
@@ -47,6 +47,10 @@ const BUNDLED_RUNNERS = {
47
47
  'login.js': () => require('../scripts/gds/login.js'),
48
48
  'setup.js': () => require('../scripts/gds/setup.js'),
49
49
  'reauth.js': () => require('../scripts/gds/reauth.js'),
50
+ // Bundled for the same reason as reauth: they are two halves of one on-ramp
51
+ // (mint the token, then store it), and a builder recovering a dead session on a
52
+ // bare laptop has the standalone binary and nothing else (task 1003961).
53
+ 'paste-token.js': () => require('../scripts/gds/paste-token.js'),
50
54
  'api.js': () => require('../scripts/gds/api.js'),
51
55
  'shell.js': () => require('../scripts/gds/shell.js'),
52
56
  'code.js': () => require('../scripts/gds/code.js'),
@@ -84,6 +88,13 @@ const VERBS = {
84
88
  login: { script: 'login.js', group: 'account', summary: 'Sign in to an instance (bongos login <url> — browser click, no paste)' },
85
89
  setup: { script: 'setup.js', group: 'account', summary: 'Full builder setup on this instance (art key, disciplines, consent)' },
86
90
  reauth: { script: 'reauth.js', group: 'account', summary: 'Refresh your session' },
91
+ // The second half of `reauth`: the hall mints a token, this stores it. A VERB
92
+ // rather than a path because the string is emitted by the SERVER and pasted on
93
+ // an INSTANCE, where `scripts/gds/` does not exist — the core is a dependency
94
+ // there, so the old `node scripts/gds/paste-token.js <token>` named a file the
95
+ // builder did not have (task 1003961). A verb also makes it allow-listable by
96
+ // name, the way claim/ship/release already are (ADR 0104).
97
+ 'paste-token': { script: 'paste-token.js', group: 'account', summary: 'Finish re-auth: store the token copied from Settings (bongos paste-token <token>)' },
87
98
  doctor: { script: 'doctor.js', group: 'account', summary: 'Check your local toolchain + session' },
88
99
  box: { script: 'api.js', prefixArgs: ['POST', '/api/gds/box/ensure'], group: 'account', summary: 'Turn on / wake your cloud dev box' },
89
100
  shell: { script: 'shell.js', group: 'account', summary: 'Open a terminal on your dev box (--local for a local clone)' },
@@ -239,7 +250,7 @@ function runBundled(verb, def, forwarded) {
239
250
  ` bongos code open VS Code on your cloud dev box (has everything)\n` +
240
251
  ` bongos shell open a terminal on your cloud dev box\n` +
241
252
  ` bongos init scaffold a new instance here\n\n` +
242
- `The standalone binary covers the on-ramp: login, box, shell, code, init, reauth.\n`
253
+ `The standalone binary covers the on-ramp: login, box, shell, code, init, reauth, paste-token.\n`
243
254
  );
244
255
  return 2;
245
256
  }
@@ -6,6 +6,8 @@
6
6
  - **Task:** task 1003341 (goal 1000045 — public builder profiles + account security, criterion `i-control-my-own-access-and-exposure`)
7
7
  - **Interacts with:** [ADR 0016](0016-trust-boundary-server-enforced-permissions.md) (the DB enforces; a session is checked fresh per request, never cached), [ADR 0053](0053-scoped-dev-box-session.md) / [ADR 0151](0151-governance-permissions-as-atom-ranks-as-roles.md) §3 (the session-scope registry — scope is capability on the token, orthogonal to rank), migration `035_auth_token_hygiene.sql` (the 24h TTL + refresh-on-use it introduced, and the deliberate backfill grace that came with it).
8
8
 
9
+ > **Amended by [ADR 0294](0294-cli-sessions-do-not-expire.md) (2026-09-16).** This ADR declined to add an absolute ceiling *on top of* the 24h sliding TTL, and that judgement stands unchanged. What changed afterwards is the sliding TTL itself: ADR 0294 removed it from CLI sessions entirely, on the same reasoning this file states below — refresh-on-use meant the timer only ever fired on the legitimate holder, never on a leak. So where the text below says an idle session expires after 24h, read: CLI sessions do not expire at all, and the own-scoped revoke shipped here is now the only bound on a leaked bearer rather than the faster of two.
10
+
9
11
  ## Context
10
12
 
11
13
  `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.
@@ -0,0 +1,56 @@
1
+ # 0294 — CLI sessions do not expire; revocation is the whole remedy
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-09-16
5
+ - **Deciders:** The owner, who asked for the CLI timeout removed entirely after a three-week gap signed him out, and accepted the stated security consequence.
6
+ - **Task:** task 1003956 (goal 1000110 — Working area 2: account creation, management & community)
7
+ - **Interacts with:** [ADR 0206](0206-own-scoped-session-revocation-no-absolute-ceiling.md) (the own-scoped revoke this leans on, and the ceiling it declined to build), [ADR 0016](0016-trust-boundary-server-enforced-permissions.md) (a session is resolved fresh per request, never cached), migrations `035_auth_token_hygiene.sql` (introduced the TTL) and `core_243_cli_sessions_never_expire.sql` (clears it).
8
+
9
+ ## Context
10
+
11
+ A `bongos` sign-in died after 24 hours of not being used. `db-kernel.js createSession` defaulted a null `expiresAt` to `now() + SESSION_TTL_MS`, and `lookupSession` rewrote `expires_at = now() + 24h` on every authenticated request. An actively-used session therefore never lapsed; an idle one did, and the builder came back to `Your Bongos CLI session is no longer valid` blocking every `/builder-*` command until they re-authed through the browser.
12
+
13
+ This is the third time the number has been argued. V3.R38 (task #259, migration 035) set it to 1 hour. V3.R90 (task 1000400) widened it to 24 hours with the note that 1h "kicked builders out over lunch". Each round shortened the nuisance and left the mechanism alone. The owner hit it again on 2026-09-16 after a three-week gap and asked for the timeout gone rather than widened a fourth time.
14
+
15
+ The argument for keeping it was always auth-token hygiene (GDS-V3 criterion C9): the token sits in a plaintext file at mode 0600, so a leaked or abandoned copy should not live forever. **That argument does not survive contact with refresh-on-use.** A leaked token is, by definition, a token somebody is using — and every use rolled the expiry forward another 24 hours. The timer could only ever fire on a session that had gone quiet, which is the legitimate holder stepping away, not the thief. The control was aimed at attackers and landed exclusively on users. ADR 0206 had already written down half of this ("a CLI bearer sitting in the session file is, in practice, immortal"); what it did not do is draw the conclusion that a control which cannot reach its target is not worth its cost.
16
+
17
+ What genuinely bounds a leaked bearer arrived with ADR 0206 itself: `GET /me/sessions`, `POST /me/sessions/revoke`, `POST /me/sessions/revoke-all`, and the hall's "Sign out everywhere" — no rank required, no Archon to find. A person who knows their token leaked gets it dead in one request. That is strictly faster than any TTL, which at best kills it the following day.
18
+
19
+ ## Decision
20
+
21
+ **CLI sessions carry no expiry. They last until revoked.**
22
+
23
+ 1. `createSession` records `expires_at = NULL` when the caller names no expiry. `lookupSession` already treated NULL as never-expires, so this is a one-line default change rather than new machinery. Every call site that passes `null` is `source: 'cli'` — the CLI device flow, the federated CLI sign-in, the hall's token re-issue, and the dev-box pair.
24
+ 2. `lookupSession` still bumps `last_used_at` on every request, but rolls `expires_at` forward **only for a row that already has one**:
25
+
26
+ ```sql
27
+ expires_at = CASE WHEN expires_at IS NULL THEN NULL
28
+ ELSE now() + ($2::int * INTERVAL '1 millisecond') END
29
+ ```
30
+
31
+ Without the `CASE`, the first request after sign-in would mint the very timeout this removes.
32
+ 3. Migration `core_243` clears `expires_at` on live, unrevoked `source='cli'` rows, so the change reaches sessions that already exist instead of charging every builder one last forced re-auth on deploy day. This is migration 035's grace inverted, for the same reason 035 gave: do not sign out the people holding sessions when the policy moves.
33
+
34
+ **Browser sessions are untouched.** They pass an explicit 30-day `expiresAt`, so the new default never reaches them, and `core_243`'s `source = 'cli'` filter leaves their rows alone. `SESSION_TTL_MS` survives as the roll-forward window for rows that have an expiry — it is no longer a default.
35
+
36
+ ### Rejected
37
+
38
+ - **Widen the TTL again (7 days, 30 days).** The fourth iteration of a mechanism whose first three failed for the same structural reason. Any window loose enough to stop annoying builders is far too loose to bound a leak, and refresh-on-use means it still never fires on the attacker.
39
+ - **Keep the TTL and make re-auth cheaper.** Already tried: ADR-era tasks 1000401 and 1000504 built the one-click hall re-issue and `/builder-reauth` precisely to soften this. The friction got smaller and the interruption stayed. Removing the interruption is what was asked for.
40
+ - **An absolute ceiling on `created_at`.** ADR 0206 rejected it and its reasoning holds unchanged: a cap signs out every dev box and long-running session on a timer, for a threat the revoke answers faster. Nothing here revives it.
41
+ - **Drop `expires_at` from the schema.** The column is load-bearing for web sessions and still honoured by `lookupSession`. Removing it would force browser expiry into some other mechanism for no gain.
42
+ - **A blanket `UPDATE builder_sessions SET expires_at = NULL`.** Would hand browser cookies an immortality nobody decided on — and irreversibly, since `lookupSession` only rolls an expiry it can already see, so a web row cleared by mistake stays cleared. The `source = 'cli'` filter is the safety property of that migration.
43
+
44
+ ## Consequences
45
+
46
+ - A builder signs in once and stays signed in. Coming back after a weekend, a holiday, or three weeks costs nothing.
47
+ - **A leaked CLI token is valid until somebody revokes it.** This is the real cost and it was accepted explicitly. In practice it is a smaller change than it sounds: under refresh-on-use a leaked token in active use was already immortal, so what actually changes is the *abandoned* copy — one on a decommissioned laptop or an old dev box — which previously went stale on its own and now does not.
48
+ - The remedy is self-serve and immediate (ADR 0206). The compounding half of that decision now carries more weight: the session list shows `created_at` beside `last_used_at`, so a session nobody recognises is something a person can see and kill. That visibility was a nice-to-have beside a timer; it is now the primary control.
49
+ - Nothing about rank or deactivation changes. `setBuilderRank` still calls `revokeAllSessionsForBuilder` on every demotion, and `requireBuilder` still refuses a deactivated builder on the hot path regardless of a live token. Neither consults `expires_at` — the TTL was never load-bearing for C7.
50
+ - Criterion C9's idle-session hygiene claim is no longer met by a timer, and should be read as met by revocation plus visibility instead.
51
+
52
+ ## When to revisit
53
+
54
+ - **Sessions start being issued to principals nobody will notice a leak on** — machine identities, CI runners, embedded devices. ADR 0206 flagged this and it is the strongest argument for a cap; such a principal wants a short, non-sliding expiry set explicitly at creation, which the `expiresAt` parameter already supports without changing this default.
55
+ - **A compliance or hosting requirement names a maximum session age.** Then it belongs on `created_at` as a true ceiling, per ADR 0206, with the grace treatment `core_243` and 035 both gave.
56
+ - **The session list shows long-dead sessions accumulating faster than people prune them.** That is the visibility half failing, and it is now the only half.
@@ -385,3 +385,4 @@ This keeps the decision history honest and traceable.
385
385
  | 0291 | [**The accessibility dimension scores the transcript, so it lives in the session evaluator, not the grader panel** ([task 1002516](https://cloudbongos.com/builders#/task/1002516), clause 4 of criterion `accessibility-fluency-profile`). The task and its sibling task 1003937 both point at `modules/grading/`, but a v4 worker is handed `{ task, valueSummary, diff, changedFiles, … }` and **no transcript** — it judges the change a session produced, never how the builder was spoken to while producing it. The transcript lives in `session_records`, so the dimension does too: `modules/sessions/accessibility.js`, a pure scorer over the digest's per-turn detail, scored SERVER-SIDE at upload (a score the uploader could supply is a score nobody can read; and the SessionEnd hook's 6-second watchdog rules out a second round trip for the profile). Three signals, exactly the three the criterion names. **Profile-relative, and a sanctioned signal is not scored at all** — `engineer` waives jargon + framing, `comfortable` waives terminal-first, `mixed` widens rather than waives, and an all-waived session scores `null`, not a number, so a profile-sanctioned reply cannot score low even in principle; `verbosity` is not an input and every rate is per-1000-characters, so brevity is arithmetically incapable of lowering it. Advisory, gating nothing (ADR 0241's shape). The profile crosses the module boundary as DATA — the route resolves the `builder-settings` kernel port, never a sibling require (ADR 0083), with a test as the tripwire against a value rename. Detectors deliberately undercount (word-bounded jargon, after a substring match fired `ORM` on "format"; a click-path table of UIs that actually exist), because a metric that punishes correct work is what the task itself calls actively harmful. Does NOT answer task 1003937, whose copy/voice lens judges a DIFF and still needs the owner on cost, gate-or-advisory, and what standard to judge against.](0291-the-accessibility-dimension-scores-the-transcript-not-the-diff.md) | grading / sessions / accessibility |
386
386
  | 0292 | [**Both install paths stay, and vendoring turns out to be the production one** ([task 1003879](https://cloudbongos.com/builders#/task/1003879), goal 1000090 — public-npm **gate 6**, the decision row held back from gate 5's mechanical cleanup because both candidates were large deletions that could as easily be mistakes). Gate 6 framed the **vendored-tarball path** (~124 refs across `upgrade.js`/`init.js`/`provision-repo.js`/`package-core.js`) as insurance for hosts that cannot reach npm, to be weighed against the AGPL self-host promise. Read that way it loses. **That is not what the code does with it:** `provision-repo.js` resolves the core for every wizard-created project as "a tarball built from THIS CHECKOUT, file:-pinned", with `--registry-core` as the opt-out — vendoring is the production provisioning path, not the fallback, and deleting it breaks project creation. `resolveVendorableCoreTarball`'s three-rung ladder (reuse the control plane's own pinned tarball → pack the core it has INSTALLED → rebuild from source) exists to answer one question a registry install cannot express: *what is the control plane actually running?* Rungs 1 and 2 each name the bug they fixed — empty new repos when the runner is itself a consumer (task 1002275), and "the frozen-old-tarball births" (task 1002706). The owner's input — a checkout routinely runs ahead of the registry — holds, though not via `main`: since publish-on-merge, 136 of the last 300 `main` commits are release bumps, releases land every 9–51 min, and `main`'s `CORE_VERSION` equalled the registry `latest` (1.19.785) when this was written. Provisioning does not provision from `main`; it provisions from the control plane's own tree, and an unmerged branch or a deliberately-held pin exists on the registry at NO version, ever. **Supersedes ADR 0134's "vendored is the default until the registry path is proven end-to-end" clause:** the condition is MET twice over — with a token 2026-07-08 (task 2118 / blocker 71) and tokenless since [ADR 0283](0283-the-core-publishes-publicly-because-access-was-never-the-thing-protecting-it.md) (`npm pack @bongos/core` from a clean `HOME` fetches 25.5 MB) — and the default stands anyway, for load-bearing reasons rather than for want of proof. Costs accepted with open eyes: ~124 refs, a second install mode every scaffold/upgrade/provision path must keep straight, a Windows-only test failure ([task 1003839](https://cloudbongos.com/builders#/task/1003839)), `refreshStandaloneCorePin`. **Candidate 2 — `@cloudbongos/cli` — also STAYS, on a NEW justification:** task 1003872 was right that the original reason (reaching the board without a private-registry credential) is gone, so it needs one of its own, and the measurement supplies it — 32 files / 0.5 MB / `undici`, versus the core's 2,611 files / 46.4 MB / `express`+`pg`+`pino`. Collapsing it would make `npx` a 46 MB download dragging in a web server and a Postgres driver to print a task list. **Unpublishing was never on the table** (public at 0.1.3, strangers may have installed it); [ADR 0258](0258-the-public-cli-is-a-generated-client-package-not-the-published-core.md) stands with its *rationale* amended — a deliberately small entry point, no longer a private-core workaround. The one real rot the flip left is rung 2's stale "still-private registry package" clause, a comment. **Gate 6 closes keep/keep, no removal task.** Reopens if provisioning ever stops meaning "match the control plane".](0292-both-install-paths-stay-and-vendoring-is-the-production-one.md) | distribution / provisioning |
387
387
  | 0293 | [**The owner's deploy door is an INTENT the control plane drains, not a web tier with a shell** ([task 1003159](https://cloudbongos.com/builders#/task/1003159), goal 1000090). The owner, twice in a month: *"I don't think I can make anything live"*, then *"this is above my paygrade"*. Moving a core pin is gate 3 of the release pipeline and the ONLY act that makes shipped work visible to users — and it was reachable from exactly one place: a shell on the control-plane box holding an ssh key. No route, no hall control. An agent could not close the loop either, because the permission classifier blocks a mutating `sudo -u lars` over ssh BY DESIGN — so the gate had no owner-reachable control AND no agent-reachable one, which is a structural mismatch with a platform whose premise is that agents build and the human holds the gates. Measured cost the day it was written: cloudbongos.com served 1.19.754 while 1.19.757, carrying a shipped fix to the claimable-task feed, sat published and unreachable. **Decision: the door is an INTENT the control-plane runner drains** — the seam [ADR 0111](0111-instance-hosting-provisioning-module.md) already built and that provision/teardown/domain-attach/settings-apply/restart already ride; `enqueueIntent` already carries `requested_by`, so the actor requirement is met by the existing shape rather than bolted on. The web tier gains no sudo, no ssh key, no new capability — it gains the ability to ASK. Rejected: giving the web tier sudo (ADR 0043 makes the control plane a separate privilege domain precisely so compromising the HTTP-reachable process does not hand over the box), and a second bespoke queue (another set of claim/settle/retry semantics to get right, when this one's failure modes are already understood in production). **The control plane is enrolled as a SELF ROW** because `provisioning_intents.instance_id` is NOT NULL and the box has never had one — which is exactly why the door had nothing to point at. That same row puts a teardown button one query away from the platform, so `hosting_shape='control-plane'` is a distinct value every destructive leg can refuse BY NAME, and the rule is an ALLOW-list (`core-upgrade`, `restart`) rather than a deny-list of today's destructive actions: a deny-list would admit tomorrow's by omission, and "permitted by omission" here means the platform deleting itself. A partial unique index makes a second self row impossible. Rejected: a nullable `instance_id` meaning "the control plane" (an implicit encoding that reads as a bug at every later call site and silently widens every query assuming the FK holds). **The target version comes from the published registry, never the request** — not tidiness: the release lane mints a git tag BEFORE the npm publish completes (measured 8–13 minutes on 2026-09-16 against ~100s recorded earlier, learnings 1000263/1000266), so a door trusting "whatever main says" would routinely queue a version that does not exist and die on `npm error notarget`. **Two-step preview-then-confirm**, the owner's own call: auto-rollback already guards the bad-release case, this guards the different one — the wrong button. **Archon-gated server-side, no caching** (ADR 0016), with `core_241` giving the ledger an actor and a source, because a privileged action with an unattributed row is not acceptable and the table carried only a free-text note. **Truth is the BOX's `/version`, not the apex** (the apex sits behind pre-launch basic_auth and its HTML rides a CDN, so an apex read reports a stale answer with total confidence). Self-restart is safe for a stated reason rather than an assumed one: the runner and the web service are DIFFERENT systemd units, and a running process keeps the code it already loaded, so an upgrade rewriting the runner's own files takes effect next tick, never mid-run. Tenant instances ride the same queue and are a filed follow-up. What it does NOT do: make deploys automatic — ADR 0137's three gates stand, and this only gives the third a handle.](0293-the-owners-deploy-door-is-an-intent-the-control-plane-drains.md) | deploy / authority / provisioning |
388
+ | 0294 | [**CLI sessions do not expire; revocation is the whole remedy** ([task 1003956](https://cloudbongos.com/builders#/task/1003956), goal 1000110 — owner request 2026-09-16, after a three-week gap signed him out). A `bongos` sign-in died after 24h idle: `createSession` defaulted a null `expiresAt` to `now()+SESSION_TTL_MS` and `lookupSession` rewrote `expires_at = now()+24h` on every request. **This is the third argument about the number and the first about the mechanism** — V3.R38 ([task #259](https://cloudbongos.com/builders#/task/259), migration 035) set 1h, V3.R90 ([task 1000400](https://cloudbongos.com/builders#/task/1000400)) widened it to 24h because 1h "kicked builders out over lunch", and each round shortened the nuisance while leaving the mechanism alone. The hygiene case for keeping it (criterion C9 — bound a leaked token in the mode-0600 plaintext session file) **does not survive contact with refresh-on-use**: a leaked token is by definition one somebody is using, and every use rolled the expiry forward another day, so the timer could only ever fire on a session gone quiet — the legitimate holder stepping away. The control was aimed at attackers and landed exclusively on users. [ADR 0206](0206-own-scoped-session-revocation-no-absolute-ceiling.md) had already written half of this down ("a CLI bearer in daily use is, in practice, immortal") without drawing the conclusion. Decision: **CLI sessions carry no expiry and last until revoked.** `createSession` records `expires_at = NULL` when the caller names none — every such call site is `source: 'cli'` (device flow, federated sign-in, hall re-issue, dev-box pair) — and `lookupSession` still bumps `last_used_at` but rolls `expires_at` forward only via `CASE WHEN expires_at IS NULL THEN NULL ELSE …`; **without that CASE the first request after sign-in re-mints the very timeout this removes.** Migration `core_243` clears `expires_at` on live unrevoked `source='cli'` rows, so the change reaches existing sessions instead of charging every builder one last forced re-auth on deploy day — migration 035's grace inverted, for the reason 035 gave. **Browser sessions are untouched** (they pass an explicit 30 days, so the new default never reaches them, and the migration's `source='cli'` filter is its whole safety property — `lookupSession` only rolls an expiry it can already see, so a web row cleared by mistake stays cleared forever). `SESSION_TTL_MS` survives as the roll-forward window for rows that have an expiry, no longer as a default. The accepted cost, stated plainly: **a leaked CLI token is valid until revoked** — smaller in practice than it sounds, since under refresh-on-use an actively-used leak was already immortal, so what changes is the *abandoned* copy on a dead laptop. The remedy is ADR 0206's own-scoped revoke, immediate where a TTL is next-day, and that ADR's visibility half (`created_at` beside `last_used_at`) is now the primary control rather than a nice-to-have. Nothing about rank moves: `setBuilderRank` still sweeps on demotion and `requireBuilder` still refuses a deactivated builder on the hot path, neither consulting `expires_at`. Rejected: widening the TTL a fourth time; keeping it and making re-auth cheaper (already built — [task 1000401](https://cloudbongos.com/builders#/task/1000401), [task 1000504](https://cloudbongos.com/builders#/task/1000504) — the friction shrank, the interruption stayed); an absolute `created_at` ceiling (ADR 0206's reasoning holds unchanged); dropping the column (load-bearing for web); a blanket `UPDATE … SET expires_at = NULL`. Revisit for machine principals nobody will notice a leak on, a compliance maximum, or long-dead sessions accumulating faster than people prune them.](0294-cli-sessions-do-not-expire.md) | auth / account security / CLI |
@@ -1329,7 +1329,7 @@
1329
1329
  "auth"
1330
1330
  ],
1331
1331
  "summary": "POST /auth/cli-token/issue",
1332
- "description": "POST /auth/cli-token/issue — V3.R91 / task 401. Issue a fresh CLI bearer for the requesting builder, gated on a WEB COOKIE session (not a bearer). The browser already proves identity via the gds_session cookie; this endpoint mints a separate cli-source session row the builder can paste into ~/.config/otb/gds-session.json via scripts/gds/paste-token.js. Replaces the ~30-60s Device Flow when the CLI bearer has expired. rank: any-builder (own identity only) — no rank gate. auth source restriction: cookie ONLY. A bearer cannot mint a new bearer (a leaked CLI token shouldn't be able to extend its own lifetime; the httpOnly browser cookie is the harder-to-steal credential).\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
1332
+ "description": "POST /auth/cli-token/issue — V3.R91 / task 401. Issue a fresh CLI bearer for the requesting builder, gated on a WEB COOKIE session (not a bearer). The browser already proves identity via the gds_session cookie; this endpoint mints a separate cli-source session row the builder can paste into ~/.config/otb/gds-session.json via `bongos paste-token`. Replaces the ~30-60s Device Flow when the CLI bearer has expired. rank: any-builder (own identity only) — no rank gate. auth source restriction: cookie ONLY. A bearer cannot mint a new bearer (a leaked CLI token shouldn't be able to extend its own lifetime; the httpOnly browser cookie is the harder-to-steal credential).\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
1333
1333
  "x-rank": "any-builder",
1334
1334
  "x-source": "src/bongos/routes/auth.js",
1335
1335
  "responses": {