@bongos/core 1.20.20 → 1.20.22
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 +80 -50
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +6 -2
- package/clients/bongos-client/index.cjs +6 -2
- package/clients/bongos-client/index.d.ts +11 -4
- package/clients/bongos-client/index.mjs +6 -2
- package/docs/adr/0335-an-invite-is-a-pre-approved-row-on-the-projects-own-instance.md +1 -0
- package/docs/adr/0353-a-hub-invite-is-a-notice-of-the-projects-own-invite.md +70 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +179 -73
- package/docs/api-reference.md +8 -6
- package/docs/copy-inventory.md +196 -196
- package/docs/copy-registry.json +250 -250
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +2 -1
- package/docs/page-readings.json +310 -306
- package/modules/hall-ui/public/watch.js +23 -0
- package/modules/onboarding/routes/access-requests.js +36 -6
- package/modules/platform-identity/guild-map.js +105 -0
- package/modules/platform-identity/invite-notices.js +61 -0
- package/modules/platform-identity/module.json +1 -1
- package/modules/platform-identity/platform-identity.js +12 -18
- package/modules/platform-identity/routes/guilds-public.js +36 -0
- package/modules/platform-identity/routes/my-projects.js +6 -15
- package/modules/platform-identity/routes/projects.js +22 -15
- package/modules/platform-identity/routes/sso-invites.js +82 -0
- package/modules/platform-identity/tests/platform-identity.mjs +14 -11
- package/modules/public-landing/public/assets/cosmos.css +5 -0
- package/modules/public-landing/public/projects.html +35 -29
- package/modules/public-landing/public/projects.states.json +1 -0
- package/modules/ui-design/kit/serve.js +9 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/gds/run-unit-tests.js +4 -0
- package/src/bongos/auth-admission.js +40 -0
- package/src/bongos/auth.js +2 -1
- package/src/module-api.js +8 -1
- package/tests/access_requests_invite.mjs +39 -1
- package/tests/application_lifecycle.mjs +41 -1
- package/tests/catalog_only_client.mjs +5 -3
- package/tests/guild_map_db.mjs +157 -0
- package/tests/guilds_api.mjs +74 -0
- package/tests/hub_invite_notices.mjs +261 -0
- package/tests/module_api.mjs +1 -0
- package/tests/my_projects_surface.mjs +11 -1
- package/tests/project_invite_ownership.mjs +12 -7
- package/tests/project_invite_ui.mjs +42 -45
- package/tests/watch_applications_queue.mjs +33 -2
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# ADR 0353 — A hub invite is a notice of the project's own invite, and signing in accepts it
|
|
2
|
+
|
|
3
|
+
- **Status:** accepted
|
|
4
|
+
- **Date:** 2026-09-30
|
|
5
|
+
- **Task:** [task 1002818](https://cloudbongos.com/builders#/task/1002818) (R6 of the project admin console wave, goal 1000110 — BONGOS-V2)
|
|
6
|
+
- **Builds on:** [ADR 0335](0335-an-invite-is-a-pre-approved-row-on-the-projects-own-instance.md) D1/D2 (authority lives on the project's own instance; an invite is a pre-approved `access_requests` row; signing in is the acceptance act)
|
|
7
|
+
- **Interacts with:** [ADR 0141](<redacted>.md) §3 (membership check-in), [ADR 0205](0205-federated-checkin-needs-the-hubs-own-signin-witness.md) (client credentials never mint a membership), [ADR 0352](0352-a-rollup-report-carries-its-as-of-in-a-header.md) (hub↔instance wire skew)
|
|
8
|
+
- **Decided with:** the owner chose the split in D3 on 2026-09-30, over building the signed-assertion relay in this task.
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
ADR 0335 left one dead end that real users could reach. The hub had its own invite, `POST /projects/invite`, called from the creation wizard's invite step and from a project's manage page. It wrote a `pending` row in `<redacted>`, and accepting it (`POST /my-projects/invites/:clientId/accept`) flipped that row to `member`. No project reads that table to decide who may enter. So the invitee "accepted" and then met the project's own sign-in gate, which knew nothing about them. A hosted project's join door defaults to `apply`, so this was the common case, not an edge case.
|
|
13
|
+
|
|
14
|
+
The project's own invite (`POST /access-requests/invite`, R2) does admit: it writes the `invited` row the sign-in gate reads. But nothing told the invitee it existed, and rescinding it left nothing to clean up on the hub because the hub had never heard of it.
|
|
15
|
+
|
|
16
|
+
The task's done-when: **no state where an accepted invite leads to a locked door.**
|
|
17
|
+
|
|
18
|
+
## Decision
|
|
19
|
+
|
|
20
|
+
### D1 — The hub's `pending` row is a NOTICE of the project's own invite
|
|
21
|
+
|
|
22
|
+
The row stays, and My Projects lists it as before, but only the project writes it now. Two new server-to-server routes, authenticated by `client_id` + `client_secret` like the membership check-in:
|
|
23
|
+
|
|
24
|
+
| Route | When the project calls it | What the hub does |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `POST /sso/invites/notify` | every outcome of its invite route that leaves the login holding an `invited` row: a fresh invite, a pending application approved in place, and a re-invite of one already standing | `INSERT … 'pending' ON CONFLICT DO NOTHING` for the account that login resolves to |
|
|
27
|
+
| `POST /sso/invites/clear` | a dismissal after which the login holds no `invited` row | `DELETE` that `pending` row, and only a `pending` one |
|
|
28
|
+
|
|
29
|
+
Both calls touch a `pending` row only, so client credentials still cannot create, change or remove a membership (ADR 0205). A login with no hub account is a no-op, because there is nobody on the hub to notify; the project's row still admits them. **The answer is always `{ ok: true }` once the client authenticates**, whether or not anything was written, so a project cannot use the route to learn which GitHub logins have platform accounts.
|
|
30
|
+
|
|
31
|
+
The sender is core's `notifyHubOfInvite` (`src/bongos/auth-admission.js`). It sits in the kernel beside the other hub calls that hold the client secret, and modules reach it as a narrow doorway port. It is fire-and-forget, time-bounded and swallows every failure: an invite must never wait on the hub or fail because of it. Re-inviting is how a notice the hub missed is healed, since the hub's insert is idempotent.
|
|
32
|
+
|
|
33
|
+
Only a dismissal that leaves no `invited` row clears the notice. If two archons invited the same login and one rescinds, the other invite still admits, so the notice stays. A declined application also sends a clear. That is a no-op unless the hub still holds a pre-ADR-0353 row for that login, which would be a dead end anyway.
|
|
34
|
+
|
|
35
|
+
### D2 — Accepting is signing in to the project; the hub-side accept is retired
|
|
36
|
+
|
|
37
|
+
`POST /my-projects/invites/:clientId/accept` is deleted, and `resolveInvite` becomes the decline-only `removePendingInvite`. The hub page has not called accept since the invitations list came back (task 1003774): each invitation renders as **Open project →** with "Sign in there to accept." What was missing was the server half, so no caller (an old cached page, a script) can still flip a row and admit nobody.
|
|
38
|
+
|
|
39
|
+
Signing in closes the loop without any new code. The hub's own sign-in witness (`recordFederatedSignin`, stamped at `/sso/token` and `/sso/device/poll`) already turns a `pending` row into `member` when the invitee signs in to that project.
|
|
40
|
+
|
|
41
|
+
### D3 — The hub's own invite writes nothing; it hands the owner to the project's hall
|
|
42
|
+
|
|
43
|
+
`POST /projects/invite` keeps its owner-or-curator gate and its byte-identical 403, and it still resolves the catalog client. It no longer writes. It answers `200 { invited: false, invite_url }`: the project's hall invite box with the login filled in, `<origin>/builders/watch?invite=<login>`. A 200 rather than a refusal, because it is the answer every admitted caller gets, and a 4xx on that path would log a failed request in the owner's browser on every invite. Every instance already canonicalises `/builders/watch` to its hall (a one-host instance serves `/watch`, a two-host one redirects to `builders.<apex>/watch`, both keeping the query), so the hub needs no hall address of its own. The sign-in redirect carries the full URL, so the name survives signing in.
|
|
44
|
+
|
|
45
|
+
The wizard and manage-page widget keep their field, their suggestions and their Skip button. Each name becomes a line reading "not sent yet — finish it on your project's hall" with a **Send the invite →** link. It is a link the owner clicks, never a window opened for them: an open triggered by a network answer is blocked by browsers, and a surprise navigation would lose the wizard.
|
|
46
|
+
|
|
47
|
+
The hall's Watch page reads `?invite=` once, only if it is a valid GitHub username, and **only fills the box**. Sending stays the owner's click, because a link anyone can craft must never be the act that admits.
|
|
48
|
+
|
|
49
|
+
The owner chose this over building the relay ADR 0335 foresaw (see Alternatives) in this task.
|
|
50
|
+
|
|
51
|
+
### D4 — Wire skew (ADR 0352's rule)
|
|
52
|
+
|
|
53
|
+
The two notice routes are **new**. An older hub answers 404, the sender swallows it, and the invite still admits: only the notice is lost. A newer hub with an older instance simply receives no notices. Each route's body is validated strictly (three fields), so a field added later must ride a header, or ship on the hub and be released before any instance sends it.
|
|
54
|
+
|
|
55
|
+
## Consequences
|
|
56
|
+
|
|
57
|
+
- **The done-when holds for every invite created from now on.** A hub notice exists only because the project wrote an `invited` row, and a rescind that closes the door removes the notice. The hub's own invite creates no row at all.
|
|
58
|
+
- **Rows written before this change are untouched.** Pending rows the old hub invite wrote still render as **Open project →**, and on a door other than `open` they still lead to the project's gate. The invitee can decline them, and the project can clear one by inviting and then rescinding. Deleting them at deploy would need a data migration, and it would also delete notices that do work (on `open` doors). That is left to the owner (see the task's handoff), not done silently here.
|
|
59
|
+
- **The hub's invite now reaches people with no platform account.** The project's own invite takes any GitHub login, so the old `account_not_found` refusal is gone. Such an invitee sees no hub notice, because there is no account to show it to, but they are admitted when they sign in.
|
|
60
|
+
- **Three things still bypass the notice**, all by scope: the reviewer queue's approve (`PATCH` pending → invited), the Discord approval reaction, and R3's future recruiter rescind route. The first two are approvals of an application the applicant already knows about. R3 (task 1002815) must call the same port when it adds its rescind.
|
|
61
|
+
- **The 30-day invite expiry** (task 1002969) is read-time on the instance. An expired invite's hub notice stays until the invitee declines it or the project re-invites and rescinds. Task 1002969 owns closing that.
|
|
62
|
+
|
|
63
|
+
## Alternatives considered
|
|
64
|
+
|
|
65
|
+
- **The signed-assertion relay (ADR 0335's foreseen design).** The hub mints a new `purpose: 'invite'` token for the inviter, and a new token-authenticated route on the project checks that builder's own permission and writes the real row. It keeps the in-place form, but it adds an authentication path to every instance (security-sensitive), roughly doubles this task, needs a fallback for every older instance, and cannot work until the owner has signed in to a new project once (there is no local builder to check the permission against). The owner deferred it. D3's hand-off leaves room for it: if it is built, the route's `invited: false` becomes `true` for instances that support it.
|
|
66
|
+
- **Seed the hub row only when the project's door is `open`.** An owner who later closes the door strands every such notice in front of a locked door, and a self-hosted project's door is not known to the hub reliably.
|
|
67
|
+
- **Keep the hub-side accept, but have the instance read hub memberships at sign-in.** This puts a hub call inside the admission gate and moves a verdict onto the hub, which ADR 0335 D1 forbids.
|
|
68
|
+
- **A body field on the membership check-in instead of new routes.** The check-in body is validated strictly, so an older hub would 400 the whole check-in (ADR 0352), and a check-in is sent at sign-in, not at invite time.
|
|
69
|
+
|
|
70
|
+
Proof: `tests/hub_invite_notices.mjs` (the hub routes over the real domain functions: pending-only writes keyed on the authenticated client, the identical answer for an unknown login, the credential refusals, the three-field body, the hand-off link, and the sender), `tests/access_requests_invite.mjs` and `tests/application_lifecycle.mjs` (which invite outcomes notify and which dismissals clear), `tests/project_invite_ownership.mjs` and `tests/catalog_only_client.mjs` (the gate is unchanged and nothing is written), `tests/project_invite_ui.mjs` (the hand-off line, no non-http link, no window opened), `tests/watch_applications_queue.mjs` (the prefill fills and never sends), `tests/my_projects_surface.mjs` and `modules/platform-identity/tests/platform-identity.mjs` (the accept route and flip are gone).
|
package/docs/adr/README.md
CHANGED
|
@@ -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 |
|
package/docs/api/openapi.json
CHANGED
|
@@ -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": [
|
|
@@ -8590,6 +8590,32 @@
|
|
|
8590
8590
|
}
|
|
8591
8591
|
},
|
|
8592
8592
|
"/guilds": {
|
|
8593
|
+
"get": {
|
|
8594
|
+
"operationId": "get_guilds",
|
|
8595
|
+
"tags": [
|
|
8596
|
+
"guilds"
|
|
8597
|
+
],
|
|
8598
|
+
"summary": "GET /guilds",
|
|
8599
|
+
"description": "the guild map (?order=size|shipped, default size; ?limit=&offset=). Viewer-independent: it reads no session. size is the public roster's length; shipped is R26's stored band floor (ADR 0337 D4.4), never an exact sum, null before a guild's first refresh. no-store: a hide or a visibility flip moves size on the very next read (ADR 0171 D5).\n\n**Rank:** `public` — No authentication — any caller.",
|
|
8600
|
+
"x-rank": "public",
|
|
8601
|
+
"x-source": "modules/platform-identity/routes/guilds-public.js",
|
|
8602
|
+
"responses": {
|
|
8603
|
+
"200": {
|
|
8604
|
+
"description": "Success.",
|
|
8605
|
+
"content": {
|
|
8606
|
+
"application/json": {
|
|
8607
|
+
"schema": {
|
|
8608
|
+
"$ref": "#/components/schemas/GetGuildsResponse"
|
|
8609
|
+
}
|
|
8610
|
+
}
|
|
8611
|
+
}
|
|
8612
|
+
},
|
|
8613
|
+
"400": {
|
|
8614
|
+
"$ref": "#/components/responses/BadRequest"
|
|
8615
|
+
}
|
|
8616
|
+
},
|
|
8617
|
+
"security": []
|
|
8618
|
+
},
|
|
8593
8619
|
"post": {
|
|
8594
8620
|
"operationId": "post_guilds",
|
|
8595
8621
|
"tags": [
|
|
@@ -13168,58 +13194,6 @@
|
|
|
13168
13194
|
]
|
|
13169
13195
|
}
|
|
13170
13196
|
},
|
|
13171
|
-
"/my-projects/invites/{clientId}/accept": {
|
|
13172
|
-
"post": {
|
|
13173
|
-
"operationId": "post_my_projects_invites_clientId_accept",
|
|
13174
|
-
"tags": [
|
|
13175
|
-
"my-projects"
|
|
13176
|
-
],
|
|
13177
|
-
"summary": "POST /my-projects/invites/:clientId/accept",
|
|
13178
|
-
"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).",
|
|
13179
|
-
"x-rank": "any-builder",
|
|
13180
|
-
"x-source": "modules/platform-identity/routes/my-projects.js",
|
|
13181
|
-
"parameters": [
|
|
13182
|
-
{
|
|
13183
|
-
"name": "clientId",
|
|
13184
|
-
"in": "path",
|
|
13185
|
-
"required": true,
|
|
13186
|
-
"schema": {
|
|
13187
|
-
"type": "string"
|
|
13188
|
-
},
|
|
13189
|
-
"description": "Path parameter `clientId`."
|
|
13190
|
-
}
|
|
13191
|
-
],
|
|
13192
|
-
"responses": {
|
|
13193
|
-
"200": {
|
|
13194
|
-
"description": "Success.",
|
|
13195
|
-
"content": {
|
|
13196
|
-
"application/json": {
|
|
13197
|
-
"schema": {
|
|
13198
|
-
"$ref": "#/components/schemas/PostMyProjectsInvitesClientIdAcceptResponse"
|
|
13199
|
-
}
|
|
13200
|
-
}
|
|
13201
|
-
}
|
|
13202
|
-
},
|
|
13203
|
-
"400": {
|
|
13204
|
-
"$ref": "#/components/responses/BadRequest"
|
|
13205
|
-
},
|
|
13206
|
-
"401": {
|
|
13207
|
-
"$ref": "#/components/responses/Unauthorized"
|
|
13208
|
-
},
|
|
13209
|
-
"403": {
|
|
13210
|
-
"$ref": "#/components/responses/Forbidden"
|
|
13211
|
-
},
|
|
13212
|
-
"404": {
|
|
13213
|
-
"$ref": "#/components/responses/NotFound"
|
|
13214
|
-
}
|
|
13215
|
-
},
|
|
13216
|
-
"security": [
|
|
13217
|
-
{
|
|
13218
|
-
"builderSession": []
|
|
13219
|
-
}
|
|
13220
|
-
]
|
|
13221
|
-
}
|
|
13222
|
-
},
|
|
13223
13197
|
"/my-projects/invites/{clientId}/decline": {
|
|
13224
13198
|
"post": {
|
|
13225
13199
|
"operationId": "post_my_projects_invites_clientId_decline",
|
|
@@ -13975,7 +13949,7 @@
|
|
|
13975
13949
|
"projects"
|
|
13976
13950
|
],
|
|
13977
13951
|
"summary": "POST /projects/invite",
|
|
13978
|
-
"description": "own-or-metic in effect: invite a builder into a project
|
|
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).",
|
|
13979
13953
|
"x-rank": "metic+archon",
|
|
13980
13954
|
"x-source": "modules/platform-identity/routes/projects.js",
|
|
13981
13955
|
"x-permissions": [
|
|
@@ -13994,7 +13968,14 @@
|
|
|
13994
13968
|
},
|
|
13995
13969
|
"responses": {
|
|
13996
13970
|
"200": {
|
|
13997
|
-
"description": "Success."
|
|
13971
|
+
"description": "Success.",
|
|
13972
|
+
"content": {
|
|
13973
|
+
"application/json": {
|
|
13974
|
+
"schema": {
|
|
13975
|
+
"$ref": "#/components/schemas/PostProjectsInviteResponse"
|
|
13976
|
+
}
|
|
13977
|
+
}
|
|
13978
|
+
}
|
|
13998
13979
|
},
|
|
13999
13980
|
"400": {
|
|
14000
13981
|
"$ref": "#/components/responses/ValidationFailed"
|
|
@@ -18658,6 +18639,70 @@
|
|
|
18658
18639
|
"security": []
|
|
18659
18640
|
}
|
|
18660
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
|
+
},
|
|
18661
18706
|
"/sso/membership/check-in": {
|
|
18662
18707
|
"post": {
|
|
18663
18708
|
"operationId": "post_sso_membership_check_in",
|
|
@@ -22551,6 +22596,21 @@
|
|
|
22551
22596
|
"builders"
|
|
22552
22597
|
]
|
|
22553
22598
|
},
|
|
22599
|
+
"GetGuildsResponse": {
|
|
22600
|
+
"type": "object",
|
|
22601
|
+
"properties": {
|
|
22602
|
+
"order": {},
|
|
22603
|
+
"count": {},
|
|
22604
|
+
"results": {},
|
|
22605
|
+
"page": {}
|
|
22606
|
+
},
|
|
22607
|
+
"required": [
|
|
22608
|
+
"order",
|
|
22609
|
+
"count",
|
|
22610
|
+
"results",
|
|
22611
|
+
"page"
|
|
22612
|
+
]
|
|
22613
|
+
},
|
|
22554
22614
|
"GetGuildsSlugMembersResponse": {
|
|
22555
22615
|
"type": "object",
|
|
22556
22616
|
"properties": {
|
|
@@ -27248,21 +27308,6 @@
|
|
|
27248
27308
|
"left"
|
|
27249
27309
|
]
|
|
27250
27310
|
},
|
|
27251
|
-
"PostMyProjectsInvitesClientIdAcceptResponse": {
|
|
27252
|
-
"type": "object",
|
|
27253
|
-
"properties": {
|
|
27254
|
-
"ok": {
|
|
27255
|
-
"type": "boolean"
|
|
27256
|
-
},
|
|
27257
|
-
"accepted": {
|
|
27258
|
-
"type": "boolean"
|
|
27259
|
-
}
|
|
27260
|
-
},
|
|
27261
|
-
"required": [
|
|
27262
|
-
"ok",
|
|
27263
|
-
"accepted"
|
|
27264
|
-
]
|
|
27265
|
-
},
|
|
27266
27311
|
"PostMyProjectsInvitesClientIdDeclineResponse": {
|
|
27267
27312
|
"type": "object",
|
|
27268
27313
|
"properties": {
|
|
@@ -27359,6 +27404,19 @@
|
|
|
27359
27404
|
],
|
|
27360
27405
|
"additionalProperties": false
|
|
27361
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
|
+
},
|
|
27362
27420
|
"PostProjectsProjectIdSpecialitiesRequest": {
|
|
27363
27421
|
"type": "object",
|
|
27364
27422
|
"properties": {
|
|
@@ -28411,6 +28469,54 @@
|
|
|
28411
28469
|
],
|
|
28412
28470
|
"additionalProperties": false
|
|
28413
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
|
+
},
|
|
28414
28520
|
"PostSsoMembershipCheckInRequest": {
|
|
28415
28521
|
"type": "object",
|
|
28416
28522
|
"properties": {
|
|
@@ -29523,9 +29629,9 @@
|
|
|
29523
29629
|
"description": "A required dependency/feature is not configured or is temporarily down."
|
|
29524
29630
|
}
|
|
29525
29631
|
},
|
|
29526
|
-
"x-endpoint-count":
|
|
29527
|
-
"x-schema-count":
|
|
29632
|
+
"x-endpoint-count": 463,
|
|
29633
|
+
"x-schema-count": 496,
|
|
29528
29634
|
"x-undocumented-bodies": 12,
|
|
29529
|
-
"x-response-schemas":
|
|
29635
|
+
"x-response-schemas": 336,
|
|
29530
29636
|
"x-generated-by": "scripts/gds/gen-api-docs.js"
|
|
29531
29637
|
}
|
package/docs/api-reference.md
CHANGED
|
@@ -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.
|
|
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`.
|
|
@@ -342,10 +342,11 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
|
|
|
342
342
|
| POST | `/api/bongos/guild-requests/:id/accept` | `any-builder` | `visibility`, `counted_in_totals` | accept an invite addressed to ME, or (as the guild's owner) a request to join it. |
|
|
343
343
|
| POST | `/api/bongos/guild-requests/:id/decline` | `any-builder` | — | decline an invite to ME, or (as owner) a request to my guild. |
|
|
344
344
|
|
|
345
|
-
## `guilds` (
|
|
345
|
+
## `guilds` (13)
|
|
346
346
|
|
|
347
347
|
| Method | Path | Rank | Body | Description |
|
|
348
348
|
|---|---|---|---|---|
|
|
349
|
+
| GET | `/api/bongos/guilds` | `public` | — | the guild map (?order=size\|shipped, default size; ?limit=&offset=). |
|
|
349
350
|
| POST | `/api/bongos/guilds` | `any-builder` | `slug`, `name`, `description`, `visibility` | create a guild. |
|
|
350
351
|
| GET | `/api/bongos/guilds/:slug` | `public` | — | a public guild's page data. |
|
|
351
352
|
| PATCH | `/api/bongos/guilds/:slug` | `any-builder` | `name`, `description`, `visibility` | owner: edit name, description or visibility. |
|
|
@@ -505,14 +506,13 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
|
|
|
505
506
|
| POST | `/api/bongos/modules/:key/submit` | `metic+archon` | `signed_by` | POST /api/bongos/modules/:key/submit — ADR 0107 §1 upstreaming submission. |
|
|
506
507
|
| GET | `/api/bongos/modules/submissions` | `metic+archon` | — | GET /api/bongos/modules/submissions?status=&module_key= — the upstreaming review queue (task 1771 reads this). |
|
|
507
508
|
|
|
508
|
-
## `my-projects` (
|
|
509
|
+
## `my-projects` (5)
|
|
509
510
|
|
|
510
511
|
| Method | Path | Rank | Body | Description |
|
|
511
512
|
|---|---|---|---|---|
|
|
512
513
|
| GET | `/api/bongos/my-projects` | `any-builder` | — | the caller's OWN cross-project memberships (own data). |
|
|
513
514
|
| POST | `/api/bongos/my-projects/:clientId/leave` | `any-builder` | — | leave a project from the caller's OWN membership list. |
|
|
514
515
|
| GET | `/api/bongos/my-projects/invites` | `any-builder` | — | the caller's OWN pending invitations (ADR 0141 §5). |
|
|
515
|
-
| POST | `/api/bongos/my-projects/invites/:clientId/accept` | `any-builder` | — | accept an invite (pending → builder) for the caller's OWN account. |
|
|
516
516
|
| POST | `/api/bongos/my-projects/invites/:clientId/decline` | `any-builder` | — | decline an invite (delete the pending row) for the caller's OWN account. |
|
|
517
517
|
| POST | `/api/bongos/my-projects/join` | `any-builder` | `origin`, `note` | request to join a project. |
|
|
518
518
|
|
|
@@ -558,7 +558,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
|
|
|
558
558
|
| PATCH | `/api/bongos/projects/:id/status` | `metic+archon` | `status` | THE TAKEDOWN LEVER (R08, task 1002327; ADR 0252 §5.1). |
|
|
559
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. |
|
|
560
560
|
| GET | `/api/bongos/projects/featured` | `public` | — | the public map of projects; no auth by design. |
|
|
561
|
-
| POST | `/api/bongos/projects/invite` | `metic+archon` | `client_id`, `github_login` | own-or-metic in effect: invite a builder into a project
|
|
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… |
|
|
562
562
|
|
|
563
563
|
## `provisioning` (34)
|
|
564
564
|
|
|
@@ -718,7 +718,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
|
|
|
718
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… |
|
|
719
719
|
| GET | `/api/bongos/specialities/offered` | `any-builder` | — | GET /specialities/offered — the suggestions for THIS builder. |
|
|
720
720
|
|
|
721
|
-
## `sso` (
|
|
721
|
+
## `sso` (10)
|
|
722
722
|
|
|
723
723
|
| Method | Path | Rank | Body | Description |
|
|
724
724
|
|---|---|---|---|---|
|
|
@@ -727,6 +727,8 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
|
|
|
727
727
|
| GET | `/api/bongos/sso/authorize` | `public` | — | GET /sso/authorize?client_id=&redirect_uri=&state= — the browser authorize leg. |
|
|
728
728
|
| POST | `/api/bongos/sso/device/poll` | `public` | `device_code` | POST /sso/device/poll — poll the hub device flow. |
|
|
729
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. |
|
|
730
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). |
|
|
731
733
|
| GET | `/api/bongos/sso/pubkey` | `public` | — | GET /sso/pubkey — the hub's Ed25519 assertion-verification key (public). |
|
|
732
734
|
| POST | `/api/bongos/sso/token` | `public` | `client_id`, `client_secret`, `code`, `redirect_uri` | POST /sso/token — server-to-server code redemption. |
|