@bongos/core 1.19.374 → 1.19.376

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 (56) hide show
  1. package/.bongos-core.json +99 -54
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +4 -0
  4. package/clients/bongos-client/index.cjs +4 -0
  5. package/clients/bongos-client/index.d.ts +9 -1
  6. package/clients/bongos-client/index.mjs +4 -0
  7. package/docs/adr/0194-joinability-one-door-decision.md +42 -0
  8. package/docs/adr/README.md +1 -0
  9. package/docs/api/openapi.json +158 -5
  10. package/docs/api-reference.md +6 -4
  11. package/docs/copy-inventory.md +373 -356
  12. package/docs/copy-registry.json +646 -452
  13. package/migrations/core_227_task_created_by.sql +83 -0
  14. package/modules/dev-box/app/src/vendor/bongos-client.cjs +4 -0
  15. package/modules/government/catalog.js +1 -1
  16. package/modules/hall-ui/public/builders.js +5 -3
  17. package/modules/hall-ui/public/goals-lib.js +5 -5
  18. package/modules/hall-ui/public/goals-page.js +54 -23
  19. package/modules/hall-ui/public/goals-render.js +15 -0
  20. package/modules/hall-ui/public/hall-render.js +63 -22
  21. package/modules/hall-ui/public/my-projects.js +6 -1
  22. package/modules/hall-ui/public/task-lib.js +29 -0
  23. package/modules/hall-ui/public/watch.css +8 -0
  24. package/modules/hall-ui/public/watch.js +56 -3
  25. package/modules/hall-ui/public/work.js +117 -5
  26. package/modules/ideas/inbox.js +4 -0
  27. package/modules/lifecycle/db-tasks.js +61 -5
  28. package/modules/lifecycle/goal-rollup.js +118 -0
  29. package/modules/lifecycle/routes/goals.js +26 -0
  30. package/modules/lifecycle/routes/task-write-routes.js +3 -0
  31. package/modules/lifecycle/routes/tasks.js +47 -1
  32. package/modules/onboarding/module.json +1 -1
  33. package/modules/onboarding/routes/access-requests.js +87 -1
  34. package/modules/platform-identity/routes/my-projects.js +35 -9
  35. package/package-lock.json +2 -2
  36. package/package.json +1 -1
  37. package/public-landing/projects.html +169 -3
  38. package/scripts/gds/login.js +24 -3
  39. package/src/bongos/auth-admission.js +50 -5
  40. package/src/bongos/auth.js +2 -1
  41. package/src/bongos/routes/auth.js +15 -0
  42. package/src/branding.js +3 -2
  43. package/src/module-api.js +22 -1
  44. package/tests/access_requests_invite.mjs +165 -0
  45. package/tests/auth_admission_gate.mjs +42 -0
  46. package/tests/goal_sort_staleness.mjs +286 -0
  47. package/tests/goal_subtitle.mjs +6 -1
  48. package/tests/goals_lib.mjs +16 -4
  49. package/tests/join_relay_outcome.mjs +61 -0
  50. package/tests/joinability_apply.mjs +72 -0
  51. package/tests/joinability_invite_only.mjs +192 -0
  52. package/tests/joinability_open.mjs +89 -0
  53. package/tests/module_api.mjs +1 -0
  54. package/tests/projects_hub.mjs +76 -2
  55. package/tests/projects_hub_pre_uat.mjs +4 -2
  56. package/tests/public_landing_projects.mjs +10 -3
