@bongos/core 1.22.2 → 1.23.2

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 (66) hide show
  1. package/.bongos-core.json +104 -64
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +8 -0
  4. package/clients/bongos-client/index.cjs +8 -0
  5. package/clients/bongos-client/index.d.ts +11 -1
  6. package/clients/bongos-client/index.mjs +8 -0
  7. package/docs/adr/0342-module-categories-are-eight-parents-with-approved-sub-categories.md +4 -2
  8. package/docs/api/openapi.json +210 -4
  9. package/docs/api-reference.md +6 -2
  10. package/docs/architecture.md +11 -0
  11. package/docs/copy-inventory.md +1 -1
  12. package/docs/copy-registry.json +1 -1
  13. package/docs/module-api-changelog.md +8 -0
  14. package/docs/modules-contract.md +1 -0
  15. package/docs/page-readings.json +40 -40
  16. package/migrations/core_280_store_subcategories.sql +58 -0
  17. package/modules/government/catalog.js +3 -0
  18. package/modules/government/migrations/government_034_module_subcategory_review.sql +35 -0
  19. package/modules/hall-ui/public/deploy.js +3 -3
  20. package/modules/hall-ui/public/dom-utils.js +5 -1
  21. package/modules/hall-ui/public/genesis-home.js +1 -1
  22. package/modules/lifecycle/github-push.js +1 -2
  23. package/modules/npm-release/preview/supervisor.js +2 -1
  24. package/modules/platform-identity/project-stats.js +2 -1
  25. package/package-lock.json +2 -2
  26. package/package.json +1 -1
  27. package/release-notes.json +13 -0
  28. package/scripts/bongos/autobongos-run.js +2 -1
  29. package/scripts/bongos/autonomy-gauge.js +2 -1
  30. package/scripts/bongos/backfill-disciplines.js +1 -1
  31. package/scripts/bongos/backfill-sessions.js +1 -1
  32. package/scripts/bongos/backup.js +2 -1
  33. package/scripts/bongos/dev.js +1 -1
  34. package/scripts/bongos/do-api.js +2 -1
  35. package/scripts/bongos/fitness.js +5 -6
  36. package/scripts/bongos/local-preview.js +2 -1
  37. package/scripts/bongos/login.js +1 -1
  38. package/scripts/bongos/page-reader.js +2 -1
  39. package/scripts/bongos/probe-cache.js +2 -1
  40. package/scripts/bongos/provision-core-upgrade.js +2 -1
  41. package/scripts/bongos/provision.js +5 -4
  42. package/scripts/bongos/publish-integration-gate.js +2 -1
  43. package/scripts/bongos/render-api.js +2 -1
  44. package/scripts/bongos/sandbox-stage.js +2 -1
  45. package/scripts/bongos/seed-bongos-v1-idea-routing-tasks.js +1 -1
  46. package/scripts/bongos/seed-bv1-profiles-tasks.js +1 -1
  47. package/scripts/bongos/seed-bv2-ideator-surface-tasks.js +1 -1
  48. package/scripts/bongos/session-eval.js +2 -1
  49. package/scripts/bongos/setup.js +2 -1
  50. package/scripts/bongos/sleep-helper-guard.js +88 -0
  51. package/scripts/bongos/upgrade.js +2 -2
  52. package/scripts/gds/sleep-helper-guard.js +6 -0
  53. package/src/bongos/module-scope-map.js +1 -0
  54. package/src/bongos/module-store.js +8 -3
  55. package/src/bongos/module-subcategories.js +316 -0
  56. package/src/bongos/route-rank-check.js +5 -0
  57. package/src/bongos/routes/modules.js +55 -1
  58. package/src/module-api.js +6 -1
  59. package/src/module-loader/manifest-schema.js +4 -3
  60. package/src/sleep.js +24 -0
  61. package/tests/deploy_divergence_line.mjs +4 -1
  62. package/tests/deploy_page_projects.mjs +2 -1
  63. package/tests/module_api.mjs +1 -0
  64. package/tests/module_store_instance_root.mjs +2 -1
  65. package/tests/module_store_subcategories.mjs +364 -0
  66. package/tests/sleep_helper.mjs +99 -0
