@bongos/core 1.19.416 → 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.
Files changed (35) hide show
  1. package/.bongos-core.json +70 -35
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +6 -0
  4. package/clients/bongos-client/index.cjs +6 -0
  5. package/clients/bongos-client/index.d.ts +10 -0
  6. package/clients/bongos-client/index.mjs +6 -0
  7. package/docs/adr/0205-federated-checkin-needs-the-hubs-own-signin-witness.md +79 -0
  8. package/docs/adr/0206-own-scoped-session-revocation-no-absolute-ceiling.md +59 -0
  9. package/docs/adr/README.md +2 -0
  10. package/docs/api/openapi.json +185 -4
  11. package/docs/api-reference.md +5 -2
  12. package/docs/architecture.md +1 -0
  13. package/docs/copy-inventory.md +51 -36
  14. package/docs/copy-registry.json +175 -40
  15. package/docs/file-map.md +1 -0
  16. package/docs/module-api-changelog.md +4 -0
  17. package/modules/dev-box/app/src/vendor/bongos-client.cjs +6 -0
  18. package/modules/hall-ui/public/settings-sessions.js +119 -0
  19. package/modules/hall-ui/public/settings.css +25 -0
  20. package/modules/hall-ui/public/settings.html +22 -0
  21. package/modules/platform-identity/migrations/platform_identity_014_membership_signin_proof.sql +41 -0
  22. package/modules/platform-identity/platform-identity.js +88 -15
  23. package/modules/platform-identity/routes/sso.js +54 -10
  24. package/modules/platform-identity/tests/platform-identity.mjs +25 -6
  25. package/package-lock.json +2 -2
  26. package/package.json +1 -1
  27. package/src/bongos/db.js +46 -0
  28. package/src/bongos/route-rank-check.js +10 -0
  29. package/src/bongos/routes/my-sessions.js +128 -0
  30. package/src/bongos/routes.js +5 -0
  31. package/src/module-api.js +1 -1
  32. package/tests/hub_recent_ships.mjs +4 -1
  33. package/tests/own_scope_guard.mjs +5 -0
  34. package/tests/own_session_revoke.mjs +410 -0
  35. package/tests/sso_checkin_identity_spoof.mjs +348 -0
@@ -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",
@@ -12603,7 +12740,7 @@
12603
12740
  "sso"
12604
12741
  ],
12605
12742
  "summary": "POST /sso/membership/check-in",
12606
- "description": "POST /sso/membership/check-in — a federated instance reports, on every sign-in, that a builder builds on its project (ADR 0141 §3). Server-to-server, authed by client_id + client_secret (same constant-time check as /sso/token). Upserts the account (so a CLI-only builder still appears in My Projects) + the membership with a COARSE membership_kind. The client_id is taken from the AUTHENTICATED client — the caller cannot report membership for a project it doesn't hold the secret for. THE FIELD IS `membership_kind`; `role` IS ACCEPTED FOR THE WINDOW (task 1003090, ADR 0177 §\"the fifth use\"). ADR 0174 reserves `role` for the three crafts, and the old value 'builder' was a craft name meaning \"non-owner member\" — so both the field and that value are renamed. Unlike the goal-membership rename, this is a CROSS-VERSION wire contract: the callers are other instances on their own upgrade schedule, so an un-upgraded instance still sends `role: 'builder'` and the hub must keep understanding it. `membership_kind` WINS when both arrive (a sender in the window sends both deliberately). rank: public — authenticated by client_id + client_secret, not a builder rank.\n\n**Rank:** `public` — No authentication — any caller.",
12743
+ "description": "POST /sso/membership/check-in — a federated instance reports, on every sign-in, that a builder builds on its project (ADR 0141 §3). Server-to-server, authed by client_id + client_secret (same constant-time check as /sso/token). Refreshes an EXISTING `platform_identity_project_memberships` row's COARSE membership_kind. The client_id is taken from the AUTHENTICATED client — the caller cannot report membership for a project it doesn't hold the secret for. WHAT THIS CALL MAY NOT DO, AND WHY (ADR 0205, security report 1000027). Client credentials say WHICH PROJECT is calling; they say nothing about WHO the github_id in the body is. So this route may neither create a membership nor rewrite an account's identity: it updates a row the HUB already witnessed a sign-in for (signin_verified_at, stamped only at /sso/token and /sso/device/poll), and it creates the account shell only when absent. Before this, a client holding only its own secret could overwrite anyone's public github_login + avatar_url AND mint the membership that /sso/activity/rollup's membership_required precheck (the ADR 0171 D7 anti-impersonation gate) trusts — so the gate was satisfiable by the very call it was meant to gate. Nothing legitimate is lost: check-in only ever fires after a hub-authenticated sign-in (src/bongos/auth-idp.js), which stamps the witness first. THE FIELD IS `membership_kind`; `role` IS ACCEPTED FOR THE WINDOW (task 1003090, ADR 0177 §\"the fifth use\"). ADR 0174 reserves `role` for the three crafts, and the old value 'builder' was a craft name meaning \"non-owner member\" — so both the field and that value are renamed. Unlike the goal-membership rename, this is a CROSS-VERSION wire contract: the callers are other instances on their own upgrade schedule, so an un-upgraded instance still sends `role: 'builder'` and the hub must keep understanding it. `membership_kind` WINS when both arrive (a sender in the window sends both deliberately). rank: public — authenticated by client_id + client_secret, not a builder rank.\n\n**Rank:** `public` — No authentication — any caller.",
12607
12744
  "x-rank": "public",