@@ -0,0 +1,42 @@
1
+ # ADR 0194 — A project's membership door is one decision, answered the same way everywhere a stranger meets it
2
+
3
+ - **Status:** accepted
4
+ - **Date:** 2026-08-25
5
+ - **Task:** [task 1003044](https://cloudbongos.com/builders#/task/1003044) (goal 1000058 — the non-cloudbongos project lifecycle)
6
+ - **Relates to:** ADR 0050 (invite-gated by default; the `OPEN_XENOS_ENROLLMENT` escape hatch), ADR 0190 (the settings channel: `brand.project` as the read-back), ADR 0192 (the platform-visibility member door — this setting's sibling), ADR 0116 (the error envelope), ADR 0083 (the module doorway), ADR 0161 (a MINOR is a human edit), task 1003040 (the settings row), task 1003043 (the manage-page section shape)
7
+
8
+ ## Context
9
+
10
+ The owner decided on 2026-08-16 that a project chooses how people join it — **open** (whoever signs in is in), **apply** (a stranger files an access request, an Archon invites; today's behaviour), or **invite only** (no applications; only an Archon's invite admits). Task 1003040 stored the choice on the platform's settings row, 1003139/1003140 pushed it into the instance's environment, and `branding().project.joinability` resolved it — enforced by nothing, and the manage page said so.
11
+
12
+ Admission on an instance is decided in one place already: `firstTimeAdmissionBlocked` (SR-19 — one pure decision both sign-in flows call, so they cannot drift), fed by `openEnrollmentEnabled()`, which read only the legacy env flag. Four sign-in paths call it (the GitHub web and device flows, and their federated twins). Beside it sit the onboarding module's public `POST /access-requests` (the one unauthenticated write), the Archon's `PATCH … invited`, the hall's landing page with its request form, the web sign-in's 403 page, the CLI's stranger path, and the hub's join relay — each of which tells a stranger what to do next.
13
+
14
+ ## Decision
15
+
16
+ 1. **One reader, and open enrolment follows it.** `joinabilityMode()` (`src/bongos/auth-admission.js`) is the project's effective door: `branding().project.joinability` when it says `open` or `invite_only`; otherwise `apply` — **unless** the legacy `OPEN_XENOS_ENROLLMENT` flag is set, which still turns an *apply* project into an open one. `openEnrollmentEnabled()` becomes `joinabilityMode() === 'open'`, so the four sign-in call sites, `webAdmissionStatus`, and the manifest's `admission.policy` follow without being touched. Why the flag keeps its power on `apply` only: a self-hosted instance that set it (ADR 0050) must keep its open door across the upgrade — the neutral pack defaults to `apply`, so "flag ignored" would have silently closed those doors; and an explicit `invite_only` is the stricter, newer choice, which the flag must not reopen. Why the flag is not folded into `branding()`: the pack is memoized for the process while the flag is read per call (and toggled by the existing tests), and the manifest's `brand.project.joinability` must stay the *pushed* value the hub reads back. The one case where the two disagree — a self-hosted instance carrying the flag — is never platform-managed: its hall landing offers the request form (the injected pack says `apply`), and the form collapses into "no request needed — sign in" on the door's own 409; an owner who wants the hall to say so up front sets `<PREFIX>_JOINABILITY=open`. The flag is kept only so nothing changes on upgrade.
17
+
18
+ 2. **An unreadable pack, or a value this core cannot read, fails closed: `invite_only`.** When the door's policy is unknown — the pack will not load, or it says `invite-only` with a hyphen, or a truncated write — the widest door is never the fallback, the same rule the visibility gate applies (ADR 0192 §2). Only an absent value or `apply` reaches the legacy-flag branch. Logged through the kernel logger, an unknown value once per process.
19
+
20
+ 3. **The public `POST /access-requests` answers the door first** — before validating the body and before any lookup. On `invite_only` it refuses `403 invite_only`; on `open` it refuses `409 open_enrollment` ("no request needed — sign in"). Both are plain sentences in the ADR 0116 envelope. Answering first costs no query, reveals nothing about who already exists (the account-existence oracle ADR 0192 §3 deferred does not widen), and keeps the refusal DB-free — which is what lets the real booted app prove it. A non-2xx for `open` rather than a friendly 200: an older `bongos login` would otherwise print "✓ Requested access" and wait thirty minutes for an approval that never comes; a refusal it does not recognise prints its status and stops, which is honest.
21
+
22
+ 4. **An Archon's invite is the same row the gate already reads.** `POST /access-requests/invite { github_login }` (rank archon — the gate that resolves a request) writes an `access_requests` row at `'invited'` — resolving a pending one in place, or inserting one. `hasInvitedAccessRequest` admits it at sign-in with nothing new consulted. No second table, no token, no expiry: an invite here is exactly what the `PATCH … invited` has meant since ADR 0050, created before the request instead of after. It reaches the hall as an "Invite by GitHub username" form in the Watch page's Access-requests section, whose one-line explanation names the door the project runs.
23
+
24
+ 5. **The door is said in every place a stranger meets it, in the project's own words.** The hall landing renders one door per mode from the pack the server injects (`window.__BRANDING__.project` — no fetch, no flash); its request form collapses into "sign in" on the door's own refusal codes. The web sign-in's 403 page names the invite-only door and the one way in instead of sending a stranger to a form the project refuses. The CLI says invite-only and stops instead of collecting a username. The hub's join relay names the door in the hub's own words (`joinRelayOutcome`, pure) — a registered project is trusted to build, not to write prose into the platform's UI — rather than painting a refusal as "requested", or as "could not be reached", which a 403 used to become; a 401 (the project's member-only door, ADR 0192, refusing the ask itself) is said as such, and the hub's join box keeps the typed address and does not repaint as if a request went out. Someone who applied before a project switched to invite-only stays in the queue — the Archon can still invite or dismiss them from the Watch page, and the manage fold says so.
25
+
26
+ 6. **The manifest's `admission.policy` stays binary.** It is a pinned public field derived from `openEnrollmentEnabled()`, so it agrees with the new decision for free (`open` → `open`, else `invite-only`, which is true of both `apply` and `invite_only`: you need an invite). The three-valued truth is `brand.project.joinability`, which the hub's "runs as" line already reads.
27
+
28
+ 7. **`joinabilityMode` joins the doorway — and rides the next CI patch, not a hand-bumped 1.20.0.** The onboarding module must refuse with the *same* decision the sign-in gate makes; re-deriving it from `api.branding()` would copy the flag rule and the fail-closed default into a second place — the drift SR-19 exists to prevent. An added export is a statement about the doorway surface, which ADR 0161 §2 reserves as a human MINOR (the 1.19.0 precedent). This task tried exactly that, and the publish lane refused it: `tests/bump_version.mjs`'s LIVE test asserts HEAD's version is one the lane's own carrier commits announced, so a branch declaring `1.20.0` fails the `unit` check by construction (1.19.0 was "bumped and never packaged" for the same reason). Landing hands-free on CI (ADR 0162) outranks expressing the MINOR in the number: the version stays the carrier's, the changelog says why, `modules/onboarding/module.json` floors at `^1.19.374`, and the contradiction between ADR 0161 §2's promise and the lane's test is filed as an idea for the owner.
29
+
30
+ 8. **The hub's section clones `#mVisibility`'s shape rather than extracting a factory.** Task 1003043's tests pin that block's text function by function; two settings sharing `visPushOpen()` is tolerable duplication. A third setting is the point at which a factory — and one manifest read serving every key — pays for itself.
31
+
32
+ ## What was deliberately not built
33
+
34
+ - No change to the Discord `#member-approvals` broadcast (an invite-only project simply never posts one, because nothing is filed).
35
+ - No widening of `admission.policy`; no projection of the manifest (idea 1000845); no throttling of the admission-status lookup (ADR 0192 §3).
36
+ - No invite expiry, revocation, or token. An Archon can dismiss nothing they created here — the row is the audit trail, as it is for a resolved request.
37
+
38
+ ## Consequences
39
+
40
+ - A project's door is enforced server-side wherever admission is decided, from one reader, and every surface that speaks to a stranger says which door it is.
41
+ - The hall's `postJSON` now reads the envelope (`error.code` / `error.message`); its already-a-member branch had silently never fired since the envelope landed, and the new refusal codes needed the same read.
42
+ - A self-hosted instance on the legacy flag is unchanged. A platform project defaults to `apply`, unchanged. Only a pushed `open` or `invite_only` changes behaviour, and only after the restart the runner performs (ADR 0190).
@@ -271,5 +271,6 @@ This keeps the decision history honest and traceable.
271
271
  | 0192 | [**A gated project refuses strangers at the app's front door, and the hub reads what the project runs from the project itself** ([task 1003043](https://cloudbongos.com/builders#/task/1003043), goal 1000058). Task 1003040 gave every project a `platform_visibility` setting and 1003139/1003140 carried it into the instance's `branding().project.platformVisibility` — resolved, enforced by nothing. Decision: ONE app-level middleware (`src/bongos/platform-visibility-gate.js`), mounted beside the pre-launch gate ahead of every human and public-read surface — the landing, the hub pages, the status host, the hall shell, `/public/*` and every module's `rank: public` route — rather than per-surface wiring, which misses the next surface nobody told it about. "Member" is a live, non-deactivated session on this instance, the same `resolveSession` lookup `requireBuilder` uses, per request, no cache; a lookup that throws fails CLOSED. The exempt list is the door, the manifest and the probes and nothing wider: `/api/*/auth/*` (a door that cannot be opened locks the members out too), `/api/*/instance` (what a stranger reads to learn how to sign in AND what the runner reads after a settings restart to learn whether the value was consumed — including the push that turns this gate on), `/healthz`, `/version`, `/metrics`; the pre-launch gate's blanket `/api/` exemption is deliberately not copied. A refused page gets a self-contained 401 door (no stylesheet, no branding bundle, no script) with one control — Sign in, returning (relatively) to the page knocked on; a refused API call gets `res.fail('unauthenticated', 401)`, exactly requireBuilder's envelope, which is why it mounts after `attachFail`. A member's answer leaves marked `private, no-store` whatever the route said — the public read API sends `public, max-age`, and a CDN edge would otherwise serve a member's copy to the next stranger. The exempt list covers every API prefix (the versioned contract too), `/downloads/*` and Caddy's TLS ask; a request's session is resolved once (`resolveSession` memoizes per request). The hub's manage page shows the CHOICE from the platform's row and what the project RUNS from the project's own public manifest (`brand.project`), and never confuses the two: `push.message` is rendered verbatim, `saved` moves only the radio, the "runs" line is written only from the manifest and says "doesn't report this setting yet" for a core that predates the channel. Rejected: per-surface wiring; a 302 into OAuth for a refused page (sends assets and crawlers to GitHub); copying the `/api/` exemption; painting the radio from the manifest or the runs line from `saved`; reusing the pre-launch gate.](<redacted>.md) | instance / project visibility |
272
272
  | 0192 | [**A work category orients a human and authorises nothing** ([task 1003275](https://cloudbongos.com/builders#/task/1003275), goal 1000072, criterion `legibility-categories`). A goal gains a human CATEGORY from an owner-named set of eight (Builders Hall, Context Management, Government (Decision Making), Modules, Security, Economics, Legal, User/ABI Experience) so a person scanning the board can tell at a glance which goals are about money, which about the walls, which about the hall — today each goal's own words say so, but only if you read all thirty-eight. The DECISION is the constraint, not the feature: the owner said twice that this organisation has no hierarchical purpose and no effect on the agentic management of the work, and the system ALREADY has a load-bearing hierarchy (ADR 0086 — the scope_modules wall, rank floors, goal membership). The goal's own prose predicts the failure: a second thing that looks like a hierarchy gets mistaken for one within a release, and the mistake arrives as ONE plausible line in a gate ('skip Legal goals in the feed'), after which work is refused because of a label nobody meant as a permission. So: no authority, claim-eligibility, feed or dependency surface may read a category — enforced by the `scripts/gds/category-advisory-guard.js` fitness check, which scans every decision surface with comments stripped (a gate may CITE the rule, never branch on it) AND asserts the feature still exists, so a renamed-away subject fails loudly instead of passing vacuously (the ADR 0022 assurance-of-assurance finding). Uncategorised is LEGAL — nullable, no default, required by no write — because the criterion is explicit that Claude failing to place a goal is a designed prompt, not an error. A task stores no category and INHERITS its goal's through `tasks.goal_id`; a stored copy would be a second answer to one question. The set is a table, not an enum (runtime-extensible without a migration per addition) and retiring is `active = false`, never a DELETE, so a goal keeps rendering a label someone tidied away. The three route-rank pins are a tripwire: if one ever needs a WIDER gate than its neighbours, the label has grown into a permission. Deliberately NOT built: a per-category page or filtered navigation view — a chip is a label, a drill-down destination is a tier. Making a category gate anything is an owner decision and needs its own ADR first.](<redacted>.md) | GDS / goals |
273
273
  | 0193 | [**Pause task-scoped box slices: a box serves its rank scope (the full repo for Metic+)** ([task 1003285](https://cloudbongos.com/builders#/task/1003285), owner decision from the 2026-08-25 dev-box efficiency review). Pauses, does not repeal, ADR 0148 / goal 1000051: `routes/box.js` skips the lifecycle-port `scopeKeys` resolution unless `BOX_TASK_SCOPE=on`, so `decideSourceAccess` takes its documented rank fallback — full repo for Metic+, starter surface below. WHY: the slice was defense-in-depth git cannot hard-enforce (ADR 0031 §6's honest limit) while its costs were real on the live fleet — sessions on a box that cannot see a module (or its nested CLAUDE.md) misdiagnose themselves as stuck, the scope machinery kept failing in the field (task 1002729's base-slice collapse), and every live box user is Metic+ anyway, so the slice narrowed trusted builders while newcomers were already gated by rank. UNCHANGED: live-rank allow/deny per request (ADR 0016), the provision floor and demotion clawback, the read-only per-request credential (ADR 0055 — push still 403s, re-verified on hardware in task 1003253), and sub-Metic starter boxes. `GET /box/scope` now reports `task_scoping_paused` so the hall/app can say why `task_scope` is false; the app task-picker's "scoped to `<modules>` and nothing else" copy overstates while paused (cosmetic). Revert = set the env flag; make-permanent = delete the resolution + a follow-up ADR.](<redacted>.md) | dev-box |
274
+ | 0194 | [**A project's membership door is one decision, answered the same way everywhere a stranger meets it** ([task 1003044](https://cloudbongos.com/builders#/task/1003044), goal 1000058). The owner's 2026-08-16 joinability setting — open / apply / invite_only — was stored (1003040), pushed (1003139/1003140) and resolved into `branding().project.joinability`, enforced by nothing. Decision: ONE reader, `joinabilityMode()` in `auth-admission.js`, and `openEnrollmentEnabled()` becomes `mode === 'open'`, so the four sign-in call sites, `webAdmissionStatus` and the manifest's `admission.policy` follow untouched (SR-19: one gate, no drift). The legacy `OPEN_XENOS_ENROLLMENT` flag still opens an APPLY project — a self-hosted instance that set it keeps its door across the upgrade — but never reopens an explicit invite_only; an unreadable pack or a value this core cannot read fails CLOSED (invite_only), the ADR 0192 rule. The public `POST /access-requests` answers the door FIRST — before validation, before any lookup — `403 invite_only` / `409 open_enrollment` with plain sentences: no query, nothing revealed about who exists, and DB-free enough for the real booted app to prove. An Archon's invite is the row the gate already reads: `POST /access-requests/invite` writes `access_requests` at 'invited' (resolving a pending one in place), so sign-in consults nothing new — surfaced as "Invite by GitHub username" on the Watch page. The door is SAID wherever a stranger meets it, in the project's words: the hall landing renders one door per mode from the injected pack, the web 403 names the invite-only door and the way in, the CLI stops instead of filing, the hub's join relay names the door in the hub's own words (a project is trusted to build, not to write prose into the platform's UI) instead of painting a refusal as "requested" or "unreachable". `admission.policy` stays binary (pinned; agrees for free); `joinabilityMode` joins the doorway — a human MINOR by ADR 0161 §2, which the publish lane's LIVE test refused (HEAD's version must be a CI carrier's), so it rides the next CI patch and the contradiction is filed as an idea. The hub's `#mJoinability` clones `#mVisibility` rather than a factory — a third setting is where a factory pays. Rejected: folding the flag into `branding()` (memoized vs per-call; the manifest must stay the pushed value); a 200 for the open door (an older CLI would wait thirty minutes for an approval); re-deriving the mode inside the module; a second invite table.](<redacted>.md) | instance / project joinability |
274
275
 
275
276
  > ⚠️ **Numbering collision (known):** two ADRs share `0008`. The numbers were assigned in parallel sessions before the lock could be enforced. The files are kept under their existing filenames to preserve git history, but the table disambiguates them as `0008-a` / `0008-b`. New ADRs should resume at `0040` to avoid further collisions. (`0024` is also a real on-disk duplicate: the cloneable-repo/local-first-memory ADR and the multi-agent-system-architecture ADR were both numbered `0024` in parallel; both keep their filenames, disambiguated as `0024-a` / `0024-b` like `0008`. Likewise `0025` ended up with two on-disk files — the offsite-backup ADR and the structured-criterion↔task-link ADR — both kept under their existing filenames and disambiguated as `0025-a` / `0025-b`; separately, the BFG ADR had also claimed `0025` on 2026-05-30 and yielded to `0026`. A third happened 2026-06-06: `0036` was claimed in parallel by the builders-subdomain ADR ([#726](https://example.com/builders#/task/726)) and this Discord-channels ADR ([#727](https://example.com/builders#/task/727)); the Discord-channels ADR yielded to `0037`. A fourth landed 2026-06-10: `0044` was claimed in parallel by the palette-replacement ADR and the per-box game-preview ADR ([#897](https://example.com/builders#/task/897)); both keep their filenames, disambiguated as `0044-a` / `0044-b` like `0008`. A fifth was caught pre-land 2026-06-13: `0050` was claimed in parallel by the device-flow-admission ADR ([#1000](https://example.com/builders#/task/1000)) and the sandbox-for-everyone ADR ([#1001](https://example.com/builders#/task/1001)); the latter yielded to `0052` before merging, so there is no `0050` duplicate. A sixth was caught pre-land 2026-06-21: the tool-agnostic-design-layer ADR ([#1375](https://example.com/builders#/task/1375)) first collided at `0079` with the value-gated-autonomous-engagement ADR, then at `0080` with the llm-cache-api-transport ADR (both part of the 0075–0080 LLM-cost-reduction series, landed on `main` in parallel); it yielded to `0081` before merging, so there is no `0079`/`0080` design-layer duplicate. A seventh landed 2026-06-23: `0084` was claimed in parallel by the auto-derived-per-task-rank-floor ADR and the remove-default-spend-cap ADR (task 1499); the rank-floor ADR landed on `main` first, so the spend-cap ADR yielded to `0085`, leaving no `0084` duplicate. An eighth landed 2026-06-27: `0095` was claimed in parallel by the borrowed-memory-and-retrieval-concepts ADR (PR #459, landed first) and the cross-agent-context-management ADR ([#1651](https://example.com/builders#/task/1651), PR #460); both keep their filenames, disambiguated as `0095-a` / `0095-b` like `0008`. These two are companions — `0095-a` adopts the memory/retrieval concepts and explicitly defers the grep-vs-semantic question to `0095-b`.)
@@ -309,6 +309,58 @@
309
309
  "security": []
310
310
  }
311
311
  },
312
+ "/access-requests/invite": {
313
+ "post": {
314
+ "operationId": "post_access_requests_invite",
315
+ "tags": [
316
+ "access-requests"
317
+ ],
318
+ "summary": "POST /access-requests/invite",
319
+ "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. body: { github_login }. 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).",
320
+ "x-rank": "archon",
321
+ "x-source": "modules/onboarding/routes/access-requests.js",
322
+ "x-permissions": [
323
+ "access_request.review"
324
+ ],
325
+ "requestBody": {
326
+ "required": true,
327
+ "content": {
328
+ "application/json": {
329
+ "schema": {
330
+ "$ref": "#/components/schemas/PostAccessRequestsInviteRequest"
331
+ }
332
+ }
333
+ },
334
+ "x-validated": true
335
+ },
336
+ "responses": {
337
+ "200": {
338
+ "description": "Success.",
339
+ "content": {
340
+ "application/json": {
341
+ "schema": {
342
+ "$ref": "#/components/schemas/PostAccessRequestsInviteResponse"
343
+ }
344
+ }
345
+ }
346
+ },
347
+ "400": {
348
+ "$ref": "#/components/responses/ValidationFailed"
349
+ },
350
+ "401": {
351
+ "$ref": "#/components/responses/Unauthorized"
352
+ },
353
+ "403": {
354
+ "$ref": "#/components/responses/Forbidden"
355
+ }
356
+ },
357
+ "security": [
358
+ {
359
+ "builderSession": []
360
+ }
361
+ ]
362
+ }
363
+ },
312
364
  "/access-requests/status": {
313
365
  "get": {
314
366
  "operationId": "get_access_requests_status",
@@ -4376,6 +4428,44 @@
4376
4428
  ]
4377
4429
  }
4378
4430
  },
4431
+ "/goals/rollups": {
4432
+ "get": {
4433
+ "operationId": "get_goals_rollups",
4434
+ "tags": [
4435
+ "goals"
4436
+ ],
4437
+ "summary": "GET /goals/rollups",
4438
+ "description": "GET /goals/rollups?version= — one small row per goal: readiness counters, the last-activity timestamp, the craft set, and the next claimable task (task 1003278). It replaces the Goals page's whole-version task fetch, which measured 5,229,455 bytes for ~1,400 tasks on 2026-08-25 to produce ~40 counter objects. Registered BEFORE /goals/:id or Express routes 'rollups' to the param. rank: any-builder — the same read as the goal board it feeds.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
4439
+ "x-rank": "any-builder",
4440
+ "x-source": "modules/lifecycle/routes/goals.js",
4441
+ "responses": {
4442
+ "200": {
4443
+ "description": "Success.",
4444
+ "content": {
4445
+ "application/json": {
4446
+ "schema": {
4447
+ "$ref": "#/components/schemas/GetGoalsRollupsResponse"
4448
+ }
4449
+ }
4450
+ }
4451
+ },
4452
+ "400": {
4453
+ "$ref": "#/components/responses/BadRequest"
4454
+ },
4455
+ "401": {
4456
+ "$ref": "#/components/responses/Unauthorized"
4457
+ },
4458
+ "403": {
4459
+ "$ref": "#/components/responses/Forbidden"
4460
+ }
4461
+ },
4462
+ "security": [
4463
+ {
4464
+ "builderSession": []
4465
+ }
4466
+ ]
4467
+ }
4468
+ },
4379
4469
  "/goals/suggestions": {
4380
4470
  "get": {
4381
4471
  "operationId": "get_goals_suggestions",
@@ -12580,7 +12670,7 @@
12580
12670
  "tasks"
12581
12671
  ],
12582
12672
  "summary": "GET /tasks",
12583
- "description": "reads (filtered task list); preserved per CRITICAL §4.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
12673
+ "description": "**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
12584
12674
  "x-rank": "any-builder",
12585
12675
  "x-source": "modules/lifecycle/routes/tasks.js",
12586
12676
  "responses": {
@@ -12635,7 +12725,14 @@
12635
12725
  },
12636
12726
  "responses": {
12637
12727
  "200": {
12638
- "description": "Success."
12728
+ "description": "Success.",
12729
+ "content": {
12730
+ "application/json": {
12731
+ "schema": {
12732
+ "$ref": "#/components/schemas/PostTasksResponse"
12733
+ }
12734
+ }
12735
+ }
12639
12736
  },
12640
12737
  "400": {
12641
12738
  "$ref": "#/components/responses/ValidationFailed"
@@ -15078,6 +15175,15 @@
15078
15175
  "goals"
15079
15176
  ]
15080
15177
  },
15178
+ "GetGoalsRollupsResponse": {
15179
+ "type": "object",
15180
+ "properties": {
15181
+ "rollups": {}
15182
+ },
15183
+ "required": [
15184
+ "rollups"
15185
+ ]
15186
+ },
15081
15187
  "GetGovernmentAssignmentsResponse": {
15082
15188
  "type": "object",
15083
15189
  "properties": {
@@ -16635,6 +16741,36 @@
16635
16741
  },
16636
16742
  "additionalProperties": false
16637
16743
  },
16744
+ "PostAccessRequestsInviteRequest": {
16745
+ "type": "object",
16746
+ "properties": {
16747
+ "github_login": {
16748
+ "type": "string",
16749
+ "minLength": 1,
16750
+ "maxLength": 60
16751
+ }
16752
+ },
16753
+ "required": [
16754
+ "github_login"
16755
+ ],
16756
+ "additionalProperties": false
16757
+ },
16758
+ "PostAccessRequestsInviteResponse": {
16759
+ "type": "object",
16760
+ "properties": {
16761
+ "ok": {
16762
+ "type": "boolean"
16763
+ },
16764
+ "already_invited": {
16765
+ "type": "boolean"
16766
+ },
16767
+ "github_login": {},
16768
+ "request": {}
16769
+ },
16770
+ "required": [
16771
+ "ok"
16772
+ ]
16773
+ },
16638
16774
  "PostAccessRequestsRequest": {
16639
16775
  "type": "object",
16640
16776
  "properties": {
@@ -19892,6 +20028,23 @@
19892
20028
  ],
19893
20029
  "additionalProperties": false
19894
20030
  },
20031
+ "PostTasksResponse": {
20032
+ "type": "object",
20033
+ "properties": {
20034
+ "task": {},
20035
+ "needs_migration": {},
20036
+ "criteria": {},
20037
+ "idempotent": {
20038
+ "type": "boolean"
20039
+ }
20040
+ },
20041
+ "required": [
20042
+ "task",
20043
+ "needs_migration",
20044
+ "criteria",
20045
+ "idempotent"
20046
+ ]
20047
+ },
19895
20048
  "PostVersionsIdDoneWhenRequest": {
19896
20049
  "type": "object",
19897
20050
  "properties": {
@@ -20152,9 +20305,9 @@
20152
20305
  "description": "A required dependency/feature is not configured or is temporarily down."
20153
20306
  }
20154
20307
  },
20155
- "x-endpoint-count": 320,
20156
- "x-schema-count": 353,
20308
+ "x-endpoint-count": 322,
20309
+ "x-schema-count": 357,
20157
20310
  "x-undocumented-bodies": 9,
20158
- "x-response-schemas": 237,
20311
+ "x-response-schemas": 240,
20159
20312
  "x-generated-by": "scripts/gds/gen-api-docs.js"
20160
20313
  }
@@ -2,18 +2,19 @@
2
2
 
3
3
  # Bongos API reference
4
4
 
5
- > **Generated from the live route files** — the route file is authoritative. 320 endpoints across 52 route files.
5
+ > **Generated from the live route files** — the route file is authoritative. 322 endpoints across 52 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`.
9
9
 
10
- ## `access-requests` (4)
10
+ ## `access-requests` (5)
11
11
 
12
12
  | Method | Path | Rank | Body | Description |
13
13
  |---|---|---|---|---|
14
14
  | GET | `/api/bongos/access-requests` | `archon` | — | GET /access-requests?status=pending — Archon queue. |
15
15
  | POST | `/api/bongos/access-requests` | `public` | `github_login`, `display_name`, `note` | POST /access-requests — PUBLIC. |
16
16
  | PATCH | `/api/bongos/access-requests/:id` | `archon` | `status` | PATCH /access-requests/:id — Archon resolves a request. |
17
+ | POST | `/api/bongos/access-requests/invite` | `archon` | `github_login` | POST /access-requests/invite — an Archon admits a GitHub username BEFORE any request exists (task 1003044): the only way in on an invite-… |
17
18
  | GET | `/api/bongos/access-requests/status` | `public` | — | GET /access-requests/status?github_login=X — PUBLIC. |
18
19
 
19
20
  ## `achievements` (1)
@@ -205,7 +206,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
205
206
  | GET | `/api/bongos/github/repos` | `any-builder` | — | acts only on the caller's OWN GitHub identity (the token they granted). |
206
207
  | POST | `/api/bongos/github/repos` | `any-builder` | `name` | creates a repo in the caller's OWN account. |
207
208
 
208
- ## `goals` (27)
209
+ ## `goals` (28)
209
210
 
210
211
  | Method | Path | Rank | Body | Description |
211
212
  |---|---|---|---|---|
@@ -235,6 +236,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
235
236
  | PATCH | `/api/bongos/goals/:id/visibility` | `any-builder` | — | PATCH /goals/:id/visibility — make a goal public\|private (task 1952 / ADR 0112). |
236
237
  | GET | `/api/bongos/goals/inbox` | `any-builder` | — | GET /goals/inbox — the caller's private-goals inbox (task 1952 / ADR 0112): { invitations: invites addressed to me (accept/decline); join… |
237
238
  | POST | `/api/bongos/goals/reorder` | `metic+archon` | `version_id`, `ordered_ids` | POST /goals/reorder — Archon drag-to-reorder: set the priority order of every goal WITHIN one version (task 1760). |
239
+ | GET | `/api/bongos/goals/rollups` | `any-builder` | — | GET /goals/rollups?version= — one small row per goal: readiness counters, the last-activity timestamp, the craft set, and the next claima… |
238
240
  | GET | `/api/bongos/goals/suggestions` | `any-builder` | — | GET /goals/suggestions — "which goal should this draft live in?", answered before anything is created (task 1003062 / R07). |
239
241
 
240
242
  ## `government` (13)
@@ -537,7 +539,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
537
539
 
538
540
  | Method | Path | Rank | Body | Description |
539
541
  |---|---|---|---|---|
540
- | GET | `/api/bongos/tasks` | `any-builder` | — | reads (filtered task list); preserved per CRITICAL §4. |
542
+ | GET | `/api/bongos/tasks` | `any-builder` | — | |
541
543
  | POST | `/api/bongos/tasks` | `metic+archon` | `version_id`, `title`, `description`, `status`, `touches`, `est_minutes`, `est_cost_usd`, `manual_degree`, `priority`, `automation_tag`, `credits_reward`, `source`, `source_ref`, `value_summary`, `kind`, `discipline`, `parallel_safe`, `newcomer_friendly`, `security_sensitive`, `requires_rank`, `auto_detect_touches`, `criterion_ids`, `module_key`, `goal_id` | Task create/promote/edit (plan/scope mutation) is Metic+ as of ADR 0090 (owner decision 2026-06-26): a Metic who can run idea-triage may … |
542
544
  | GET | `/api/bongos/tasks/:id` | `any-builder` | — | single-task read; preserved per CRITICAL §4 (supervisor hot path). |
543
545
  | PATCH | `/api/bongos/tasks/:id` | `metic+archon` | `kind`, `discipline`, `requires_rank`, `module_key`, `goal_id`, `parent_task_id`, `parallel_safe`, `newcomer_friendly`, `xenos_claimable`, `security_sensitive`, `automation_tag`, `needs_migration`, `description`, `status` | PATCH /tasks/:id — supports body.parent_task_id (V2 C2), body.kind (V2 B1), body.discipline (mig 014), body.parallel_safe (mig 028), and … |