@@ -1265,6 +1265,14 @@ export function createClient(opts = {}) {
1265
1265
  getStoreModulesKeyVersionsVersionTarball: (args) => request("GET", "/store/modules/{key}/versions/{version}/tarball", { hasBody: false }, args),
1266
1266
  // GET /store/modules/latest — rank: any-builder — GET /store/modules/latest
1267
1267
  getStoreModulesLatest: (args) => request("GET", "/store/modules/latest", { hasBody: false }, args),
1268
+ // GET /store/subcategories — rank: any-builder — GET /store/subcategories
1269
+ getStoreSubcategories: (args) => request("GET", "/store/subcategories", { hasBody: false }, args),
1270
+ // POST /store/subcategories/{id}/approve — rank: metic+archon — POST /store/subcategories/:id/approve
1271
+ postStoreSubcategoriesIdApprove: (args) => request("POST", "/store/subcategories/{id}/approve", { hasBody: true }, args),
1272
+ // POST /store/subcategories/{id}/merge — rank: metic+archon — POST /store/subcategories/:id/merge
1273
+ postStoreSubcategoriesIdMerge: (args) => request("POST", "/store/subcategories/{id}/merge", { hasBody: true }, args),
1274
+ // GET /store/subcategories/queue — rank: metic+archon — GET /store/subcategories/queue
1275
+ getStoreSubcategoriesQueue: (args) => request("GET", "/store/subcategories/queue", { hasBody: false }, args),
1268
1276
  // GET /store/usage-reporting — rank: any-builder — GET /store/usage-reporting
1269
1277
  getStoreUsageReporting: (args) => request("GET", "/store/usage-reporting", { hasBody: false }, args),
1270
1278
  // POST /store/usage-reporting — rank: any-builder — POST /store/usage-reporting
@@ -66,8 +66,10 @@ scarce.
66
66
  - **Task 1003784** (nested category in the manifest and both catalog surfaces) builds to this:
67
67
  `category: { parent, sub }`. `parent` is one of the eight keys, and `sub` is the key of an
68
68
  approved sub-category or absent while a proposal is pending.
69
- - **An approval queue** for proposed sub-categories is new work, not yet filed. It needs a
70
- Metic+ permission key and follows the same pattern as the score override (task 1003796).
69
+ - **An approval queue** for proposed sub-categories — built in task 1004355: `core_278`'s
70
+ `store_subcategories` + `store_module_categories`, the Metic+ atom
71
+ `module.subcategory.review` (`government_033`), and `/store/subcategories/*` (see
72
+ docs/modules-contract.md, "Getting listed").
71
73
  - **Core modules get categories too**, so the catalog is consistent. The table in D1 is their
72
74
  starting assignment.
73
75
  - **The cap (task 1003809)** counts listed modules per approved sub-category, never per parent.
@@ -24344,6 +24344,189 @@
24344
24344
  ]
24345
24345
  }
24346
24346
  },
