@bongos/core 1.20.21 → 1.20.23

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 (47) hide show
  1. package/.bongos-core.json +78 -48
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +4 -2
  4. package/clients/bongos-client/index.cjs +4 -2
  5. package/clients/bongos-client/index.d.ts +8 -4
  6. package/clients/bongos-client/index.mjs +4 -2
  7. package/docs/adr/0335-an-invite-is-a-pre-approved-row-on-the-projects-own-instance.md +1 -0
  8. package/docs/adr/0353-a-hub-invite-is-a-notice-of-the-projects-own-invite.md +70 -0
  9. package/docs/adr/README.md +1 -0
  10. package/docs/api/openapi.json +137 -72
  11. package/docs/api-reference.md +6 -5
  12. package/docs/architecture.md +14 -0
  13. package/docs/copy-inventory.md +196 -196
  14. package/docs/copy-registry.json +250 -250
  15. package/docs/module-api-changelog.md +4 -0
  16. package/docs/page-inventory.json +2 -1
  17. package/docs/page-readings.json +310 -306
  18. package/migrations/core_265_module_assessments.sql +97 -0
  19. package/modules/hall-ui/public/watch.js +23 -0
  20. package/modules/onboarding/routes/access-requests.js +36 -6
  21. package/modules/platform-identity/invite-notices.js +61 -0
  22. package/modules/platform-identity/module.json +1 -1
  23. package/modules/platform-identity/platform-identity.js +12 -18
  24. package/modules/platform-identity/routes/my-projects.js +6 -15
  25. package/modules/platform-identity/routes/projects.js +22 -15
  26. package/modules/platform-identity/routes/sso-invites.js +82 -0
  27. package/modules/platform-identity/tests/platform-identity.mjs +14 -11
  28. package/modules/public-landing/public/assets/cosmos.css +5 -0
  29. package/modules/public-landing/public/projects.html +35 -29
  30. package/modules/public-landing/public/projects.states.json +1 -0
  31. package/modules/ui-design/kit/serve.js +9 -0
  32. package/package-lock.json +2 -2
  33. package/package.json +1 -1
  34. package/release-notes.json +12 -0
  35. package/src/bongos/auth-admission.js +40 -0
  36. package/src/bongos/auth.js +2 -1
  37. package/src/module-api.js +8 -1
  38. package/tests/access_requests_invite.mjs +39 -1
  39. package/tests/application_lifecycle.mjs +41 -1
  40. package/tests/catalog_only_client.mjs +5 -3
  41. package/tests/hub_invite_notices.mjs +261 -0
  42. package/tests/module_api.mjs +1 -0
  43. package/tests/module_assessments_migration.mjs +96 -0
  44. package/tests/my_projects_surface.mjs +11 -1
  45. package/tests/project_invite_ownership.mjs +12 -7
  46. package/tests/project_invite_ui.mjs +42 -45
  47. package/tests/watch_applications_queue.mjs +33 -2