12608
12745
  "x-source": "modules/platform-identity/routes/sso.js",
12609
12746
  "requestBody": {
@@ -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
  }
@@ -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. 327 endpoints across 52 route files.
5
+ > **Generated from the live route files** — the route file is authoritative. 330 endpoints across 53 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/<redacted>.md)): `public` < `any-builder` < `metic+archon` < `archon`.
@@ -333,7 +333,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
333
333
  | POST | `/api/bongos/llm-cache/lookup` | `metic+archon` | `cache_key`, `tokens_saved_est` | POST /llm-cache/lookup — hit/miss + atomic hit_count bump. |
334
334
  | POST | `/api/bongos/llm-cache/store` | `metic+archon` | `spec`, `result_stdout`, `result_cost_usd` | POST /llm-cache/store — upsert a freshly-produced result. |
335
335
 
336
- ## `me` (27)
336
+ ## `me` (30)
337
337
 
338
338
  | Method | Path | Rank | Body | Description |
339
339
  |---|---|---|---|---|
@@ -358,6 +358,9 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
358
358
  | GET | `/api/bongos/me/recent-ships` | `any-builder` | — | Tasks this builder has shipped, most recent first. |
359
359
  | GET | `/api/bongos/me/render` | `any-builder` | — | Per-builder "render widgets" knob (task 1279, shares the behavior_prefs jsonb / migration 119). |
360
360
  | PATCH | `/api/bongos/me/render` | `any-builder` | _undocumented_ | PATCH the render knob. |
361
+ | GET | `/api/bongos/me/sessions` | `any-builder` | — | GET /api/bongos/me/sessions — every live session on the CALLER's account. |
362
+ | POST | `/api/bongos/me/sessions/revoke` | `any-builder` | `session_id` | POST /api/bongos/me/sessions/revoke — kill ONE of the caller's own sessions. |
363
+ | POST | `/api/bongos/me/sessions/revoke-all` | `any-builder` | validated | POST /api/bongos/me/sessions/revoke-all — sign out everywhere. |
361
364
  | GET | `/api/bongos/me/skill-prefs` | `any-builder` | — | Per-builder skill model preferences (V3.R85 / task 304, migration 037). |
362
365
  | PATCH | `/api/bongos/me/skill-prefs` | `any-builder` | _undocumented_ | PATCH a partial set of skill overrides. |
363
366
  | GET | `/api/bongos/me/sound-prefs` | `any-builder` | — | Per-builder per-sound on/off preferences (V3.R91 / task 786, migration 075). |