24347
+ "/store/subcategories": {
24348
+ "get": {
24349
+ "operationId": "get_store_subcategories",
24350
+ "tags": [
24351
+ "store"
24352
+ ],
24353
+ "summary": "GET /store/subcategories",
24354
+ "description": "GET /api/bongos/store/subcategories — the approved sub-categories under each of the eight parents (task 1004355, ADR 0342 D3): what an author names in module.json's `category.sub`. A key not on this list is proposed at publish. rank: any-builder — public store metadata; nothing changed.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
24355
+ "x-rank": "any-builder",
24356
+ "x-source": "src/bongos/routes/modules.js",
24357
+ "responses": {
24358
+ "200": {
24359
+ "description": "Success."
24360
+ },
24361
+ "400": {
24362
+ "$ref": "#/components/responses/BadRequest"
24363
+ },
24364
+ "401": {
24365
+ "$ref": "#/components/responses/Unauthorized"
24366
+ },
24367
+ "403": {
24368
+ "$ref": "#/components/responses/Forbidden"
24369
+ }
24370
+ },
24371
+ "security": [
24372
+ {
24373
+ "builderSession": []
24374
+ }
24375
+ ]
24376
+ }
24377
+ },
24378
+ "/store/subcategories/queue": {
24379
+ "get": {
24380
+ "operationId": "get_store_subcategories_queue",
24381
+ "tags": [
24382
+ "store"
24383
+ ],
24384
+ "summary": "GET /store/subcategories/queue",
24385
+ "description": "GET /api/bongos/store/subcategories/queue — the proposals waiting on a Metic+ decision, oldest first, each with the modules that named it and the near-duplicate names beside it (\"Stripe\" next to \"Stripe payments\"), since stopping near-duplicates is the point of the queue (ADR 0342 D3).\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `module.subcategory.review` (all required).",
24386
+ "x-rank": "metic+archon",
24387
+ "x-source": "src/bongos/routes/modules.js",
24388
+ "x-permissions": [
24389
+ "module.subcategory.review"
24390
+ ],
24391
+ "responses": {
24392
+ "200": {
24393
+ "description": "Success."
24394
+ },
24395
+ "400": {
24396
+ "$ref": "#/components/responses/BadRequest"
24397
+ },
24398
+ "401": {
24399
+ "$ref": "#/components/responses/Unauthorized"
24400
+ },
24401
+ "403": {
24402
+ "$ref": "#/components/responses/Forbidden"
24403
+ }
24404
+ },
24405
+ "security": [
24406
+ {
24407
+ "builderSession": []
24408
+ }
24409
+ ]
24410
+ }
24411
+ },
24412
+ "/store/subcategories/{id}/approve": {
24413
+ "post": {
24414
+ "operationId": "post_store_subcategories_id_approve",
24415
+ "tags": [
24416
+ "store"
24417
+ ],
24418
+ "summary": "POST /store/subcategories/:id/approve",
24419
+ "description": "POST /api/bongos/store/subcategories/:id/approve { label?, sub_key? } — make a proposal live, optionally renamed first. A rename onto a key that already exists under the parent is refused with the merge to run instead.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `module.subcategory.review` (all required).",
24420
+ "x-rank": "metic+archon",
24421
+ "x-source": "src/bongos/routes/modules.js",
24422
+ "x-permissions": [
24423
+ "module.subcategory.review"
24424
+ ],
24425
+ "parameters": [
24426
+ {
24427
+ "name": "id",
24428
+ "in": "path",
24429
+ "required": true,
24430
+ "schema": {
24431
+ "type": "string"
24432
+ },
24433
+ "description": "Path parameter `id`."
24434
+ }
24435
+ ],
24436
+ "requestBody": {
24437
+ "required": false,
24438
+ "content": {
24439
+ "application/json": {
24440
+ "schema": {
24441
+ "$ref": "#/components/schemas/PostStoreSubcategoriesIdApproveRequest"
24442
+ }
24443
+ }
24444
+ },
24445
+ "x-validated": true
24446
+ },
24447
+ "responses": {
24448
+ "200": {
24449
+ "description": "Success."
24450
+ },
24451
+ "400": {
24452
+ "$ref": "#/components/responses/ValidationFailed"
24453
+ },
24454
+ "401": {
24455
+ "$ref": "#/components/responses/Unauthorized"
24456
+ },
24457
+ "403": {
24458
+ "$ref": "#/components/responses/Forbidden"
24459
+ },
24460
+ "404": {
24461
+ "$ref": "#/components/responses/NotFound"
24462
+ }
24463
+ },
24464
+ "security": [
24465
+ {
24466
+ "builderSession": []
24467
+ }
24468
+ ]
24469
+ }
24470
+ },
24471
+ "/store/subcategories/{id}/merge": {
24472
+ "post": {
24473
+ "operationId": "post_store_subcategories_id_merge",
24474
+ "tags": [
24475
+ "store"
24476
+ ],
24477
+ "summary": "POST /store/subcategories/:id/merge",
24478
+ "description": "POST /api/bongos/store/subcategories/:id/merge { into } — fold a proposal (or an approved duplicate) into an approved sub-category. Every module under it moves to the target.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `module.subcategory.review` (all required).",
24479
+ "x-rank": "metic+archon",
24480
+ "x-source": "src/bongos/routes/modules.js",
24481
+ "x-permissions": [
24482
+ "module.subcategory.review"
24483
+ ],
24484
+ "parameters": [
24485
+ {
24486
+ "name": "id",
24487
+ "in": "path",
24488
+ "required": true,
24489
+ "schema": {
24490
+ "type": "string"
24491
+ },
24492
+ "description": "Path parameter `id`."
24493
+ }
24494
+ ],
24495
+ "requestBody": {
24496
+ "required": false,
24497
+ "content": {
24498
+ "application/json": {
24499
+ "schema": {
24500
+ "$ref": "#/components/schemas/PostStoreSubcategoriesIdMergeRequest"
24501
+ }
24502
+ }
24503
+ },
24504
+ "x-validated": true
24505
+ },
24506
+ "responses": {
24507
+ "200": {
24508
+ "description": "Success."
24509
+ },
24510
+ "400": {
24511
+ "$ref": "#/components/responses/ValidationFailed"
24512
+ },
24513
+ "401": {
24514
+ "$ref": "#/components/responses/Unauthorized"
24515
+ },
24516
+ "403": {
24517
+ "$ref": "#/components/responses/Forbidden"
24518
+ },
24519
+ "404": {
24520
+ "$ref": "#/components/responses/NotFound"
24521
+ }
24522
+ },
24523
+ "security": [
24524
+ {
24525
+ "builderSession": []
24526
+ }
24527
+ ]
24528
+ }
24529
+ },
24347
24530
  "/store/usage-reporting": {
24348
24531
  "get": {
24349
24532
  "operationId": "get_store_usage_reporting",
@@ -35803,13 +35986,15 @@
35803
35986
  },
35804
35987
  "created_module": {},
35805
35988
  "version": {},
35806
- "channel": {}
35989
+ "channel": {},
35990
+ "category": {}
35807
35991
  },