@@ -471,3 +471,4 @@ These 20 numbers are each shared by exactly two files. They are **accepted histo
471
471
  | 0350 | [**The hub holds a per-project WRITE deploy key, so a hosted project's upgraded pin always reaches its GitHub repo** ([task 1004291](https://cloudbongos.com/builders#/task/1004291), follow-up to task 1004065). Amends ADR 0176. The owner's token lasts one hour and the checkout's own key is read-only, so most hosted upgrades ended "GitHub was not updated". **D1:** a second deploy key per repo, registered `read_only: false` as `cloudbongos-pin-<slug>`, the pull key untouched. **D2:** kept outside every checkout in the runner's own directory (0700 dir, 0600 key, modes verified), unreadable by the project's account. **D3:** minted at standup and whenever a fresh owner token finds none, so reconnecting GitHub once is enough. **D4:** `pushUpgradePin` tries write key, then owner token, then origin, never forced. **D5:** revoked on teardown and disconnect. **D6:** a project on the shared app user is refused a key: "needs its own account first". **D7:** /deploy says reconnecting sets up lasting access.](0350-the-hub-holds-a-per-project-write-deploy-key-so-a-hosted-upgrade-reaches-github.md) | provisioning / security |
472
472
  | 0351 | [**A criterion closes on a UAT, not on its linked tasks shipping** ([task 1004392](https://cloudbongos.com/builders#/task/1004392), goal 1000089 — working area 3). Supersedes ADR 0183's close rule. `wa7-government` had closed on six Board Room navigation tasks while nothing it describes was on the live site. **D1:** three checks — Code (linked tasks done, ≥1 shipped; automatic), Live, UAT — and the sweep closes a criterion only with a CURRENT sign-off (no older than the newest linked ship). **D2 (owner):** "Awaiting UAT" is a visible state. **D3 (owner):** one step; the signer is refused if they shipped linked work, the project owner excepted. **D4 (owner):** an mp4/webm up to 100 MB on the project's own server. **D5 (owner):** backend-only is set while a criterion is open and takes a recording-free sign-off. **D6 (owner):** Live is read via the `deploy.taskWhere` port where the site can tell, attested otherwise. **D7 (owner):** already-closed criteria stay closed ("Closed before UAT"). **D8:** `/satisfy` is a recorded override that needs a reason. **D9:** `/goal-uat` + `scripts/gds/uat.js`; the hall is task 1004402.](0351-a-criterion-closes-on-a-uat.md) | lifecycle / criteria / review |
473
473
  | 0352 | [**A rollup report carries its "as of" in a header, and the hub keeps the newest** ([task 1004268](https://cloudbongos.com/builders#/task/1004268), goal 1000110). `POST /sso/activity/rollup` was last-writer-wins with no report timestamp, so a sign-in push read earlier and a ship push read later could land the older totals last. **D1:** the instance's read time rides a `Bongos-Report-As-Of` HEADER, not a body field, because the hub validates the body strictly and hub and instances upgrade independently: a body field from a newer instance would 400 on an older hub and lose the report, while an unknown header is ignored. **D2:** missing or unparseable = unstamped = last-writer-wins, never a 400. **D3:** the guard is a WHERE on the upsert's conflict branch (migration 026, `reported_as_of`, no backfill); a skipped update keeps the row lock, so the task 1004244 snapshot stays in step. **D4:** the stamp is compared only with the same client's, and clamped to the hub's `now()`.](0352-a-rollup-report-carries-its-as-of-in-a-header.md) | platform identity / federation |
474
+ | 0353 | [**A hub invite is a notice of the project's own invite, and signing in accepts it** ([task 1002818](https://cloudbongos.com/builders#/task/1002818), goal 1000110 — R6 of the admin console wave). The hub's own invite wrote a `pending` membership that no project reads, so an "accepted" invite met a locked door (new projects default to `apply`). **D1:** that row is now a NOTICE the PROJECT writes: its invite route calls `POST /sso/invites/notify` and a dismissal that leaves no invited row calls `/sso/invites/clear` (client credentials, pending rows only, always `{ ok: true }` so no account oracle); core's `notifyHubOfInvite` is fire-and-forget. **D2:** the hub-side accept route is deleted; signing in to the project accepts, and the hub's own sign-in witness turns the row into a membership. **D3 (owner):** `POST /projects/invite` writes nothing and answers `{ invited: false, invite_url }`, the project's hall invite link (`/builders/watch?invite=<login>`, which only pre-fills); the signed-assertion relay is deferred. **D4:** new routes, so an older hub 404s and only the notice is lost (ADR 0352).](0353-a-hub-invite-is-a-notice-of-the-projects-own-invite.md) | platform identity / federation |
@@ -388,7 +388,7 @@
388
388
  "access-requests"
389
389
  ],
390
390
  "summary": "POST /access-requests/invite",
391
- "description": "POST /access-requests/invite — an Archon admits a GitHub username BEFORE any request exists (task 1003044): the only way in on an invite-only project, where the public POST refuses, and a shortcut on an apply one. It writes the same access_request row the sign-in gate reads (status 'invited' — hasInvitedAccessRequest), so nothing new is consulted at sign-in; a pending request for that login is resolved in place rather than duplicated. Two Archons inviting the same login at once can each insert a row (the unique index covers pending only): harmless — the gate reads \"any invited row\" — and it shows as two lines in the invited history, so no lock is taken. A fresh row is marked kind='invite' with created_by = the sender (a rescind overwrites resolved_by) and the optional note as the inviter's words. A pending request resolved in place stays an application with its applicant's own note (ADR 0335 D2.1–D2.3; task 1002814). body: { github_login, note? }. 201 with a new row; 200 with the row when a pending request was resolved; 200 already_invited when an invite already stands; 409 already_member when they have an account. rank: archon — the same gate as resolving a request.\n\n**Rank:** `archon` — Archon only (rank and identity management + the escalation keys — the trust boundary).\n\n**Permissions:** `access_request.review` (all required).",
391
+ "description": "POST /access-requests/invite — an Archon admits a GitHub username BEFORE any request exists (task 1003044): the only way in on an invite-only project, where the public POST refuses, and a shortcut on an apply one. It writes the same access_request row the sign-in gate reads (status 'invited' — hasInvitedAccessRequest), so nothing new is consulted at sign-in; a pending request for that login is resolved in place rather than duplicated. Two Archons inviting the same login at once can each insert a row (the unique index covers pending only): harmless — the gate reads \"any invited row\" — and it shows as two lines in the invited history, so no lock is taken. A fresh row is marked kind='invite' with created_by = the sender (a rescind overwrites resolved_by) and the optional note as the inviter's words. A pending request resolved in place stays an application with its applicant's own note (ADR 0335 D2.1–D2.3; task 1002814). Every outcome that leaves the login invited also notifies the hub, so the invitee sees it in My Projects (ADR 0353). body: { github_login, note? }. 201 with a new row; 200 with the row when a pending request was resolved; 200 already_invited when an invite already stands; 409 already_member when they have an account. rank: archon — the same gate as resolving a request.\n\n**Rank:** `archon` — Archon only (rank and identity management + the escalation keys — the trust boundary).\n\n**Permissions:** `access_request.review` (all required).",
392
392
  "x-rank": "archon",
393
393
  "x-source": "modules/onboarding/routes/access-requests.js",
394
394
  "x-permissions": [
@@ -13194,58 +13194,6 @@
13194
13194
  ]
13195
13195
  }
13196
13196
  },
13197
- "/my-projects/invites/{clientId}/accept": {
13198
- "post": {
13199
- "operationId": "post_my_projects_invites_clientId_accept",
13200
- "tags": [
13201
- "my-projects"
13202
- ],
13203
- "summary": "POST /my-projects/invites/:clientId/accept",
13204
- "description": "accept an invite (pending → builder) for the caller's OWN account.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
13205
- "x-rank": "any-builder",
13206
- "x-source": "modules/platform-identity/routes/my-projects.js",
13207
- "parameters": [
13208
- {
13209
- "name": "clientId",
13210
- "in": "path",
13211
- "required": true,
13212
- "schema": {
13213
- "type": "string"
13214
- },
13215
- "description": "Path parameter `clientId`."
13216
- }
13217
- ],
13218
- "responses": {
13219
- "200": {
13220
- "description": "Success.",
13221
- "content": {
13222
- "application/json": {
13223
- "schema": {
13224
- "$ref": "#/components/schemas/PostMyProjectsInvitesClientIdAcceptResponse"
13225
- }
13226
- }
13227
- }
13228
- },
13229
- "400": {
13230
- "$ref": "#/components/responses/BadRequest"
13231
- },
13232
- "401": {
13233
- "$ref": "#/components/responses/Unauthorized"
13234
- },
13235
- "403": {
13236
- "$ref": "#/components/responses/Forbidden"
13237
- },
13238
- "404": {
13239
- "$ref": "#/components/responses/NotFound"
13240
- }
13241
- },
13242
- "security": [
13243
- {
13244
- "builderSession": []
13245
- }
13246
- ]
13247
- }
13248
- },
13249
13197
  "/my-projects/invites/{clientId}/decline": {
13250
13198
  "post": {
13251
13199
  "operationId": "post_my_projects_invites_clientId_decline",
@@ -14001,7 +13949,7 @@
14001
13949
  "projects"
14002
13950
  ],
14003
13951
  "summary": "POST /projects/invite",
14004
- "description": "own-or-metic in effect: invite a builder into a project (ADR 0141 §5, task 1002115), called by a `project.curate` holder OR the project's OWN OWNER (task 1003580; the ownership proof is `provisioning_instances.owner_builder_id`, reached through a port — see invite-authz.js). The wizard's audience is any signed-in GitHub user, typically a xenos who used to get 403 on their own project. A caller who is neither gets the unchanged permission refusal, identical whether or not the project exists. Pre-seeds a 'pending' membership the invitee accepts from My Projects. V1 invites by github_login and requires the invitee to already have a platform account (they build on some project); inviting someone entirely new to the platform (GitHub-id resolution) is a follow-up.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `project.curate` (all required).",
13952
+ "description": "own-or-metic in effect: where to invite a builder into a project, asked by a `project.curate` holder OR the project's OWN OWNER (task 1003580; the ownership proof is `provisioning_instances.owner_builder_id`, reached through a port — see invite-authz.js). The wizard's audience is any signed-in GitHub user, typically a xenos who used to get 403 on their own project. A caller who is neither gets the unchanged permission refusal, identical whether or not the project exists. IT WRITES NOTHING (task 1002818, ADR 0353). It used to pre-seed a 'pending' hub membership, but the hub holds no verdict: that row let nobody in, and on any door but `open` (new projects default to `apply`) the invitee who signed in met a locked door. Only the project's own invite admits (ADR 0335 D1/D2), so the answer is 200 `{ invited: false, invite_url }` — the project's hall invite box with the login pre-filled. A 200, not a refusal: it is the answer every admitted caller gets, and a 4xx on the normal path would log a failed request in the owner's browser on every invite. Sending it there also reaches this hub: the project notifies it (POST /sso/invites/notify), and the invitee sees it in My Projects.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `project.curate` (all required).",
14005
13953
  "x-rank": "metic+archon",
14006
13954
  "x-source": "modules/platform-identity/routes/projects.js",
14007
13955
  "x-permissions": [
@@ -14020,7 +13968,14 @@
14020
13968
  },
14021
13969
  "responses": {
14022
13970
  "200": {
14023
- "description": "Success."
13971
+ "description": "Success.",
13972
+ "content": {
13973
+ "application/json": {
13974
+ "schema": {
13975
+ "$ref": "#/components/schemas/PostProjectsInviteResponse"
13976
+ }
13977
+ }
13978
+ }
14024
13979
  },
14025
13980
  "400": {
14026
13981
  "$ref": "#/components/responses/ValidationFailed"
@@ -18684,6 +18639,70 @@
18684
18639
  "security": []
18685
18640
  }
18686
18641
  },
18642
+ "/sso/invites/clear": {
18643
+ "post": {
18644
+ "operationId": "post_sso_invites_clear",
18645
+ "tags": [
18646
+ "sso"
18647
+ ],
18648
+ "summary": "POST /sso/invites/clear",
18649
+ "description": "authenticated by client_id + client_secret, not a builder rank.\n\n**Rank:** `public` — No authentication — any caller.",
18650
+ "x-rank": "public",
18651
+ "x-source": "modules/platform-identity/routes/sso-invites.js",
18652
+ "requestBody": {
18653
+ "required": true,
18654
+ "content": {
18655
+ "application/json": {
18656
+ "schema": {
18657
+ "$ref": "#/components/schemas/PostSsoInvitesClearRequest"
18658
+ }
18659
+ }
18660
+ },
18661
+ "x-validated": true
18662
+ },
18663
+ "responses": {
18664
+ "200": {
18665
+ "description": "Success."
18666
+ },
18667
+ "400": {
18668
+ "$ref": "#/components/responses/ValidationFailed"
18669
+ }
18670
+ },
18671
+ "security": []
18672
+ }
18673
+ },
18674
+ "/sso/invites/notify": {
18675
+ "post": {
18676
+ "operationId": "post_sso_invites_notify",
18677
+ "tags": [
18678
+ "sso"
18679
+ ],
18680
+ "summary": "POST /sso/invites/notify",
18681
+ "description": "authenticated by client_id + client_secret, not a builder rank.\n\n**Rank:** `public` — No authentication — any caller.",
18682
+ "x-rank": "public",
18683
+ "x-source": "modules/platform-identity/routes/sso-invites.js",
18684
+ "requestBody": {
18685
+ "required": true,
18686
+ "content": {
18687
+ "application/json": {
18688
+ "schema": {
18689
+ "$ref": "#/components/schemas/PostSsoInvitesNotifyRequest"
18690
+ }
18691
+ }
18692
+ },
18693
+ "x-validated": true
18694
+ },
18695
+ "responses": {
18696
+ "200": {
18697
+ "description": "Success."
18698
+ },
18699
+ "400": {
18700
+ "$ref": "#/components/responses/ValidationFailed"
18701
+ }
18702
+ },
18703
+ "security": []
18704
+ }
18705
+ },
18687
18706
  "/sso/membership/check-in": {
18688
18707
  "post": {
18689
18708
  "operationId": "post_sso_membership_check_in",
@@ -27289,21 +27308,6 @@
27289
27308
  "left"
27290
27309
  ]
27291
27310
  },
27292
- "PostMyProjectsInvitesClientIdAcceptResponse": {
27293
- "type": "object",
27294
- "properties": {
27295
- "ok": {
27296
- "type": "boolean"
27297
- },
27298
- "accepted": {
27299
- "type": "boolean"
27300
- }
27301
- },
27302
- "required": [
27303
- "ok",
27304
- "accepted"
27305
- ]
27306
- },
27307
27311
  "PostMyProjectsInvitesClientIdDeclineResponse": {
27308
27312
  "type": "object",
27309
27313
  "properties": {
@@ -27400,6 +27404,19 @@
27400
27404
  ],
27401
27405
  "additionalProperties": false
27402
27406
  },
27407
+ "PostProjectsInviteResponse": {
27408
+ "type": "object",
27409
+ "properties": {
27410
+ "invited": {
27411
+ "type": "boolean"
27412
+ },
27413
+ "invite_url": {}
27414
+ },
27415
+ "required": [
27416
+ "invited",
27417
+ "invite_url"
27418
+ ]
27419
+ },
27403
27420
  "PostProjectsProjectIdSpecialitiesRequest": {
27404
27421
  "type": "object",
27405
27422
  "properties": {
@@ -28452,6 +28469,54 @@
28452
28469
  ],
28453
28470
  "additionalProperties": false
28454
28471
  },
28472
+ "PostSsoInvitesClearRequest": {
28473
+ "type": "object",
28474
+ "properties": {
28475
+ "client_id": {
28476
+ "type": "string",
28477
+ "maxLength": 200
28478
+ },
28479
+ "client_secret": {
28480
+ "type": "string",
28481
+ "maxLength": 512
28482
+ },
28483
+ "github_login": {
28484
+ "type": "string",
28485
+ "minLength": 1,
28486
+ "maxLength": 200
28487
+ }
28488
+ },
28489
+ "required": [
28490
+ "client_id",
28491
+ "client_secret",
28492
+ "github_login"
28493
+ ],
28494
+ "additionalProperties": false
28495
+ },
28496
+ "PostSsoInvitesNotifyRequest": {
28497
+ "type": "object",
28498
+ "properties": {
28499
+ "client_id": {
28500
+ "type": "string",
28501
+ "maxLength": 200
28502
+ },
28503
+ "client_secret": {
28504
+ "type": "string",
28505
+ "maxLength": 512
28506
+ },
28507
+ "github_login": {
28508
+ "type": "string",
28509
+ "minLength": 1,
28510
+ "maxLength": 200
28511
+ }
28512
+ },
28513
+ "required": [
28514
+ "client_id",
28515
+ "client_secret",
28516
+ "github_login"
28517
+ ],
28518
+ "additionalProperties": false
28519
+ },
28455
28520
  "PostSsoMembershipCheckInRequest": {
28456
28521
  "type": "object",
28457
28522
  "properties": {
@@ -29564,8 +29629,8 @@
29564
29629
  "description": "A required dependency/feature is not configured or is temporarily down."
29565
29630
  }
29566
29631
  },
29567
- "x-endpoint-count": 462,
29568
- "x-schema-count": 494,
29632
+ "x-endpoint-count": 463,
29633
+ "x-schema-count": 496,
29569
29634
  "x-undocumented-bodies": 12,
29570
29635
  "x-response-schemas": 336,
29571
29636
  "x-generated-by": "scripts/gds/gen-api-docs.js"
@@ -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. 462 endpoints across 82 route files.
5
+ > **Generated from the live route files** — the route file is authoritative. 463 endpoints across 83 route files.
6
6
  > Machine-readable spec: [`docs/api/openapi.json`](api/openapi.json) (OpenAPI 3.1). Rendered docs site: **`/docs`** (e.g. `cloudbongos.com/docs`).
7
7
 
8
8
  Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust-boundary-server-enforced-permissions.md)): `public` < `any-builder` < `metic+archon` < `archon`.
@@ -506,14 +506,13 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
506
506
  | POST | `/api/bongos/modules/:key/submit` | `metic+archon` | `signed_by` | POST /api/bongos/modules/:key/submit — ADR 0107 §1 upstreaming submission. |
507
507
  | GET | `/api/bongos/modules/submissions` | `metic+archon` | — | GET /api/bongos/modules/submissions?status=&module_key= — the upstreaming review queue (task 1771 reads this). |
508
508
 
509
- ## `my-projects` (6)
509
+ ## `my-projects` (5)
510
510
 
511
511
  | Method | Path | Rank | Body | Description |
512
512
  |---|---|---|---|---|
513
513
  | GET | `/api/bongos/my-projects` | `any-builder` | — | the caller's OWN cross-project memberships (own data). |
514
514
  | POST | `/api/bongos/my-projects/:clientId/leave` | `any-builder` | — | leave a project from the caller's OWN membership list. |
515
515
  | GET | `/api/bongos/my-projects/invites` | `any-builder` | — | the caller's OWN pending invitations (ADR 0141 §5). |
516
- | POST | `/api/bongos/my-projects/invites/:clientId/accept` | `any-builder` | — | accept an invite (pending → builder) for the caller's OWN account. |
517
516
  | POST | `/api/bongos/my-projects/invites/:clientId/decline` | `any-builder` | — | decline an invite (delete the pending row) for the caller's OWN account. |
518
517
  | POST | `/api/bongos/my-projects/join` | `any-builder` | `origin`, `note` | request to join a project. |
519
518
 
@@ -559,7 +558,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
559
558
  | PATCH | `/api/bongos/projects/:id/status` | `metic+archon` | `status` | THE TAKEDOWN LEVER (R08, task 1002327; ADR 0252 §5.1). |
560
559
  | POST | `/api/bongos/projects/:projectId/specialities` | `any-builder` | `name`, `discipline`, `summary`, `contract_md`, `visibility`, `sellable`, `skills` | POST /projects/:projectId/specialities — author one OWNED BY A PROJECT. |
561
560
  | GET | `/api/bongos/projects/featured` | `public` | — | the public map of projects; no auth by design. |
562
- | POST | `/api/bongos/projects/invite` | `metic+archon` | `client_id`, `github_login` | own-or-metic in effect: invite a builder into a project (ADR 0141 §5, task 1002115), called by a `project.curate` holder OR the project's… |
561
+ | POST | `/api/bongos/projects/invite` | `metic+archon` | `client_id`, `github_login` | own-or-metic in effect: where to invite a builder into a project, asked by a `project.curate` holder OR the project's OWN OWNER (task 100… |
563
562
 
564
563
  ## `provisioning` (34)
565
564
 
@@ -719,7 +718,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
719
718
  | GET | `/api/bongos/specialities/installed-skills` | `any-builder` | — | GET /specialities/installed-skills — every skill THIS instance has, each explained the way the adoption walkthrough explains it, so a spe… |
720
719
  | GET | `/api/bongos/specialities/offered` | `any-builder` | — | GET /specialities/offered — the suggestions for THIS builder. |
721
720
 
722
- ## `sso` (8)
721
+ ## `sso` (10)
723
722
 
724
723
  | Method | Path | Rank | Body | Description |
725
724
  |---|---|---|---|---|
@@ -728,6 +727,8 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
728
727
  | GET | `/api/bongos/sso/authorize` | `public` | — | GET /sso/authorize?client_id=&redirect_uri=&state= — the browser authorize leg. |
729
728
  | POST | `/api/bongos/sso/device/poll` | `public` | `device_code` | POST /sso/device/poll — poll the hub device flow. |
730
729
  | POST | `/api/bongos/sso/device/start` | `public` | `origin` | POST /sso/device/start — begin CLI federation. |
730
+ | POST | `/api/bongos/sso/invites/clear` | `public` | `client_id`, `client_secret`, `github_login` | authenticated by client_id + client_secret, not a builder rank. |
731
+ | POST | `/api/bongos/sso/invites/notify` | `public` | `client_id`, `client_secret`, `github_login` | authenticated by client_id + client_secret, not a builder rank. |
731
732
  | POST | `/api/bongos/sso/membership/check-in` | `public` | `client_id`, `client_secret`, `github_id`, `github_login`, `display_name`, `avatar_url`, `membership_kind` | POST /sso/membership/check-in — a federated instance reports, on every sign-in, that a builder builds on its project (ADR 0141 §3). |
732
733
  | GET | `/api/bongos/sso/pubkey` | `public` | — | GET /sso/pubkey — the hub's Ed25519 assertion-verification key (public). |
733
734
  | POST | `/api/bongos/sso/token` | `public` | `client_id`, `client_secret`, `code`, `redirect_uri` | POST /sso/token — server-to-server code redemption. |
@@ -258,6 +258,20 @@ module_entitlements(id, module_key, holder_kind IN (builder|instance), holder_re
258
258
  -- keeping the row.
259
259
  ```
260
260
 
261
+ Module assessments (migration `core_265`, task 1003814 — [ADR 0343](adr/0343-a-module-score-is-a-security-gate-then-an-average-of-visible-parts.md) + [ADR 0347](adr/0347-every-store-module-ships-a-how-to.md) D5; per module VERSION, append-only history, the current row is the newest; never reads price):
262
+
263
+ ```
264
+ module_assessment_signals(id, version_id → store_module_versions, part IN (security|tests|
265
+ install|reliability|tester_feedback|docs), outcome IN (scored|passed|failed|
266
+ no_data|pending|not_scored), score 0-100 iff scored, sample_size, reason,
267
+ detail jsonb, measured_at) -- security is pass/fail only (the D2 gate);
268
+ -- "no data yet" is an outcome, never a zero (D3/D5)
269
+ module_assessment_scores(id, version_id, kind IN (computed|override), overall 0-100 | NULL,
270
+ security_passed, is_new (D5 "New" label), formula, signal_ids bigint[],
271
+ override_by, override_reason, computed_at) -- an override names who + why
272
+ -- and is a new row on the computed history, never an edit (D6)
273
+ ```
274
+
261
275
  Code: `src/bongos/module-entitlements.js` (`grantEntitlement`, `recordAcquired`, `revokeEntitlement`, `checkEntitlement`, `listEntitlements`). Read routes (own-scoped, `requireBuilder`): `GET /store/entitlements`, `GET /store/modules/:key/entitlement`. Install (task 1003785) grants a free module through `POST /store/modules/:key/acquire`; the buy action (area 8) will grant a paid one.
262
276
 
263
277
  Bongos views: `claimable_tasks` (rebuilt in 006 to use SELECT t.*; rebuilt again in 014 so the new `discipline` column flows through), `version_progress` (rebuilt in 006 to count only leaf tasks for forward-compat with task hierarchy), `tasks_session_fit` (new in 006: derives `fits_session_modes[]` and `credits_per_minute`; rebuilt in 014 for the same SELECT t.* refresh), `task_grades_with_context` (new in 016: joins task_grades × tasks × builders × cost_log so the V4 optimizer has quality-per-dollar in one read).