@@ -220,6 +220,7 @@ Bongos views: `claimable_tasks` (rebuilt in 006 to use SELECT t.*; rebuilt again
220
220
  - **Self-describing (task 1918, [ADR 0109](adr/<redacted>.md)).** The whole surface is documented in the standard **OpenAPI 3.1** format at [`docs/api/openapi.json`](api/openapi.json), **generated from the live route files** by `scripts/gds/gen-api-docs.js` (reusing the same `route-rank-check.js` introspection the ship-time rank gate uses — so it can't drift; a fitness `--check` + ship-time regen enforce freshness). A rendered docs site (vendored Redoc) is served at **`/docs`** on the apex of every instance (`cloudbongos.com/docs` canonical); `GET /api/gds` returns a machine-discovery index pointing at it. The generated [`docs/api-reference.md`](api-reference.md) supersedes the old hand-maintained `routes-permissions.md`. The mount prefix is the single `src/bongos/api-prefix.js` constant.
221
221
 
222
222
  - Auth: `POST /auth/device/start`, `/poll` (CLI Device Flow); `GET /auth/web/start`, `/web/callback` (browser); `POST /auth/logout`; app pairing for the Dev Box desktop app ([#904](https://example.com/builders#/task/904), [ADR 0045](adr/<redacted>.md), store [`src/bongos/app-pair.js`](../src/bongos/app-pair.js) — in-memory, 10-min TTL): `POST /auth/app-pair/start` + `/poll` (public; the secret poll_token is the bearer and the minted token is delivered to it exactly once), `GET /auth/app-pair/info` + `POST /auth/app-pair/approve` (any-builder; approve is **cookie-gated** like `/auth/cli-token/issue` so a leaked bearer can't mint siblings; approving from `/builders/pair?code=…` signs the app in as the approver, `source='cli'`).
223
+ - Own-scoped session self-service ([ADR 0206](adr/<redacted>.md), task 1003341), in [`src/bongos/routes/my-sessions.js`](../src/bongos/routes/my-sessions.js): `GET /me/sessions` (every live session on the caller's own account — source, user agent, created/last-used/expires, a `current` flag; the token value is never selected), `POST /me/sessions/revoke` (`{session_id}` — one of your own; a miss answers `revoked: 0` so a stranger's id cannot be probed) and `POST /me/sessions/revoke-all` (sign out everywhere, the requesting session included). All three gate on `requireBuilder` alone and key every statement on `req.builder.id` — an account is never named in the query, body or path. They exist because a bearer refreshes to `now()+24h` on every call, so a leaked one never expires, and the cross-builder pair (`POST /auth/revoke`, `/auth/revoke-all`, both still gated on the admin `auth.session.revoke` atom) needed an Archon the victim might not have. Surfaced in the hall at Settings → Active sessions.
223
224
  - Authenticated CLI/web: `GET /me` (returns `rank`), `GET /versions[/progress]`, `GET /tasks[?version=&status=&kind=&discipline=]`, `GET /tasks/claimable[?version=&discipline=]`, `GET /tasks/:id`, `POST /tasks` (accepts `discipline` + `criterion_ids`; **metic+** — [ADR 0090](adr/<redacted>.md)), `PATCH /tasks/:id` (supports `parent_task_id`, `kind`, `discipline`, `goal_id` — (re)assign the task's goal; must be a goal in the task's own version, task 1763; **metic+**), `POST /tasks/:id/promote` (**metic+**), `POST /claims`, `POST /claims/:id/resolve`, `POST /cost`.
224
225
  - Criterion rollup + task↔criterion links ([ADR 0025](adr/<redacted>.md), [#435](https://example.com/builders#/task/435)/[#438](https://example.com/builders#/task/438)): `GET /versions/:id/progress` rolls up each done-when criterion → its gating tasks (via `task_criteria`) → live status counts + the not-yet-shipped `remaining[]` + `unattributed_tasks` (the read behind the `/status` skill, in `src/bongos/done-when.js criterionProgress`). The link CRUD mirrors the dependency endpoints: `GET /tasks/:id/criteria`, `POST /tasks/:id/criteria` (**metic+** — [ADR 0090](adr/<redacted>.md)) and `DELETE /tasks/:id/criteria/:criterionId` (**metic+**). A criterion ref is a numeric `done_when_criteria.id`, a positional `"Cn"` token, or a `criterion_id` slug, resolved against the task's version (`POST /tasks` links at create time so `/status` counts the task with no backfill).
225
226
  - Server-mediated branch publish (ADR 0055 / [#1025](https://example.com/builders#/task/1025) — lets a dev box with no GitHub push credential ship). Owner-gated like ship: `POST /tasks/:id/publish-branch` (`requireBuilder` + `gateTaskOwnership`, **NOT** `allowBoxScope` → a box-scoped session is `403 box_scope`, so the builder must `/builder-reauth` first; own 32 MB json parser for the base64 thin-bundle body, 20 MB decoded cap; the server pushes the branch + opens the PR + auto-merges with a server-side push credential — a **GitHub App** installation token (short-lived, repo-scoped; preferred, [ADR 0055](adr/<redacted>.md) update / task 1028) or the `GITHUB_PUSH_TOKEN` PAT fallback; `503 push_unconfigured` when neither is set; `400 tip_mismatch` if the bundle tip ≠ the claimed `head_sha`) and `GET /tasks/:id/publish-status?branch=…` (polls PR + deploy-prod state derived live from GitHub). `ship.js` uses these in `ci` mode only when `pushVia()` resolves to server (a box, or `OTB_PUSH_VIA_SERVER=1`); a laptop with `gh` keeps the local push path unchanged. Implementation in `src/bongos/github-push.js`.