35808
35992
  "required": [
35809
35993
  "ok",
35810
35994
  "created_module",
35811
35995
  "version",
35812
- "channel"
35996
+ "channel",
35997
+ "category"
35813
35998
  ]
35814
35999
  },
35815
36000
  "PostStoreModulesKeyVersionsVersionErrorCountRequest": {
@@ -35905,6 +36090,27 @@
35905
36090
  "reason"
35906
36091
  ]
35907
36092
  },
36093
+ "PostStoreSubcategoriesIdApproveRequest": {
36094
+ "type": "object",
36095
+ "properties": {
36096
+ "label": {
36097
+ "type": "string",
36098
+ "maxLength": 80
36099
+ },
36100
+ "sub_key": {
36101
+ "type": "string",
36102
+ "maxLength": 80
36103
+ }
36104
+ },
36105
+ "additionalProperties": false
36106
+ },
36107
+ "PostStoreSubcategoriesIdMergeRequest": {
36108
+ "type": "object",
36109
+ "properties": {
36110
+ "into": {}
36111
+ },
36112
+ "additionalProperties": false
36113
+ },
35908
36114
  "PostStoreUsageReportingRequest": {
35909
36115
  "type": "object",
35910
36116
  "properties": {
@@ -36980,8 +37186,8 @@
36980
37186
  "description": "A required dependency/feature is not configured or is temporarily down."
36981
37187
  }
36982
37188
  },
