@bongos/core 1.19.514 → 1.19.516

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 (38) hide show
  1. package/.bongos-core.json +81 -36
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +12 -0
  4. package/clients/bongos-client/index.cjs +12 -0
  5. package/clients/bongos-client/index.d.ts +22 -2
  6. package/clients/bongos-client/index.mjs +12 -0
  7. package/docs/adr/0240-a-skipped-module-picker-stores-nothing-and-resolves-the-bundle-on-read.md +157 -0
  8. package/docs/adr/README.md +1 -0
  9. package/docs/api/openapi.json +362 -6
  10. package/docs/api-reference.md +12 -2
  11. package/docs/copy-inventory.md +224 -218
  12. package/docs/copy-registry.json +357 -291
  13. package/docs/module-api-changelog.md +4 -0
  14. package/modules/dev-box/app/src/vendor/bongos-client.cjs +12 -0
  15. package/modules/government/catalog.js +6 -0
  16. package/modules/government/migrations/government_009_mingle_manage.sql +55 -0
  17. package/modules/lifecycle/migrations/lifecycle_007_mingle.sql +134 -0
  18. package/modules/lifecycle/mingle.js +337 -0
  19. package/modules/lifecycle/module.json +1 -0
  20. package/modules/lifecycle/routes/mingle.js +116 -0
  21. package/modules/provisioning/migrations/provisioning_014_module_selection.sql +60 -0
  22. package/modules/provisioning/provisioning.js +24 -4
  23. package/modules/provisioning/routes/provisioning.js +59 -2
  24. package/modules/provisioning/starter-bundles.js +91 -0
  25. package/modules/public-landing/public/assets/cosmos.css +1 -1
  26. package/modules/public-landing/public/projects.html +230 -35
  27. package/modules/public-landing/public/projects.probes.json +2 -2
  28. package/modules/public-landing/public/projects.states.json +5 -4
  29. package/modules/ui-design/kit/serve.js +32 -0
  30. package/package-lock.json +2 -2
  31. package/package.json +1 -1
  32. package/src/module-api.js +1 -1
  33. package/tests/government_seed.mjs +35 -0
  34. package/tests/mingle.mjs +370 -0
  35. package/tests/projects_hub.mjs +72 -7
  36. package/tests/projects_hub_module_picker.mjs +369 -0
  37. package/tests/provisioning_module_selection.mjs +286 -0
  38. package/tests/provisioning_starter_bundles.mjs +131 -0