36983
- "x-endpoint-count": 578,
36984
- "x-schema-count": 622,
37189
+ "x-endpoint-count": 582,
37190
+ "x-schema-count": 624,
36985
37191
  "x-undocumented-bodies": 12,
36986
37192
  "x-response-schemas": 404,
36987
37193
  "x-generated-by": "scripts/bongos/gen-api-docs.js"
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Bongos API reference
4
4
 
5
- > **Generated from the live route files** — the route file is authoritative. 578 endpoints across 98 route files.
5
+ > **Generated from the live route files** — the route file is authoritative. 582 endpoints across 98 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`.
@@ -863,7 +863,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
863
863
  | GET | `/api/bongos/sso/pubkey` | `public` | — | GET /sso/pubkey — the hub's Ed25519 assertion-verification key (public). |
864
864
  | POST | `/api/bongos/sso/token` | `public` | `client_id`, `client_secret`, `code`, `redirect_uri` | POST /sso/token — server-to-server code redemption. |
865
865
 
866
- ## `store` (18)
866
+ ## `store` (22)
867
867
 
868
868
  | Method | Path | Rank | Body | Description |
869
869
  |---|---|---|---|---|
@@ -883,6 +883,10 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
883
883
  | POST | `/api/bongos/store/modules/:key/versions/:version/release` | `metic+archon` | `reason` | POST /api/bongos/store/modules/:key/versions/:version/release — a Metic+ person releases a version the promotion gate HELD on tester (a t… |
884
884
  | GET | `/api/bongos/store/modules/:key/versions/:version/tarball` | `any-builder` | — | |
885
885
  | GET | `/api/bongos/store/modules/latest` | `any-builder` | — | The newest published version of each named module — what `bongos upgrade` compares an instance's installed store modules against to repor… |
886
+ | GET | `/api/bongos/store/subcategories` | `any-builder` | — | GET /api/bongos/store/subcategories — the approved sub-categories under each of the eight parents (task 1004355, ADR 0342 D3): what an au… |
887
+ | POST | `/api/bongos/store/subcategories/:id/approve` | `metic+archon` | `label`, `sub_key` | POST /api/bongos/store/subcategories/:id/approve { label?, sub_key? } — make a proposal live, optionally renamed first. |
888
+ | POST | `/api/bongos/store/subcategories/:id/merge` | `metic+archon` | `into` | POST /api/bongos/store/subcategories/:id/merge { into } — fold a proposal (or an approved duplicate) into an approved sub-category. |
889
+ | GET | `/api/bongos/store/subcategories/queue` | `metic+archon` | — | GET /api/bongos/store/subcategories/queue — the proposals waiting on a Metic+ decision, oldest first, each with the modules that named it… |
886
890
  | GET | `/api/bongos/store/usage-reporting` | `any-builder` | — | The caller's own reporting switch (Settings → Profile and privacy). |
887
891
  | POST | `/api/bongos/store/usage-reporting` | `any-builder` | `enabled` | |
888
892
 
@@ -262,6 +262,17 @@ passkey_challenges(challenge PK, purpose, builder_id NULL, created_at, expires_a
262
262
  -- single-use (DELETEd on consume), TTL swept; builder_id NULL for sign-in
263
263
  ```
264
264
 
265
+ Store sub-categories (migration `core_280`, task 1004355, [ADR 0342](adr/0342-module-categories-are-eight-parents-with-approved-sub-categories.md) D3 — an author proposes one at publish, a Metic+ person approves, renames or merges it; `src/bongos/module-subcategories.js`):
266
+
267
+ ```
268
+ store_subcategories(id, parent one of 8, sub_key, label, status proposed|approved|merged,
269
+ proposed_by, decided_by, decided_at, merged_into FK self, created_at)
270
+ -- UNIQUE (parent, sub_key); merged ⇔ merged_into set; proposed ⇔ undecided.
271
+ store_module_categories(module_key PK FK store_modules, parent, subcategory_id FK NULL, updated_at)
272
+ -- where a module sits now; a merge moves rows here. Only an approved
273
+ -- sub-category is live for the cap and ranking.
274
+ ```
275
+
265
276
  Module release channels (migration `core_276`, task 1003815, [ADR 0344](adr/0344-a-tester-is-a-project-that-opts-in-once.md) — which channel a published version is on; the data only, the tester-first publish, promotion gate and channel-aware install are their own tasks):
266
277
 
267
278
  ```
@@ -1857,7 +1857,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1857
1857
  | `4de556377db2` | certain | verdict {…} | `modules/hall-ui/public/task.js:432` |
1858
1858
  | `f20985df3729` | certain | version · {…} | `modules/hall-ui/public/sky-interaction.js:38` |
1859
1859
  | `969577b937d6` | certain | versions in flight | `modules/hall-ui/public/roadmap.js:321` |
1860
- | `8cd3cc4005fc` | certain | via goal | `modules/hall-ui/public/dom-utils.js:446` |
1860
+ | `8cd3cc4005fc` | certain | via goal | `modules/hall-ui/public/dom-utils.js:450` |
1861
1861
  | `18cf722bdd7e` | certain | waiting · | `modules/hall-ui/public/sky-interaction.js:128` |
1862
1862
  | `354861d3c2ce` | certain | waits for a restart | `modules/hall-ui/public/modules.js:139` |
1863
1863
  | `35be9e0923da` | certain | walks the whole path in plain language. | `modules/hall-ui/public/hall-render.js:841` |
@@ -18583,7 +18583,7 @@
18583
18583
  "surface": "builders-hall",
18584
18584
  "text": "via goal",
18585
18585
  "file": "modules/hall-ui/public/dom-utils.js",
18586
- "line": 446,
18586
+ "line": 450,
18587
18587
  "origin": "js-markup",
18588
18588
  "confidence": "certain"
18589
18589
  },
@@ -3213,5 +3213,13 @@ is load-bearing: the script throws rather than guess if it is missing, and
3213
3213
  landed since 1.22.0 with no explicit bump. run 40264537233. (task 1002620)
3214
3214
  1.22.2 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3215
3215
  landed since 1.22.1 with no explicit bump. run 40266740044. (task 1002620)
3216
+ 1.23.0 — sleep: THE server-side "wait N milliseconds" (src/sleep.js), so a
3217
+ module waits through one shared copy instead of hand-rolling a Promise
3218
+ around setTimeout (task 1004893; fitness fails on a third copy). Additive,
3219
+ so MINOR. The browser twin is window.OTB.sleep.
3220
+ 1.23.1 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3221
+ landed since 1.23.0 with no explicit bump. run 40270556091. (task 1002620)
3222
+ 1.23.2 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3223
+ landed since 1.23.1 with no explicit bump. run 40399986777. (task 1002620)
3216
3224
  ---------------------------------------------------------------------------
3217
3225
  ```