@@ -327,5 +327,6 @@ This keeps the decision history honest and traceable.
327
327
  | 0238 | [**The join door is a provisioning SETTINGS key, and the hub column stays the cache** ([task 1002324](https://cloudbongos.com/builders#/task/1002324) · goal 1000046, criterion `proj-visibility-states` — R05). [ADR 0182](<redacted>.md) D5 says R05 "moves host — an instance surface, not a hub one", and on the tree both halves of that sentence pointed at nothing: `platform_identity_projects.visibility` (which R04 shipped) is written by **no route** — every write on `routes/projects.js` is the `project.curate` CURATION gate with no ownership check, `upsertProject` deliberately omits the column, and `owner_github_id` there is documented "advisory attribution (not an authority)" — while "the project's own hall settings" is a page that does not exist (`settings.html` is per-BUILDER; the hall's my-projects page was deleted by task 1003308). **Decision: the door is a key in `provisioning.SETTINGS_VOCAB`** (`visibility ∈ public|private|stealth`, default `public`), set through the existing own-scoped `PATCH /provisioning/instances/:id/settings` and rendered as one more `.mSec` section on the manage view an owner already uses. One line in the vocabulary buys the whole task from machinery already proven: `instanceAccessDenied` (own-scoped, Archon override, **404** not 403 — no existence leak), a validator that IS the vocabulary (so an off-list value 400s and a key can never drift out of the schema), a `provisioning_events` audit row, the `<PREFIX>_VISIBILITY` env the runner patches into `web.env`, and `src/branding.js` mapping it onto `project.visibility` in the public manifest. It is a **POLICY** key, not a platform-drawn one like the planet, for two reasons that outlive today: ADR 0182 D6 makes the door the CEILING `joinability` may only narrow and D1/D5 put admission authority on the instance — a door the instance cannot read cannot be a ceiling — and a key with a DEFAULT belongs in the defaulted vocabulary (`PLANET_VOCAB` keys are nullable because null means "the platform picks", and there is no such thing as the platform picking your join door). Nothing here writes the hub column: it stays ADR 0182 D5's cache, and R08 is the report that will feed it — so today an owner's pick is stored, validated, audited and delivered to their instance, and does not yet move the orb (that gap is R06/R08's, named so the next builder does not hunt a bug). The section deliberately ships **no "what your project runs" read-back line**, unlike its two siblings: a deployed project reports no door until it takes a release carrying the field, and a line that could only ever say "this project's server doesn't report this yet" is worse than none. Rejected: a new owner-scoped write route on the hub catalog (D5 forbids the hub authorising the change); a new instance-side settings page on its own (right long-run home, but today a control with no consumer while R08 is unshipped); a `visibility` COLUMN beside `description` (that one earned a column as half the publish gate and is read by SQL — a closed-vocabulary knob is what the jsonb store exists for); and `PLANET_VOCAB` to dodge the restart (a quieter save today, the ceiling rule lost tomorrow).](<redacted>.md) | provisioning / public-landing |
328
328
  | 0237 | [**A starter bundle is a preset over the always-on core, keyed by the declared project type** ([task 1002338](https://cloudbongos.com/builders#/task/1002338) · goal 1000046, criterion `proj-module-select`). "During setup a default set of modules is pre-selected" can only mean the OPT-IN modules, since every instance already boots with the same sixteen `default: true` core-domain ones — so a bundle is a short list of opt-in module keys layered on that floor, preselected and freely toggled off by R20's picker ([task 1002339](https://cloudbongos.com/builders#/task/1002339)). **The key is the DECLARED type, and the seeded vocabulary is stale:** the task text (2026-07-17) names *Game / SaaS-Tool / Community*, but task [1002772](https://cloudbongos.com/builders#/task/1002772)'s owner decision (2026-08-13) fixed the live closed set as `game · research · business · non-profit · not-sure` — two CHECK constraints, two `PROJECT_TYPES` constants and five wizard buttons — and "community" survives only as a `team_shape`. Two constraints ride the key: the arrow runs **type → modules, never back** (1002772 rejected inferring type from module flags, which flip all through setup), and the R15 interview must **not re-ask** what `type` declares, so R19 adds no `PROJECT_DETAIL_VOCAB` key and no migration. `not-sure` and *undeclared* resolve to the same modules but are deliberately different answers — `type` is nullable precisely so "nobody said" is never back-filled with a guess, and the resolver returns `type: null` with its own copy. **The declaration is pinned by a test, not a runtime catalog read:** a module may not import `src/module-loader/catalog.js` (the one-way rule, [ADR 0083](<redacted>.md)), so `modules/provisioning/starter-bundles.js` stays PURE like `onboard-plan.js` beside it and `tests/provisioning_starter_bundles.mjs` reads every `modules/*/module.json` off disk — failing the build if a bundle names a module that does not exist, if `ALWAYS_ON_CORE` drifts from the manifests' `default: true` set, if a bundle names an always-on module (a preselect that toggles nothing), or if it names one of the two hub-only modules. The bundles are `game → dev-box, discord`; `research → agents`; `business → agents, dev-box`; `non-profit → discord`; `not-sure → —`, and they are SHORT because today's opt-in catalog is three modules wide and a preselect an owner must audit is worse than none. Served whole at public `GET /provisioning/starter-bundles` (`{ core, bundles }`, same absent gate as `onboard-plan`) so the picker re-preselects without a refetch and R17's recommendations read the same shape. A bundle is **never a publish precondition** — [ADR 0182](<redacted>.md) D4 fixes that bar at name + description alone. Rejected: a runtime catalog read, a `bundle` key in the interview vocabulary, inferring the bundle from the description (the deferred AI layer, [task 1003500](https://cloudbongos.com/builders#/task/1003500)), and keeping the seeded Game/SaaS-Tool/Community triple.](<redacted>.md) | provisioning / project creation |
329
329
  | 0239 | [**A recommendation is a table that carries its own reason; governance is a preview and invites are a seam** ([task 1002336](https://cloudbongos.com/builders#/task/1002336) · goal 1000046, criteria `proj-module-select` / `proj-governance-reco` / `proj-invite-builders`). R19 ([ADR 0237](<redacted>.md)) declared the per-type starter bundle and said outright it is "a declaration and not a rule engine — R17/R22 own the layer that reasons"; this is that layer, and the goal pins it **rule-based**, with the AI/embeddings pass deferred ([task 1003500](https://cloudbongos.com/builders#/task/1003500), idea 1000552). **Two inputs, and the free-text description is not one of them:** the DECLARED `type` (a column) and `team_shape` (the one closed-answer key the R15 interview collects, [task 1002334](https://cloudbongos.com/builders#/task/1002334)) — inferring from prose is the deferred layer ADR 0237 §7 already rejected once. The engine is three frozen tables (`TEAM_SHAPE_RULES` add/remove ONE opt-in module; `GOVERNANCE_RULES` name a preset; `INVITE_RULES` emit a seek) and **every row carries its own `reason` sentence**, shipped with the effect rather than reconstructed by whichever surface renders it — a recommendation an owner cannot be told the reason for is one they cannot disagree with. `bundle.adjustments` is a **DELTA**: a rule whose condition matched but whose module the bundle already carried records NOTHING, because "we switched discord on because you said community" over a module a game would have had anyway is a claim the owner can check and find false. **Governance is a named preview, never a setting** — the governance module is planned and unbuilt (goal 1000030), so a preset is a name plus one sentence of what it feels like (`owner-only` / `trusted-team` / `open-contribution`) and every result carries `applied: false`, one flag instead of the same fact hardcoded in R25, R18, the wizard and the project page. **Invites are a SEAM**: `suggested` is always `[]` because ranking builders means querying the leaderboard in the sibling `economy` module, which the one-way rule ([ADR 0083](<redacted>.md)) forbids importing — the engine emits `seeking: { kind: 'top-builders', limit }` (or `null` = suggest nobody, a real answer) and R28 ([task 1002347](https://cloudbongos.com/builders#/task/1002347)) turns it into names. **Skipping is an answer:** every table states a default and each is the NARROWEST option (widening later costs one setting; a role granted to strangers is not recovered by setting it back), an off-vocabulary `team_shape` reads as no answer exactly as `normalizeProjectDetail` drops it, and the route answers 200-with-defaults rather than 400 — refusing would make the supported path ([ADR 0182](<redacted>.md) D4) an error. `tailored: boolean` is the machine-readable half of `proj-detail-interview`'s details-vs-skip split that R18 ([task 1002337](https://cloudbongos.com/builders#/task/1002337)) proves. Served at public `GET /provisioning/recommendations?type=&team_shape=` (same absent gate as `starter-bundles`; no database, pinned by a `pool`-throws stub), returning ADR 0237 §5's bundle shape plus `adjustments` so a recommendation and R20's preselect can never name different modules. Rejected: reading the description, filling `suggested` from `GET /public/leaderboard`, a weighted scoring function, a reason per matched condition, and a 400 on an unknown shape.](<redacted>.md) | provisioning / project creation |
330
+ | 0240 | [**A skipped module picker stores nothing, and the bundle is resolved on read** ([task 1002339](https://cloudbongos.com/builders#/task/1002339) · goal 1000046, criterion `proj-module-select`). [ADR 0237](<redacted>.md) declared the starter bundles and closed with "where a chosen selection lands is R20's decision". It lands in a new **nullable** `modules jsonb` column on `provisioning_instances` (`provisioning_014`), read through `starter-bundles.effectiveModules(row)` and **three-valued**: `NULL` = nobody answered (the picker was skipped, or the caller does not ask) and the declared type's bundle is resolved AT READ TIME; `[]` = the owner switched them all off, a real answer; `["dev-box",…]` = what they picked. `NOT NULL DEFAULT '[]'` was rejected because it collapses the first two forever — the `PROJECT_DETAIL_VOCAB` rule ("absent stays absent, skipped stays skipped") applied to a list. **Skipping is safe because the preselect and the fallback are the same function**, `bundleForProjectType`, called twice — not two lists kept in agreement — so `bongos onboard` and any API caller inherit the bundle by omitting the field, and the canonical sequence in `onboard-plan.js` grows **no new step** (the picker refines `type`, an answer already in it). NOT `detail` (its vocabulary is closed + scalar by construction, and [ADR 0237](<redacted>.md) §2 ruled the interview must not ask this) and NOT `settings` (a setting is a policy the RUNNING instance obeys — web.env, a settings-apply intent, a restart). The picker's catalog copy (`OPTIONAL_MODULES`) is **declared beside the bundles**, because `module.json` descriptions are written for builders, `GET /modules` needs a session the signed-out wizard has not got, and `GET /public/modules` serves booleans — pinned the ADR 0237 way, so a new `default: false` module cannot ship without owner-facing copy. `GET /provisioning/starter-bundles` gains `optional` so the preselect and the catalog arrive in **one** read. The route **refuses** an unknown / always-on / hub-only key (`400 bad_modules`) rather than dropping it silently (`badProjectDetail`'s rule), and a re-request reports `modules_ignored`. The column is a DECLARATION, not yet an input to the control-plane runner. Rejected: storing the preselect on a skip (it writes down an answer nobody gave), a new `onboardPlan` step, and rendering the always-on core as sixteen disabled toggles (a disabled control reads as a choice the owner failed to make).](<redacted>.md) | provisioning / project creation |
330
331
 
331
332
  > ⚠️ **Numbering collisions are now machine-enforced, not narrated.** Twenty numbers are shared by two ADRs each — assigned in parallel sessions before anything checked. The files keep their filenames (renumbering would break every existing citation, and a number once assigned is never reused), and this table disambiguates each pair as `NNNN-a` / `NNNN-b`. The authoritative list is `LEGACY_DUPLICATE_ADRS` in [`scripts/gds/adr-namespace.js`](../../scripts/gds/adr-namespace.js), frozen by exact filename: a **new** collision — or a third file joining a legacy number, or a rename of either half — hard-fails the `unit` CI gate, as does an index row that points at no file or an ADR with no row. The list may only shrink. This paragraph used to narrate the collisions one by one and had fallen eight behind reality; see [ADR 0195](<redacted>.md) for why the check exists and why the pairs are grandfathered rather than renumbered.
@@ -161,6 +161,10 @@
161
161
  "name": "memory",
162
162
  "description": "Endpoints under `/api/bongos/memory`."
163
163
  },
164
+ {
165
+ "name": "mingle",
166
+ "description": "Endpoints under `/api/bongos/mingle`."
167
+ },
164
168
  {
165
169
  "name": "modules",
166
170
  "description": "Endpoints under `/api/bongos/modules`."
@@ -10054,6 +10058,253 @@
10054
10058
  ]
10055
10059
  }
10056
10060
  },
10061
+ "/mingle": {
10062
+ "get": {
10063
+ "operationId": "get_mingle",
10064
+ "tags": [
10065
+ "mingle"
10066
+ ],
10067
+ "summary": "GET /mingle",
10068
+ "description": ", and OWN-SCOPED — your own state, never someone else's. Who else has opted in to being introduced is a fact about them.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
10069
+ "x-rank": "any-builder",
10070
+ "x-source": "modules/lifecycle/routes/mingle.js",
10071
+ "responses": {
10072
+ "200": {
10073
+ "description": "Success.",
10074
+ "content": {
10075
+ "application/json": {
10076
+ "schema": {
10077
+ "$ref": "#/components/schemas/GetMingleResponse"
10078
+ }
10079
+ }
10080
+ }
10081
+ },
10082
+ "400": {
10083
+ "$ref": "#/components/responses/BadRequest"
10084
+ },
10085
+ "401": {
10086
+ "$ref": "#/components/responses/Unauthorized"
10087
+ },
10088
+ "403": {
10089
+ "$ref": "#/components/responses/Forbidden"
10090
+ }
10091
+ },
10092
+ "security": [
10093
+ {
10094
+ "builderSession": []
10095
+ }
10096
+ ]
10097
+ }
10098
+ },
10099
+ "/mingle/opt-in": {
10100
+ "put": {
10101
+ "operationId": "put_mingle_opt_in",
10102
+ "tags": [
10103
+ "mingle"
10104
+ ],
10105
+ "summary": "PUT /mingle/opt-in",
10106
+ "description": ", OWN-SCOPED. Keyed by the SESSION, never a body field — consent you can give on someone else's behalf is not consent.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
10107
+ "x-rank": "any-builder",
10108
+ "x-source": "modules/lifecycle/routes/mingle.js",
10109
+ "requestBody": {
10110
+ "required": true,
10111
+ "content": {
10112
+ "application/json": {
10113
+ "schema": {
10114
+ "$ref": "#/components/schemas/PutMingleOptInRequest"
10115
+ }
10116
+ }
10117
+ },
10118
+ "x-validated": true
10119
+ },
10120
+ "responses": {
10121
+ "200": {
10122
+ "description": "Success.",
10123
+ "content": {
10124
+ "application/json": {
10125
+ "schema": {
10126
+ "$ref": "#/components/schemas/PutMingleOptInResponse"
10127
+ }
10128
+ }
10129
+ }
10130
+ },
10131
+ "400": {
10132
+ "$ref": "#/components/responses/ValidationFailed"
10133
+ },
10134
+ "401": {
10135
+ "$ref": "#/components/responses/Unauthorized"
10136
+ },
10137
+ "403": {
10138
+ "$ref": "#/components/responses/Forbidden"
10139
+ }
10140
+ },
10141
+ "security": [
10142
+ {
10143
+ "builderSession": []
10144
+ }
10145
+ ]
10146
+ }
10147
+ },
10148
+ "/mingle/pairs/{id}/decide": {
10149
+ "post": {
10150
+ "operationId": "post_mingle_pairs_id_decide",
10151
+ "tags": [
10152
+ "mingle"
10153
+ ],
10154
+ "summary": "POST /mingle/pairs/:id/decide",
10155
+ "description": ". The UPDATE itself requires the caller to be one of the two people in the pair, so a builder cannot answer an introduction they are not part of — enforced in SQL, not by a check here that a later caller could skip.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
10156
+ "x-rank": "any-builder",
10157
+ "x-source": "modules/lifecycle/routes/mingle.js",
10158
+ "parameters": [
10159
+ {
10160
+ "name": "id",
10161
+ "in": "path",
10162
+ "required": true,
10163
+ "schema": {
10164
+ "type": "string"
10165
+ },
10166
+ "description": "Path parameter `id`."
10167
+ }
10168
+ ],
10169
+ "requestBody": {
10170
+ "required": true,
10171
+ "content": {
10172
+ "application/json": {
10173
+ "schema": {
10174
+ "$ref": "#/components/schemas/PostMinglePairsIdDecideRequest"
10175
+ }
10176
+ }
10177
+ },
10178
+ "x-validated": true
10179
+ },
10180
+ "responses": {
10181
+ "200": {
10182
+ "description": "Success.",
10183
+ "content": {
10184
+ "application/json": {
10185
+ "schema": {
10186
+ "$ref": "#/components/schemas/PostMinglePairsIdDecideResponse"
10187
+ }
10188
+ }
10189
+ }
10190
+ },
10191
+ "400": {
10192
+ "$ref": "#/components/responses/ValidationFailed"
10193
+ },
10194
+ "401": {
10195
+ "$ref": "#/components/responses/Unauthorized"
10196
+ },
10197
+ "403": {
10198
+ "$ref": "#/components/responses/Forbidden"
10199
+ },
10200
+ "404": {
10201
+ "$ref": "#/components/responses/NotFound"
10202
+ }
10203
+ },
10204
+ "security": [
10205
+ {
10206
+ "builderSession": []
10207
+ }
10208
+ ]
10209
+ }
10210
+ },
10211
+ "/mingle/project": {
10212
+ "put": {
10213
+ "operationId": "put_mingle_project",
10214
+ "tags": [
10215
+ "mingle"
10216
+ ],
10217
+ "summary": "PUT /mingle/project",
10218
+ "description": "perm: mingle.manage (floor metic) — see the header. Offering the feature to the project is an operator act; it still cannot pair anyone who has not opted themselves in.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `mingle.manage` (all required).",
10219
+ "x-rank": "metic+archon",
10220
+ "x-source": "modules/lifecycle/routes/mingle.js",
10221
+ "x-permissions": [
10222
+ "mingle.manage"
10223
+ ],
10224
+ "requestBody": {
10225
+ "required": true,
10226
+ "content": {
10227
+ "application/json": {
10228
+ "schema": {
10229
+ "$ref": "#/components/schemas/PutMingleProjectRequest"
10230
+ }
10231
+ }
10232
+ },
10233
+ "x-validated": true
10234
+ },
10235
+ "responses": {
10236
+ "200": {
10237
+ "description": "Success."
10238
+ },
10239
+ "400": {
10240
+ "$ref": "#/components/responses/ValidationFailed"
10241
+ },
10242
+ "401": {
10243
+ "$ref": "#/components/responses/Unauthorized"
10244
+ },
10245
+ "403": {
10246
+ "$ref": "#/components/responses/Forbidden"
10247
+ }
10248
+ },
10249
+ "security": [
10250
+ {
10251
+ "builderSession": []
10252
+ }
10253
+ ]
10254
+ }
10255
+ },
10256
+ "/mingle/round": {
10257
+ "post": {
10258
+ "operationId": "post_mingle_round",
10259
+ "tags": [
10260
+ "mingle"
10261
+ ],
10262
+ "summary": "POST /mingle/round",
10263
+ "description": "perm: mingle.manage — running a round writes introductions for other people, so it is an operator action rather than something a builder does to themselves. Refuses on its own when the project switch is off (runMingleRound checks).\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `mingle.manage` (all required).",
10264
+ "x-rank": "metic+archon",
10265
+ "x-source": "modules/lifecycle/routes/mingle.js",
10266
+ "x-permissions": [
10267
+ "mingle.manage"
10268
+ ],
10269
+ "requestBody": {
10270
+ "required": false,
10271
+ "content": {
10272
+ "application/json": {
10273
+ "schema": {
10274
+ "$ref": "#/components/schemas/PostMingleRoundRequest"
10275
+ }
10276
+ }
10277
+ },
10278
+ "x-validated": true
10279
+ },
10280
+ "responses": {
10281
+ "200": {
10282
+ "description": "Success.",
10283
+ "content": {
10284
+ "application/json": {
10285
+ "schema": {
10286
+ "$ref": "#/components/schemas/PostMingleRoundResponse"
10287
+ }
10288
+ }
10289
+ }
10290
+ },
10291
+ "400": {
10292
+ "$ref": "#/components/responses/ValidationFailed"
10293
+ },
10294
+ "401": {
10295
+ "$ref": "#/components/responses/Unauthorized"
10296
+ },
10297
+ "403": {
10298
+ "$ref": "#/components/responses/Forbidden"
10299
+ }
10300
+ },
10301
+ "security": [
10302
+ {
10303
+ "builderSession": []
10304
+ }
10305
+ ]
10306
+ }
10307
+ },
10057
10308
  "/modules": {
10058
10309
  "get": {
10059
10310
  "operationId": "get_modules",
@@ -11763,7 +12014,7 @@
11763
12014
  "provisioning"
11764
12015
  ],
11765
12016
  "summary": "GET /provisioning/starter-bundles",
11766
- "description": "GET /provisioning/starter-bundles — the curated module preset for each declared project type (BV1.R19, task 1002338; goal 1000046 criterion `proj-module-select`). Returns the always-on core once, then one resolved bundle per type: `{ type, label, summary, core, bundle, modules }`. The whole table in one read, because the creation picker (R20, task 1002339) re-preselects as the owner flips the type question and must not refetch to do it; R17's rule engine reads the same shape so a recommendation and a preselect can never name different modules. A DECLARATION, not a query — modules/provisioning/starter-bundles.js is pure, so this route touches no database and no per-request state. rank: public — the signed-out hub wizard (/projects?view=new) reads it while choosing a type, exactly like onboard-plan above; no write, no secret, and the module catalog it names is already public at GET /public/modules.\n\n**Rank:** `public` — No authentication — any caller.",
12017
+ "description": "GET /provisioning/starter-bundles — the curated module preset for each declared project type (BV1.R19, task 1002338; goal 1000046 criterion `proj-module-select`). Returns the always-on core once, then one resolved bundle per type: `{ type, label, summary, core, bundle, modules }`. The whole table in one read, because the creation picker (R20, task 1002339) re-preselects as the owner flips the type question and must not refetch to do it; R17's rule engine reads the same shape so a recommendation and a preselect can never name different modules. A DECLARATION, not a query — modules/provisioning/starter-bundles.js is pure, so this route touches no database and no per-request state. rank: public — the signed-out hub wizard (/projects?view=new) reads it while choosing a type, exactly like onboard-plan above; no write, no secret, and the module catalog it names is already public at GET /public/modules. `optional` joined the payload for R20's picker (task 1002339): the opt-in catalog with the owner-facing label + one-line summary each row renders. It lives here rather than at a module-catalog route because the two things a picker needs — what is preselected and what may be chosen — have to arrive in ONE read of ONE declaration, or the panel can preselect a key it has no row for. The alternatives were both worse: `GET /modules` needs a session (this wizard is read signed-out) and `GET /public/modules` serves booleans, not copy. Additive — `core` and `bundles` keep their shape.\n\n**Rank:** `public` — No authentication — any caller.",
11767
12018
  "x-rank": "public",
11768
12019
  "x-source": "modules/provisioning/routes/provisioning.js",
11769
12020
  "responses": {
@@ -16840,6 +17091,19 @@
16840
17091
  "files"
16841
17092
  ]
16842
17093
  },
17094
+ "GetMingleResponse": {
17095
+ "type": "object",
17096
+ "properties": {
17097
+ "mingle": {},
17098
+ "open_pairs": {},
17099
+ "matching": {}
17100
+ },
17101
+ "required": [
17102
+ "mingle",
17103
+ "open_pairs",
17104
+ "matching"
17105
+ ]
17106
+ },
16843
17107
  "GetModulesResponse": {
16844
17108
  "type": "object",
16845
17109
  "properties": {
@@ -17053,11 +17317,13 @@
17053
17317
  "type": "object",
17054
17318
  "properties": {
17055
17319
  "core": {},
17056
- "bundles": {}
17320
+ "bundles": {},
17321
+ "optional": {}
17057
17322
  },
17058
17323
  "required": [
17059
17324
  "core",
17060
- "bundles"
17325
+ "bundles",
17326
+ "optional"
17061
17327
  ]
17062
17328
  },
17063
17329
  "GetPublicBlockersIdResponse": {
@@ -20219,6 +20485,54 @@
20219
20485
  },
20220
20486
  "additionalProperties": true
20221
20487
  },
20488
+ "PostMinglePairsIdDecideRequest": {
20489
+ "type": "object",
20490
+ "properties": {
20491
+ "decision": {
20492
+ "type": "string",
20493
+ "enum": [
20494
+ "accepted",
20495
+ "declined"
20496
+ ]
20497
+ }
20498
+ },
20499
+ "required": [
20500
+ "decision"
20501
+ ],
20502
+ "additionalProperties": false
20503
+ },
20504
+ "PostMinglePairsIdDecideResponse": {
20505
+ "type": "object",
20506
+ "properties": {
20507
+ "pair": {}
20508
+ },
20509
+ "required": [
20510
+ "pair"
20511
+ ]
20512
+ },
20513
+ "PostMingleRoundRequest": {
20514
+ "type": "object",
20515
+ "properties": {
20516
+ "limit": {
20517
+ "type": "integer",
20518
+ "minimum": 1
20519
+ }
20520
+ },
20521
+ "additionalProperties": false
20522
+ },
20523
+ "PostMingleRoundResponse": {
20524
+ "type": "object",
20525
+ "properties": {
20526
+ "paired": {},
20527
+ "count": {},
20528
+ "skipped": {}
20529
+ },
20530
+ "required": [
20531
+ "paired",
20532
+ "count",
20533
+ "skipped"
20534
+ ]
20535
+ },
20222
20536
  "PostModulesKeyEnableRequest": {
20223
20537
  "type": "object",
20224
20538
  "properties": {
@@ -20591,6 +20905,13 @@
20591
20905
  },
20592
20906
  "detail": {
20593
20907
  "type": "object"
20908
+ },
20909
+ "modules": {
20910
+ "type": "array",
20911
+ "items": {
20912
+ "type": "string"
20913
+ },
20914
+ "maxItems": 50
20594
20915
  }
20595
20916
  },
20596
20917
  "required": [
@@ -21683,6 +22004,41 @@
21683
22004
  "granted"
21684
22005
  ]
21685
22006
  },
22007
+ "PutMingleOptInRequest": {
22008
+ "type": "object",
22009
+ "properties": {
22010
+ "opted_in": {
22011
+ "type": "boolean"
22012
+ }
22013
+ },
22014
+ "required": [
22015
+ "opted_in"
22016
+ ],
22017
+ "additionalProperties": false
22018
+ },
22019
+ "PutMingleOptInResponse": {
22020
+ "type": "object",
22021
+ "properties": {
22022
+ "opted_in": {},
22023
+ "updated_at": {}
22024
+ },
22025
+ "required": [
22026
+ "opted_in",
22027
+ "updated_at"
22028
+ ]
22029
+ },
22030
+ "PutMingleProjectRequest": {
22031
+ "type": "object",
22032
+ "properties": {
22033
+ "enabled": {
22034
+ "type": "boolean"
22035
+ }
22036
+ },
22037
+ "required": [
22038
+ "enabled"
22039
+ ],
22040
+ "additionalProperties": false
22041
+ },
21686
22042
  "PutTasksIdDependenciesRequest": {
21687
22043
  "type": "object",
21688
22044
  "properties": {
@@ -21834,9 +22190,9 @@
21834
22190
  "description": "A required dependency/feature is not configured or is temporarily down."
21835
22191
  }
21836
22192
  },
21837
- "x-endpoint-count": 347,
21838
- "x-schema-count": 379,
22193
+ "x-endpoint-count": 352,
22194
+ "x-schema-count": 387,
21839
22195
  "x-undocumented-bodies": 9,
21840
- "x-response-schemas": 259,
22196
+ "x-response-schemas": 263,
21841
22197
  "x-generated-by": "scripts/gds/gen-api-docs.js"
21842
22198
  }
@@ -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. 347 endpoints across 56 route files.
5
+ > **Generated from the live route files** — the route file is authoritative. 352 endpoints across 57 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`.
@@ -399,6 +399,16 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
399
399
  | GET | `/api/bongos/memory/files` | `any-builder` | — | GET /memory/files — list the AUTHENTICATED builder's memory files (metadata only, no content). |
400
400
  | POST | `/api/bongos/memory/sync` | `any-builder` | `files` | POST /memory/sync — push a builder's local memory files to the server. |
401
401
 
402
+ ## `mingle` (5)
403
+
404
+ | Method | Path | Rank | Body | Description |
405
+ |---|---|---|---|---|
406
+ | GET | `/api/bongos/mingle` | `any-builder` | — | , and OWN-SCOPED — your own state, never someone else's. |
407
+ | PUT | `/api/bongos/mingle/opt-in` | `any-builder` | `opted_in` | , OWN-SCOPED. |
408
+ | POST | `/api/bongos/mingle/pairs/:id/decide` | `any-builder` | `decision` | . |
409
+ | PUT | `/api/bongos/mingle/project` | `metic+archon` | `enabled` | perm: mingle.manage (floor metic) — see the header. |
410
+ | POST | `/api/bongos/mingle/round` | `metic+archon` | `limit` | perm: mingle.manage — running a round writes introductions for other people, so it is an operator action rather than something a builder … |
411
+
402
412
  ## `modules` (4)
403
413
 
404
414
  | Method | Path | Rank | Body | Description |
@@ -452,7 +462,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
452
462
  | GET | `/api/bongos/provisioning/github-app/status` | `public` | — | GET /provisioning/github-app/status?state=<> — the PUBLIC progress read the onboarding callback page polls (task 2096) to know when setup… |
453
463
  | GET | `/api/bongos/provisioning/health` | `public` | — | GET /provisioning/health — liveness + the core version this module mounted against. |
454
464
  | GET | `/api/bongos/provisioning/instances` | `any-builder` | — | GET /provisioning/instances — the caller's own instances + each open intent. |
455
- | POST | `/api/bongos/provisioning/instances` | `any-builder` | `slug`, `target_ref`, `hosting_shape`, `domain`, `no_address`, `tier`, `onboard_mode`, `type`, `description`, `detail` | POST /provisioning/instances — request a new instance. |
465
+ | POST | `/api/bongos/provisioning/instances` | `any-builder` | `slug`, `target_ref`, `hosting_shape`, `domain`, `no_address`, `tier`, `onboard_mode`, `type`, `description`, `detail`, `modules` | POST /provisioning/instances — request a new instance. |
456
466
  | GET | `/api/bongos/provisioning/instances/:id` | `any-builder` | — | GET /provisioning/instances/:id — read one instance (the manage page's read). |
457
467
  | PATCH | `/api/bongos/provisioning/instances/:id/detail` | `any-builder` | `description`, `detail` | ── The optional project detail (task 1002334, goal 1000046) ────────────── PATCH /provisioning/instances/:id/detail — answer (or re-answe… |
458
468
  | POST | `/api/bongos/provisioning/instances/:id/domain` | `any-builder` | `domain` | POST /provisioning/instances/:id/domain — give the caller's OWN instance its address (task 1002692, gap 5: no path existed to add a domai… |