@@ -399,6 +399,7 @@ Run `bongos upgrade` to confirm coreVersion compatibility, then `node scripts/bo
399
399
  - **Sorting by a part** (task 1003800). `GET /store/modules?sort=<part>` orders the listed modules by one part a buyer cares about: `tests`, `install`, `reliability`, `tester_feedback` or `docs` (the response lists them as `sort_parts`, and says which one it applied as `sort`). Modules with a score for that part come first, highest first; a tie goes to the higher overall score, then the module key. A module with no score for that part goes last, so missing data never wins or loses a place on its own. New modules are not sorted: they are never ranked. Any other `sort` value is refused with a 400. Without `sort`, the listed modules are in the ranking below; New stays in key order. The rule is `sortByPart` in `src/bongos/module-listing.js`.
400
400
  - **The ranking, and a maximum price** (task 1003808, owner decision 2026-10-23). Without `sort`, listed modules are ranked **quality first**: highest overall score first; between equal scores, free modules first, then the lowest cash price, then the lowest credit price, then the module key. Price never buys a better place, it only orders modules the score could not tell apart (and it is never an input to the score, ADR 0343 D6). The response states the rule as `ranking`, and each entry carries its current `price` (`cents`, `credits`, `free`). A buyer may add `?max_cents=` and/or `?max_credits=` (whole numbers, 0 or more): modules that cost more are left out of both lists, free modules always stay, and a priced module stays when it is offered at or under a cap the buyer set. The applied caps come back as `max_price`. The rules are `rankListed` and `withinMaxPrice` in `src/bongos/module-listing.js`.
401
401
  - **Why a module is not listed** (task 1003810). `GET /store/modules/:key/listing` says where one module stands, judging the same newest version the catalog does: `listed` (true or false), a `reason` (`not_assessed`, `security_not_passed`, `no_score`, `below_floor`, `delisted`, `no_version`, `new` or `at_or_above_floor`), `why` in a sentence and `next_step` saying what would change it. A failed Security gate is told apart from one that has not finished, and names what it found to the module's author only (to anyone else it would point at a weakness not fixed yet); below the floor names the score, the floor and the weakest parts. It carries the same `parts`, `strengths`, `weaknesses` and `unmeasured` as the catalog, plus `security`. Any signed-in builder can read it. Being past the cap of ten per category will be a reason of its own once the cap exists (task 1003809). The rule is `explainPlacement` in `src/bongos/module-listing.js`.
402
+ - **Sub-categories are approved before they count** (task 1004355, ADR 0342 D3). A module names `category: { parent, sub }` in `module.json`; `GET /store/subcategories` lists the approved ones under each of the eight parents. A `sub` the store has not seen under that parent is **proposed** at publish (labelled from its key) and the publish answer's `category` says so (`status: "proposed"`, `live: false`): until a Metic+ person decides, the module sits under its parent only and counts toward no cap. The review queue (`GET /store/subcategories/queue`, atom `module.subcategory.review`, metic + archon via `government_033`) shows each proposal with the modules that named it and its near-duplicate names; `POST /store/subcategories/:id/approve { label?, sub_key? }` makes it live, renamed first if asked (a rename onto an existing key is refused — merge instead), and `POST /store/subcategories/:id/merge { into }` folds it into an approved one and moves its modules there. A later publish naming a merged key lands on its target. Where a module sits is `store_module_categories` (core_278), apart from the manifest; `readModuleCategories` in `src/bongos/module-subcategories.js` is the read the cap uses.
402
403
  - **Testing new versions first** (task 1003802, [ADR 0344](adr/0344-a-tester-is-a-project-that-opts-in-once.md) D1). A project can choose to get each new version of its modules before everyone else. It is the project that opts in, never one builder for someone else's project, and never by default. `GET /modules/testing` returns `{ tester, general_only, updated_at, updated_by }` to anyone signed in. `POST /modules/testing { tester: true|false }` switches it, and `POST /modules/:key/general-only { general_only: true|false }` keeps one module on the versions everyone gets while the rest are tested. Both writes take the `module.enable` permission, the same one that turns modules on and off. Both are reversible: switching testing off puts the project back on general versions from its next update, and a module's "general only" mark is kept if testing is switched off and on again. The choice is stored in the project's own settings (`modules.tester`, `modules.general_only`); anything that cannot be read counts as not testing. `testsVersionsOf` in `src/bongos/module-testing.js` is the one answer to "does this project get the tester version of this module", which installs and updates use (task 1003805). The hall's Modules tab has the switch in its "Testing new versions" group, and the general-only control in each module's details.
403
404
 
404
405
  ### 7. Install from the store