@bongos/core 1.23.5 → 1.23.7

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 (36) hide show
  1. package/.bongos-core.json +66 -36
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +2 -0
  4. package/clients/bongos-client/index.cjs +2 -0
  5. package/clients/bongos-client/index.d.ts +4 -0
  6. package/clients/bongos-client/index.mjs +2 -0
  7. package/docs/adr/0356-a-recruit-invite-expires-30-days-after-it-is-sent-read-time.md +2 -1
  8. package/docs/api/openapi.json +96 -5
  9. package/docs/api-reference.md +3 -2
  10. package/docs/module-api-changelog.md +4 -0
  11. package/docs/modules-contract.md +3 -0
  12. package/modules/onboarding/routes/access-requests.js +10 -9
  13. package/package-lock.json +2 -2
  14. package/package.json +1 -1
  15. package/release-notes.json +16 -0
  16. package/scripts/bongos/login.js +13 -14
  17. package/scripts/bongos/module-remove.js +122 -0
  18. package/scripts/bongos/module-store-cli.js +182 -0
  19. package/scripts/bongos/module.js +42 -127
  20. package/scripts/gds/module-remove.js +6 -0
  21. package/scripts/gds/module-store-cli.js +6 -0
  22. package/src/bongos/auth-admission.js +3 -3
  23. package/src/bongos/middleware/rate-limit.js +1 -1
  24. package/src/bongos/module-listing.js +12 -2
  25. package/src/bongos/module-store.js +49 -1
  26. package/src/bongos/route-rank-check.js +3 -0
  27. package/src/bongos/routes/auth.js +7 -11
  28. package/src/bongos/routes/modules.js +32 -1
  29. package/src/module-api.js +1 -1
  30. package/tests/account_existence_rate_limit.mjs +7 -3
  31. package/tests/application_lifecycle.mjs +4 -3
  32. package/tests/bongos_login.mjs +15 -4
  33. package/tests/invite_expiry_db.mjs +1 -1
  34. package/tests/module_remove.mjs +121 -0
  35. package/tests/module_store_cli.mjs +1 -1
  36. package/tests/module_store_price.mjs +207 -0
package/.bongos-core.json CHANGED
@@ -2,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/bongos/package-core.js",
5
- "core_version": "1.23.5",
6
- "core_contract": "1.23.5",
7
- "source_commit": "ef90f2c64f5ccf45c2234df0071be94480b00ce3",
5
+ "core_version": "1.23.7",
6
+ "core_contract": "1.23.7",
7
+ "source_commit": "b3d8fd1d43987850a49b0262cddfb454592c3baa",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-10-26T23:08:05.615Z",
9
+ "built_at": "2026-10-26T23:49:17.830Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 616,
13
13
  "agent_docs_stubbed": 29,
14
- "functional_verbatim": 3554,
14
+ "functional_verbatim": 3560,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 4200,
20
- "tree_sha256": "99ecba7bea108a791c4ce0f59605662920c26580844031a96841acdc4855cda4",
19
+ "file_count": 4206,
20
+ "tree_sha256": "a3771c367fe15a46a3890c73c3a51d5233d9722c445cef33b41f0c5c2689906a",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -317,12 +317,12 @@
317
317
  {
318
318
  "path": "clients/bongos-client/README.md",
319
319
  "mode": "0000644",
320
- "sha256": "869af14d8b121b4738dc05522c0c659b363b08f2e55b595e096063958562917c"
320
+ "sha256": "627dcf2fadbf1ab61e7c6efcf6d343b8b6bdb99864dfb39e5941b3ebcb1cb03a"
321
321
  },
322
322
  {
323
323
  "path": "clients/bongos-client/bongos-client.global.js",
324
324
  "mode": "0000644",
325
- "sha256": "66be8c0256cbc1e0dceedbe68b609b76b47caacbae1d709fb94afc7867a20753"
325
+ "sha256": "0741b0ffb53b7b7622370f516588ee2dc0977b242e86caf403c63d26b8d122d7"
326
326
  },
327
327
  {
328
328
  "path": "clients/bongos-client/examples/hello-world.mjs",
@@ -332,17 +332,17 @@
332
332
  {
333
333
  "path": "clients/bongos-client/index.cjs",
334
334
  "mode": "0000644",
335
- "sha256": "6fd5c25f0950eac9e1bdb0d8798dccc5718ef1d3cc1082607121b2d26ac97b90"
335
+ "sha256": "6db6d05c604d48c3afa2915a30156dc48763795f4498467a75d026e49f1f407e"
336
336
  },
337
337
  {
338
338
  "path": "clients/bongos-client/index.d.ts",
339
339
  "mode": "0000644",
340
- "sha256": "fb18d3d0dfa564c7fdaa7cc1d0e18c16d835665e37fb00a7ce0bb56e73c2fe1d"
340
+ "sha256": "433a4ae717283cb60453bb10c34b1626fc9f0cc696a81ab7b6a884b2708a7e15"
341
341
  },
342
342
  {
343
343
  "path": "clients/bongos-client/index.mjs",
344
344
  "mode": "0000644",
345
- "sha256": "1a549fe8b3fb3f3377ae771c8d5b185d6db052063d655aa428e6457676e616f9"
345
+ "sha256": "3cb6d90344a0642f3be6ceb3385be63e71c8f7e0174bf3dbfc1d2196fac101ac"
346
346
  },
347
347
  {
348
348
  "path": "clients/bongos-client/package.json",
@@ -2242,7 +2242,7 @@
2242
2242
  {
2243
2243
  "path": "docs/adr/0356-a-recruit-invite-expires-30-days-after-it-is-sent-read-time.md",
2244
2244
  "mode": "0000644",
2245
- "sha256": "ccbad5d4326b069381db47ea3fc165f9a4020485e3d6ce346393a2f6711ce1f0"
2245
+ "sha256": "c9bffb1cc48a4f34a20571be61916a5d13f6f1d8bdb50c3c5071eb6ced68efba"
2246
2246
  },
2247
2247
  {
2248
2248
  "path": "docs/adr/0357-the-update-rule-set-on-deploy-is-the-one-the-sweep-follows.md",
@@ -2392,12 +2392,12 @@
2392
2392
  {
2393
2393
  "path": "docs/api-reference.md",
2394
2394
  "mode": "0000644",
2395
- "sha256": "1fdb6d8a5788e30d6277c2b4ad8226688b06202892ddb22685987b1bd795609a"
2395
+ "sha256": "4d024afe3537d84fede274feebe91e6d4c8cb668ae5bfbc6788dfdd9bede3f1f"
2396
2396
  },
2397
2397
  {
2398
2398
  "path": "docs/api/openapi.json",
2399
2399
  "mode": "0000644",
2400
- "sha256": "2939822457141134ad9b645095278bfd5026b35b42e49d1bd80791031022922c"
2400
+ "sha256": "c94977d1c916fc4e57bd0647463a431f2d5f7ef595803442475ce108c79841c3"
2401
2401
  },
2402
2402
  {
2403
2403
  "path": "docs/architecture.md",
@@ -2937,12 +2937,12 @@
2937
2937
  {
2938
2938
  "path": "docs/module-api-changelog.md",
2939
2939
  "mode": "0000644",
2940
- "sha256": "0ca024400f00e16a1e03ffca04d90242e414de5f70e10c7466cd0eee842deebc"
2940
+ "sha256": "91bc98777d143a46a4acdb3afc7662f9b6069fd5d2954f6b9aefd03d1d327f87"
2941
2941
  },
2942
2942
  {
2943
2943
  "path": "docs/modules-contract.md",
2944
2944
  "mode": "0000644",
2945
- "sha256": "4b622fadde0eb87d5b9b9b5117d2f895f443e036e164d208383e83ed0c17dab7"
2945
+ "sha256": "1ca002715220c25f7d270c3d87d3a5827454f4abec0f6ea813f9935d1b4ced2c"
2946
2946
  },
2947
2947
  {
2948
2948
  "path": "docs/onboarding/diagrams/01-task-lifecycle.mmd",
@@ -8212,7 +8212,7 @@
8212
8212
  {
8213
8213
  "path": "modules/onboarding/routes/access-requests.js",
8214
8214
  "mode": "0000644",
8215
- "sha256": "de63d8a97156b0079d91c945988baab73f82b50a0a2ed06f792749d21f6afc6d"
8215
+ "sha256": "c472b5060dfd0143275ff6769daac986a6cd46c7d722186653f9adaf641a83be"
8216
8216
  },
8217
8217
  {
8218
8218
  "path": "modules/onboarding/routes/onboarding.js",
@@ -10837,12 +10837,12 @@
10837
10837
  {
10838
10838
  "path": "package-lock.json",
10839
10839
  "mode": "0000644",
10840
- "sha256": "bdab863c7dbf290520a865fb25ca6bbb853a2b6581dc4e5f864188e5489abc0a"
10840
+ "sha256": "b2707b768f0b92534351f237e4c1f2ad46894471f0f639f770bfa0b361fa584f"
10841
10841
  },
10842
10842
  {
10843
10843
  "path": "package.json",
10844
10844
  "mode": "0000644",
10845
- "sha256": "aad2cfe7215eeae48f25d3e5ff2b2ff75521951b796eefe1c0559e826df1e9d9"
10845
+ "sha256": "2e911efb9044f425ef86de18b03ccadf10913f7db81eac836fc07ab901bd2011"
10846
10846
  },
10847
10847
  {
10848
10848
  "path": "public-docs/index.html",
@@ -10862,7 +10862,7 @@
10862
10862
  {
10863
10863
  "path": "release-notes.json",
10864
10864
  "mode": "0000644",
10865
- "sha256": "39dc035c346baea1e6712cf27eb0697dd387ec1644655740a0367b423ea7325f"
10865
+ "sha256": "56aff299cc54f61132be2b6d1877e50cc35282ed1e759726246d0ae0f131f343"
10866
10866
  },
10867
10867
  {
10868
10868
  "path": "scripts/bongos/CLAUDE.md",
@@ -11657,7 +11657,7 @@
11657
11657
  {
11658
11658
  "path": "scripts/bongos/login.js",
11659
11659
  "mode": "0000644",
11660
- "sha256": "2777903c4d5b5ad4549f87967d34c57b62a264f7990627735ca47d5a8e6b333d"
11660
+ "sha256": "0a13005b8a73dd31b46bdb6f7762f483654e85e060f37eb8e78d9a6088f7245c"
11661
11661
  },
11662
11662
  {
11663
11663
  "path": "scripts/bongos/main-audit.js",
@@ -11734,6 +11734,16 @@
11734
11734
  "mode": "0000644",
11735
11735
  "sha256": "0334f9d2e31559bbc661ae0be5404499719d76e670560fa5f4ba72d7c14d0ac7"
11736
11736
  },
11737
+ {
11738
+ "path": "scripts/bongos/module-remove.js",
11739
+ "mode": "0000644",
11740
+ "sha256": "9470fb2f45b4cd1d8467bc590de324a87365b7449f0d0bf74ab9baa7b65e8585"
11741
+ },
11742
+ {
11743
+ "path": "scripts/bongos/module-store-cli.js",
11744
+ "mode": "0000644",
11745
+ "sha256": "9634332ab9995fbb906d856fbf575cad3ad83923e4e724e888af5fd25d82b047"
11746
+ },
11737
11747
  {
11738
11748
  "path": "scripts/bongos/module-test-runner.js",
11739
11749
  "mode": "0000644",
@@ -11752,7 +11762,7 @@
11752
11762
  {
11753
11763
  "path": "scripts/bongos/module.js",
11754
11764
  "mode": "0000644",
11755
- "sha256": "cd383da37a1210e8581b04e9feda566c3958945a488b7187ba151fcc08566e97"
11765
+ "sha256": "34b1c30a3192d28f0260950e88f27a2bb44077d7917987697b9cdbb4093b590b"
11756
11766
  },
11757
11767
  {
11758
11768
  "path": "scripts/bongos/move-escalation.js",
@@ -13399,6 +13409,16 @@
13399
13409
  "mode": "0000644",
13400
13410
  "sha256": "053d6df274f015359f4eb52361aae55857f643672fc3746c941bfed348092b21"
13401
13411
  },
13412
+ {
13413
+ "path": "scripts/gds/module-remove.js",
13414
+ "mode": "0000644",
13415
+ "sha256": "26c003efeb97c9ed3860dbcd9d35132828e55655b5f911bc7767aac82768b5b2"
13416
+ },
13417
+ {
13418
+ "path": "scripts/gds/module-store-cli.js",
13419
+ "mode": "0000644",
13420
+ "sha256": "f7060060ae2ad7f74d3ee43ae3ac7bffe45de976c2ccfbf3ca5e91aecb896b8f"
13421
+ },
13402
13422
  {
13403
13423
  "path": "scripts/gds/module-test-runner.js",
13404
13424
  "mode": "0000644",
@@ -14302,7 +14322,7 @@
14302
14322
  {
14303
14323
  "path": "src/bongos/auth-admission.js",
14304
14324
  "mode": "0000644",
14305
- "sha256": "e0e8a5b10571aa0248486e8ae6f556439e385938ca5cee9255a93df69f8ba549"
14325
+ "sha256": "1f60d40b8157a9dcc9fcff239123955553ddfe11bb671386f0a0ec4f8ae0ee75"
14306
14326
  },
14307
14327
  {
14308
14328
  "path": "src/bongos/auth-config.js",
@@ -14422,7 +14442,7 @@
14422
14442
  {
14423
14443
  "path": "src/bongos/middleware/rate-limit.js",
14424
14444
  "mode": "0000644",
14425
- "sha256": "8881220efc4b990a41fc2463dc663a5d2325a28284d2b654f5b3bc453178e3da"
14445
+ "sha256": "8191011bfa63fcfa1a77383fbab08abdfa4a4584ac641fdf382af5423b06b087"
14426
14446
  },
14427
14447
  {
14428
14448
  "path": "src/bongos/module-entitlements.js",
@@ -14432,7 +14452,7 @@
14432
14452
  {
14433
14453
  "path": "src/bongos/module-listing.js",
14434
14454
  "mode": "0000644",
14435
- "sha256": "3f7013b4e59f72fc89663461d6d5457e412624ae3d4c1df1d2a3b9a8d9168d02"
14455
+ "sha256": "ef63c8be3af54a23b36647b2402517fe2fca4c6c1b0ba5a75d07a85cd39abc92"
14436
14456
  },
14437
14457
  {
14438
14458
  "path": "src/bongos/module-overrides.js",
@@ -14457,7 +14477,7 @@
14457
14477
  {
14458
14478
  "path": "src/bongos/module-store.js",
14459
14479
  "mode": "0000644",
14460
- "sha256": "a0a1eee5faf70e37a2ea9a5a20c1f12964bf6c1b08f4213925a3f66254cd7985"
14480
+ "sha256": "75c7e86fc1c0c9d6cffaa63a4c6654fe5191dab919dd988dc04903ca84c84382"
14461
14481
  },
14462
14482
  {
14463
14483
  "path": "src/bongos/module-subcategories.js",
@@ -14542,7 +14562,7 @@
14542
14562
  {
14543
14563
  "path": "src/bongos/route-rank-check.js",
14544
14564
  "mode": "0000644",
14545
- "sha256": "05ea4b0fd219d2e03969a26c100556b18567c9b331c8c0fb69befacd46714e0d"
14565
+ "sha256": "456217f8086f0748d4bf64536cc304b0124dd4d94783b627cfc5d9ed25e3d8b4"
14546
14566
  },
14547
14567
  {
14548
14568
  "path": "src/bongos/routes.js",
@@ -14567,7 +14587,7 @@
14567
14587
  {
14568
14588
  "path": "src/bongos/routes/auth.js",
14569
14589
  "mode": "0000644",
14570
- "sha256": "dd63e8d821c0c9e0bb0cb2568f41b8df53d10c4fb3e691ef10d5ce798650b752"
14590
+ "sha256": "d5ad4455493abf3777e51b6d82a186b810275ab05f8959a7fc13213dcebe9c3a"
14571
14591
  },
14572
14592
  {
14573
14593
  "path": "src/bongos/routes/backup.js",
@@ -14627,7 +14647,7 @@
14627
14647
  {
14628
14648
  "path": "src/bongos/routes/modules.js",
14629
14649
  "mode": "0000644",
14630
- "sha256": "32d325edb4dc0720d608051974045d5cec8fe70e82856c7f77797315ba3b1e09"
14650
+ "sha256": "e7e9c57d4b5c1513425c9349d477e5b1b07c5538cabc74f30d9f899c7962384e"
14631
14651
  },
14632
14652
  {
14633
14653
  "path": "src/bongos/routes/my-passkeys.js",
@@ -14717,7 +14737,7 @@
14717
14737
  {
14718
14738
  "path": "src/module-api.js",
14719
14739
  "mode": "0000644",
14720
- "sha256": "67513cbbff215d18612fd9c8102f4163311c21b526daa4627343e269b5e9c111"
14740
+ "sha256": "6b5b56cd550854c711eef1cd99f72c7c7f52a781bb0b6391845ff8a3fb1f78c9"
14721
14741
  },
14722
14742
  {
14723
14743
  "path": "src/module-loader/catalog.js",
@@ -14822,7 +14842,7 @@
14822
14842
  {
14823
14843
  "path": "tests/account_existence_rate_limit.mjs",
14824
14844
  "mode": "0000644",
14825
- "sha256": "41170e6fa3bc063770a39f4fe5b21e049ae91c8c4115b3b2152a7e464420fb6f"
14845
+ "sha256": "41b8175653a86b79a5dae1e0be041eb4a286720cb41cbcd069afe9bc78b172ac"
14826
14846
  },
14827
14847
  {
14828
14848
  "path": "tests/account_privacy_flags.mjs",
@@ -15037,7 +15057,7 @@
15037
15057
  {
15038
15058
  "path": "tests/application_lifecycle.mjs",
15039
15059
  "mode": "0000644",
15040
- "sha256": "239566c55a726433ed288dcdcb9728eaeb2d7803d8bbdd26b7bc77c68124334b"
15060
+ "sha256": "42a2fb6dae46ffc7b41ffca65be0014be65b46d73c5167b7299543f59387ee6a"
15041
15061
  },
15042
15062
  {
15043
15063
  "path": "tests/architect.mjs",
@@ -15402,7 +15422,7 @@
15402
15422
  {
15403
15423
  "path": "tests/bongos_login.mjs",
15404
15424
  "mode": "0000644",
15405
- "sha256": "4cbb034eea342befa92a3388f5e5c97abdee256e6c2b4681abd8c1f008ca2801"
15425
+ "sha256": "468694776fb15978ac7f716b7c7d434085a5da2331ffa28e274310195cf1e7a2"
15406
15426
  },
15407
15427
  {
15408
15428
  "path": "tests/branding.mjs",
@@ -17997,7 +18017,7 @@
17997
18017
  {
17998
18018
  "path": "tests/invite_expiry_db.mjs",
17999
18019
  "mode": "0000644",
18000
- "sha256": "8be6c683c443a6b0f6767fd68a93b22da73c011e009e5abae7872e8c61606054"
18020
+ "sha256": "711b2f2fdf012b9583edbac35f9bf8eac38425bfe794d8253871d2f2d3bfa034"
18001
18021
  },
18002
18022
  {
18003
18023
  "path": "tests/join_application_note.mjs",
@@ -18479,6 +18499,11 @@
18479
18499
  "mode": "0000644",
18480
18500
  "sha256": "51ba43c92ccd963cead34b5cad855fc4d19b4698e9a27bacaa904cd999776f9e"
18481
18501
  },
18502
+ {
18503
+ "path": "tests/module_remove.mjs",
18504
+ "mode": "0000644",
18505
+ "sha256": "c9557ecb6efe346334e6d190a41b04efad156bdf5c7ce773e01ccb35e31e5124"
18506
+ },
18482
18507
  {
18483
18508
  "path": "tests/module_route_rank.mjs",
18484
18509
  "mode": "0000644",
@@ -18492,7 +18517,7 @@
18492
18517
  {
18493
18518
  "path": "tests/module_store_cli.mjs",
18494
18519
  "mode": "0000644",
18495
- "sha256": "cf4df707f62fa142f106822c44404603f8ec32213d61b6707818f5649c443275"
18520
+ "sha256": "7b57aafabc5d9c3fa568c88157e3049befde2abaa1d54c69ac69520d6cea6efd"
18496
18521
  },
18497
18522
  {
18498
18523
  "path": "tests/module_store_delist.mjs",
@@ -18514,6 +18539,11 @@
18514
18539
  "mode": "0000644",
18515
18540
  "sha256": "5f0cb9cb6e3faf90d342e1d9e1767699b677b8630d874f1c2af9cb7f713ef530"
18516
18541
  },
18542
+ {
18543
+ "path": "tests/module_store_price.mjs",
18544
+ "mode": "0000644",
18545
+ "sha256": "52fe692c1991fa33388b15ff5622f5207b5314fb05d0aa474543d888bda36e66"
18546
+ },
18517
18547
  {
18518
18548
  "path": "tests/module_store_publish.mjs",
18519
18549
  "mode": "0000644",
@@ -5,7 +5,7 @@ A **generated**, zero-dependency typed client for the Bongos API — produced fr
5
5
  by hand; it regenerates when the spec changes, so it can never drift from the routes.
6
6
 
7
7
  - API version: **v1** (served at `/api/bongos/v1`)
8
- - 583 operations across 76 resource groups
8
+ - 584 operations across 76 resource groups
9
9
 
10
10
  ## Use it from your project
11
11
 
@@ -1251,6 +1251,8 @@ function createClient(opts = {}) {
1251
1251
  getStoreModulesKeyEntitlement: (args) => request("GET", "/store/modules/{key}/entitlement", { hasBody: false }, args),
1252
1252
  // GET /store/modules/{key}/listing — rank: any-builder — GET /store/modules/:key/listing
1253
1253
  getStoreModulesKeyListing: (args) => request("GET", "/store/modules/{key}/listing", { hasBody: false }, args),
1254
+ // POST /store/modules/{key}/price — rank: metic+archon — POST /store/modules/:key/price
1255
+ postStoreModulesKeyPrice: (args) => request("POST", "/store/modules/{key}/price", { hasBody: true }, args),
1254
1256
  // POST /store/modules/{key}/versions — rank: metic+archon — POST /store/modules/:key/versions
1255
1257
  postStoreModulesKeyVersions: (args) => request("POST", "/store/modules/{key}/versions", { hasBody: true }, args),
1256
1258
  // GET /store/modules/{key}/versions/{version}/channel — rank: any-builder — GET /store/modules/:key/versions/:version/channel
@@ -1250,6 +1250,8 @@ function createClient(opts = {}) {
1250
1250
  getStoreModulesKeyEntitlement: (args) => request("GET", "/store/modules/{key}/entitlement", { hasBody: false }, args),
1251
1251
  // GET /store/modules/{key}/listing — rank: any-builder — GET /store/modules/:key/listing
1252
1252
  getStoreModulesKeyListing: (args) => request("GET", "/store/modules/{key}/listing", { hasBody: false }, args),
1253
+ // POST /store/modules/{key}/price — rank: metic+archon — POST /store/modules/:key/price
1254
+ postStoreModulesKeyPrice: (args) => request("POST", "/store/modules/{key}/price", { hasBody: true }, args),
1253
1255
  // POST /store/modules/{key}/versions — rank: metic+archon — POST /store/modules/:key/versions
1254
1256
  postStoreModulesKeyVersions: (args) => request("POST", "/store/modules/{key}/versions", { hasBody: true }, args),
1255
1257
  // GET /store/modules/{key}/versions/{version}/channel — rank: any-builder — GET /store/modules/:key/versions/:version/channel
@@ -562,6 +562,8 @@ export interface PostStoreModulesKeyAcquiredRequest { version: string }
562
562
  export interface PostStoreModulesKeyAcquiredResponse { ok: boolean; entitlement: unknown }
563
563
  export interface PostStoreModulesKeyDelistRequest { reason?: string }
564
564
  export interface PostStoreModulesKeyDelistResponse { ok: boolean; module_key: unknown; status: unknown; delisted_at: unknown }
565
+ export interface PostStoreModulesKeyPriceRequest { cents?: unknown; credits?: unknown }
566
+ export interface PostStoreModulesKeyPriceResponse { ok: boolean; module_key: unknown; previous: unknown; price: unknown }
565
567
  export interface PostStoreModulesKeyVersionsResponse { ok: boolean; created_module: unknown; version: unknown; channel: unknown; category: unknown }
566
568
  export interface PostStoreModulesKeyVersionsVersionErrorCountRequest { day: string; count: number }
567
569
  export interface PostStoreModulesKeyVersionsVersionInstallReportRequest { ok: boolean; error_code?: string }
@@ -1810,6 +1812,8 @@ export interface BongosClient {
1810
1812
  getStoreModulesKeyEntitlement(args?: RequestArgs): Promise<ApiResponse>;
1811
1813
  /** GET /store/modules/{key}/listing — rank: any-builder */
1812
1814
  getStoreModulesKeyListing(args?: RequestArgs): Promise<ApiResponse>;
1815
+ /** POST /store/modules/{key}/price — rank: metic+archon */
1816
+ postStoreModulesKeyPrice(args?: RequestArgs & { body?: PostStoreModulesKeyPriceRequest }): Promise<PostStoreModulesKeyPriceResponse>;
1813
1817
  /** POST /store/modules/{key}/versions — rank: metic+archon */
1814
1818
  postStoreModulesKeyVersions(args?: RequestArgs): Promise<PostStoreModulesKeyVersionsResponse>;
1815
1819
  /** GET /store/modules/{key}/versions/{version}/channel — rank: any-builder */
@@ -1247,6 +1247,8 @@ export function createClient(opts = {}) {
1247
1247
  getStoreModulesKeyEntitlement: (args) => request("GET", "/store/modules/{key}/entitlement", { hasBody: false }, args),
1248
1248
  // GET /store/modules/{key}/listing — rank: any-builder — GET /store/modules/:key/listing
1249
1249
  getStoreModulesKeyListing: (args) => request("GET", "/store/modules/{key}/listing", { hasBody: false }, args),
1250
+ // POST /store/modules/{key}/price — rank: metic+archon — POST /store/modules/:key/price
1251
+ postStoreModulesKeyPrice: (args) => request("POST", "/store/modules/{key}/price", { hasBody: true }, args),
1250
1252
  // POST /store/modules/{key}/versions — rank: metic+archon — POST /store/modules/:key/versions
1251
1253
  postStoreModulesKeyVersions: (args) => request("POST", "/store/modules/{key}/versions", { hasBody: true }, args),
1252
1254
  // GET /store/modules/{key}/versions/{version}/channel — rank: any-builder — GET /store/modules/:key/versions/:version/channel
@@ -5,6 +5,7 @@
5
5
  - **Task:** [task 1002969](https://cloudbongos.com/builders#/task/1002969) (goal 1000110)
6
6
  - **Builds on:** privacy spec D4 (`docs/specs/<redacted>.md`; the owner signed the 30 days), [ADR 0335](0335-an-invite-is-a-pre-approved-row-on-the-projects-own-instance.md) D2 (an invite is a `kind='invite'` row on the project's own instance), [ADR 0353](0353-a-hub-invite-is-a-notice-of-the-projects-own-invite.md) (the hub's `pending` row is a notice of that invite)
7
7
  - **Amends:** ADR 0335 D2.5 ("no admission path reads `kind`")
8
+ - **Amended by:** [ADR 0217](0217-rank-gates-inviting-not-viewing.md) D4, as built in [task 1003359](https://cloudbongos.com/builders#/task/1003359) — the public status probe answers `not_admitted`, not `expired`, for a lapsed invite, and `bongos login` no longer stops on it. In practice the CLI never saw `expired`: it files a request first, and that newer pending row is what the probe read.
8
9
  - **Decided with:** the owner split the original task on 2026-09-30. `seen_at` is [task 1004443](https://cloudbongos.com/builders#/task/1004443); the decline relay is [task 1004444](https://cloudbongos.com/builders#/task/1004444).
9
10
 
10
11
  ## Context
@@ -23,7 +24,7 @@ An invite writes an `invited` row, and the sign-in gate admits any login holding
23
24
  - The gate refuses it. The public "already approved?" check and the standing-invite probe no longer count it, so the invitee may apply and the recruiter can re-invite.
24
25
  - A re-invite writes a fresh row, which opens a fresh door. The expired row stays as history.
25
26
  - The queue marks it `expired` with `expires_at`. The outstanding-invite list keeps it so the sender knows to invite again, and drops it once a newer invite for that login is live. The hall's queue shows "Invite expired" and offers no rescind.
26
- - The public status probe answers `expired` (`admitted: false`), and `bongos login` stops waiting on it. An older CLI treats an unknown status as "wait" and times out, which is no worse than before.
27
+ - The public status probe answers `expired` (`admitted: false`), and `bongos login` stops waiting on it. An older CLI treats an unknown status as "wait" and times out, which is no worse than before. *(Superseded: it now answers `not_admitted` — see "Amended by" above.)*
27
28
  - A rescind that leaves only expired rows clears the hub notice.
28
29
 
29
30
  **D5 — The hub's notice follows the door.** `listPendingInvitesForAccount` hides a `pending` notice older than the same 30 days (its `joined_at` is when the project sent the notice). `inviteToProject` restamps `joined_at` only on a `pending` row that is already past the window, so a re-invite shows again. A re-send while the invite is live changes nothing, and a real membership is never touched.
@@ -505,7 +505,7 @@
505
505
  "access-requests"
506
506
  ],
507
507
  "summary": "GET /access-requests/status",
508
- "description": "GET /access-requests/status?github_login=X — PUBLIC. The `bongos login` CLI polls this after filing a request: the device flow's not_approved is TERMINAL (the device_code is spent), so the CLI can't re-poll the flow — it polls this instead, then restarts sign-in once admitted. Exposes nothing the public POST above doesn't already reveal through its 409s (already_member / already_approved). status ∈ member | invited | expired | pending | dismissed | none; admitted = member||invited. `expired` is an invite past its 30 days (task 1002969): the door is shut, and the CLI stops waiting rather than polling a door that will not open. rank: public — a would-be builder is by definition outside the system. Declared BEFORE `GET /access-requests` so the literal `/status` path is matched as its own route (not swallowed as a query on the list route). RATE-LIMITED (task 1003339 / ADR 0209). Public + unthrottled, this was a login→membership oracle an attacker could script at line rate to learn who builds here. accountExistenceReadRateLimit is the doorway's SHARED 120/60s per-IP budget: core's GET /auth/web/admission-status (the boolean twin of this answer) mounts the very same instance, so the two cannot be alternated for double the rate. The limiter runs before the queries, so a refused probe costs no DB work. Whether the ANSWER should also collapse — so a caller who is not the subject cannot tell pending/dismissed/none apart — is the owner's call and is filed as a blocker, not decided here.\n\n**Rank:** `public` — No authentication — any caller.",
508
+ "description": "GET /access-requests/status?github_login=X — PUBLIC. The `bongos login` CLI polls this after filing a request: the device flow's not_approved is TERMINAL (the device_code is spent), so the CLI can't re-poll the flow — it polls this instead, then restarts sign-in once admitted. Exposes nothing the public POST above doesn't already reveal through its 409s (already_member / already_approved). status ∈ member | invited | not_admitted; admitted = member||invited (ADR 0217 D4, task 1003359). `not_admitted` covers pending, declined, a lapsed invite and no request at all, so a caller who is not the subject cannot learn that someone applied here or was turned down. It does NOT close the membership oracle — `admitted` still answers that, and the limiter below is what bounds enumeration. A lapsed invite answering `not_admitted` rather than `expired` amends ADR 0356 D4. rank: public — a would-be builder is by definition outside the system. Declared BEFORE `GET /access-requests` so the literal `/status` path is matched as its own route (not swallowed as a query on the list route). RATE-LIMITED (task 1003339 / ADR 0209). Public + unthrottled, this was a login→membership oracle an attacker could script at line rate to learn who builds here. accountExistenceReadRateLimit is the doorway's SHARED 120/60s per-IP budget: core's GET /auth/web/admission-status (the boolean twin of this answer) mounts the very same instance, so the two cannot be alternated for double the rate. The limiter runs before the queries, so a refused probe costs no DB work.\n\n**Rank:** `public` — No authentication — any caller.",
509
509
  "x-rank": "public",
510
510
  "x-source": "modules/onboarding/routes/access-requests.js",
511
511
  "responses": {
@@ -1656,7 +1656,7 @@
1656
1656
  "auth"
1657
1657
  ],
1658
1658
  "summary": "GET /auth/web/admission-status",
1659
- "description": "GET /auth/web/admission-status?login=X — the probe renderAccessPendingPage polls (task 1002097). Answers ONE question: would this login be admitted right now? It mirrors the real gate via auth.webAdmissionStatus so the page can never invite someone in the gate would refuse. rank: public — it must be reachable by a visitor with no session (that is the entire situation it exists for). It grants nothing: admission still runs through the normal sign-in, and this only reads state. WHAT IT LEAKS, stated plainly (task 1002430 — an earlier version of this comment claimed the opposite and was wrong). Returning only a boolean does NOT make it non-enumerable: with open enrollment off, and for any login that is not the configured firstAdmin, `admitted:true` means that login is a member or has been invited. That reveals account existence to an unauthenticated caller. IT IS NOW THROTTLED (task 1003339 / ADR 0209). It used to say \"NOT throttled\" here, and that was true: rateLimitMiddleware returns early for non-write methods, so GETs bypassed it entirely and the page's 3s poll bounded the honest client, not an attacker. accountExistenceReadRateLimit below is the ceiling — 120 reads/60s per IP, and the SAME instance is mounted on the onboarding module's GET /access-requests/status, which is this route's strictly richer twin (member|invited|pending|dismissed|none plus its own `admitted`). One budget across both, because they are one oracle: hardening either alone would accomplish nothing, and giving each its own budget would just let an enumerator alternate for double the rate. WHAT IS STILL OPEN and is deliberately NOT decided here: whether the answer itself should collapse, so a caller who is not the subject cannot tell pending/dismissed/none apart from a bare \"not admitted\". That changes what a real invited user sees during onboarding, so it is the owner's call — filed as a blocker against task 1003339, not settled by a comment in this file. That endpoint is also why this one exists: it models neither the firstAdmin bypass nor open enrollment, so it reports the OWNER as 'none' forever — the exact case this page has to resolve.\n\n**Rank:** `public` — No authentication — any caller.",
1659
+ "description": "GET /auth/web/admission-status?login=X — the probe renderAccessPendingPage polls (task 1002097). Answers ONE question: would this login be admitted right now? It mirrors the real gate via auth.webAdmissionStatus so the page can never invite someone in the gate would refuse. rank: public — it must be reachable by a visitor with no session (that is the entire situation it exists for). It grants nothing: admission still runs through the normal sign-in, and this only reads state. WHAT IT LEAKS, stated plainly (task 1002430 — an earlier version of this comment claimed the opposite and was wrong). Returning only a boolean does NOT make it non-enumerable: with open enrollment off, and for any login that is not the configured firstAdmin, `admitted:true` means that login is a member or has been invited. That reveals account existence to an unauthenticated caller. IT IS NOW THROTTLED (task 1003339 / ADR 0209). It used to say \"NOT throttled\" here, and that was true: rateLimitMiddleware returns early for non-write methods, so GETs bypassed it entirely and the page's 3s poll bounded the honest client, not an attacker. accountExistenceReadRateLimit below is the ceiling — 120 reads/60s per IP, and the SAME instance is mounted on the onboarding module's GET /access-requests/status, which is this route's strictly richer twin (member|invited|not_admitted plus its own `admitted`). One budget across both, because they are one oracle: hardening either alone would accomplish nothing, and giving each its own budget would just let an enumerator alternate for double the rate. That twin once told pending/declined/none apart too; it collapsed to `not_admitted` on the owner's call (ADR 0217 D4, task 1003359). That endpoint is also why this one exists: it models neither the firstAdmin bypass nor open enrollment, so it reports the OWNER as 'not_admitted' forever — the exact case this page has to resolve.\n\n**Rank:** `public` — No authentication — any caller.",
1660
1660
  "x-rank": "public",
1661
1661
  "x-source": "src/bongos/routes/auth.js",
1662
1662
  "responses": {
@@ -23827,6 +23827,72 @@
23827
23827
  ]
23828
23828
  }
23829
23829
  },
23830
+ "/store/modules/{key}/price": {
23831
+ "post": {
23832
+ "operationId": "post_store_modules_key_price",
23833
+ "tags": [
23834
+ "store"
23835
+ ],
23836
+ "summary": "POST /store/modules/:key/price",
23837
+ "description": "POST /api/bongos/store/modules/:key/price — the module's author sets its price (task 1004894, criterion wa5-module-economy). Body: { cents?, credits? }, each a whole number of 0 or more, or null for \"not offered in that currency\"; at least one is given, and 0 in either is free. Appends a row to the price history; the newest is what the catalog shows and what acquire charges. A paid price may be set before buying exists (owner, 2026-10-26): the catalog marks it not yet buyable. Price never moves the score (ADR 0343 D6). The audit middleware records who changed it and to what. rank: metic+archon — the same gate as publishing (module.submit), plus the author check inside setPrice: only the person who published the module prices it.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `module.submit` (all required).",
23838
+ "x-rank": "metic+archon",
23839
+ "x-source": "src/bongos/routes/modules.js",
23840
+ "x-permissions": [
23841
+ "module.submit"
23842
+ ],
23843
+ "parameters": [
23844
+ {
23845
+ "name": "key",
23846
+ "in": "path",
23847
+ "required": true,
23848
+ "schema": {
23849
+ "type": "string"
23850
+ },
23851
+ "description": "Path parameter `key`."
23852
+ }
23853
+ ],
23854
+ "requestBody": {
23855
+ "required": false,
23856
+ "content": {
23857
+ "application/json": {
23858
+ "schema": {
23859
+ "$ref": "#/components/schemas/PostStoreModulesKeyPriceRequest"
23860
+ }
23861
+ }
23862
+ },
23863
+ "x-validated": true
23864
+ },
23865
+ "responses": {
23866
+ "200": {
23867
+ "description": "Success.",
23868
+ "content": {
23869
+ "application/json": {
23870
+ "schema": {
23871
+ "$ref": "#/components/schemas/PostStoreModulesKeyPriceResponse"
23872
+ }
23873
+ }
23874
+ }
23875
+ },
23876
+ "400": {
23877
+ "$ref": "#/components/responses/ValidationFailed"
23878
+ },
23879
+ "401": {
23880
+ "$ref": "#/components/responses/Unauthorized"
23881
+ },
23882
+ "403": {
23883
+ "$ref": "#/components/responses/Forbidden"
23884
+ },
23885
+ "404": {
23886
+ "$ref": "#/components/responses/NotFound"
23887
+ }
23888
+ },
23889
+ "security": [
23890
+ {
23891
+ "builderSession": []
23892
+ }
23893
+ ]
23894
+ }
23895
+ },
23830
23896
  "/store/modules/{key}/versions": {
23831
23897
  "post": {
23832
23898
  "operationId": "post_store_modules_key_versions",
@@ -36055,6 +36121,31 @@
36055
36121
  "delisted_at"
36056
36122
  ]
36057
36123
  },
36124
+ "PostStoreModulesKeyPriceRequest": {
36125
+ "type": "object",
36126
+ "properties": {
36127
+ "cents": {},
36128
+ "credits": {}
36129
+ },
36130
+ "additionalProperties": false
36131
+ },
36132
+ "PostStoreModulesKeyPriceResponse": {
36133
+ "type": "object",
36134
+ "properties": {
36135
+ "ok": {
36136
+ "type": "boolean"
36137
+ },
36138
+ "module_key": {},
36139
+ "previous": {},
36140
+ "price": {}
36141
+ },
36142
+ "required": [
36143
+ "ok",
36144
+ "module_key",
36145
+ "previous",
36146
+ "price"
36147
+ ]
36148
+ },
36058
36149
  "PostStoreModulesKeyVersionsResponse": {
36059
36150
  "type": "object",
36060
36151
  "properties": {
@@ -37292,9 +37383,9 @@
37292
37383
  "description": "A required dependency/feature is not configured or is temporarily down."
37293
37384
  }
37294
37385
  },
37295
- "x-endpoint-count": 583,
37296
- "x-schema-count": 626,
37386
+ "x-endpoint-count": 584,
37387
+ "x-schema-count": 628,
37297
37388
  "x-undocumented-bodies": 12,
37298
- "x-response-schemas": 405,
37389
+ "x-response-schemas": 406,
37299
37390
  "x-generated-by": "scripts/bongos/gen-api-docs.js"
37300
37391
  }
@@ -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. 583 endpoints across 98 route files.
5
+ > **Generated from the live route files** — the route file is authoritative. 584 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` (23)
866
+ ## `store` (24)
867
867
 
868
868
  | Method | Path | Rank | Body | Description |
869
869
  |---|---|---|---|---|
@@ -874,6 +874,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
874
874
  | POST | `/api/bongos/store/modules/:key/delist` | `metic+archon` | `reason` | POST /api/bongos/store/modules/:key/delist — a Metic+ person takes a module out of the store (task 1003797, ADR 0338 D2). |
875
875
  | GET | `/api/bongos/store/modules/:key/entitlement` | `any-builder` | — | |
876
876
  | GET | `/api/bongos/store/modules/:key/listing` | `any-builder` | — | GET /api/bongos/store/modules/:key/listing — whether one module is in the catalog and why (task 1003810): the reason in a sentence, what … |
877
+ | POST | `/api/bongos/store/modules/:key/price` | `metic+archon` | `cents`, `credits` | POST /api/bongos/store/modules/:key/price — the module's author sets its price (task 1004894, criterion wa5-module-economy). |
877
878
  | POST | `/api/bongos/store/modules/:key/versions` | `metic+archon` | _undocumented_ | POST /api/bongos/store/modules/:key/versions — publish one module version to the store (task 1004271, ADR 0338 D1). |
878
879
  | GET | `/api/bongos/store/modules/:key/versions/:version/channel` | `any-builder` | — | GET /api/bongos/store/modules/:key/versions/:version/channel — where a version stands on the promotion gate (task 1003804, ADR 0344 D3): … |
879
880
  | POST | `/api/bongos/store/modules/:key/versions/:version/error-count` | `any-builder` | `day`, `count` | |
@@ -3227,5 +3227,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
3227
3227
  landed since 1.23.3 with no explicit bump. run 40406624494. (task 1002620)
3228
3228
  1.23.5 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3229
3229
  landed since 1.23.4 with no explicit bump. run 40417462733. (task 1002620)
3230
+ 1.23.6 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3231
+ landed since 1.23.5 with no explicit bump. run 40420658396. (task 1002620)
3232
+ 1.23.7 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3233
+ landed since 1.23.6 with no explicit bump. run 40422218359. (task 1002620)
3230
3234
  ---------------------------------------------------------------------------
3231
3235
  ```
@@ -400,6 +400,7 @@ Run `bongos upgrade` to confirm coreVersion compatibility, then `node scripts/bo
400
400
  - **Tester feedback** (task 1003806, ADR 0344 D5). While a version is on the tester channel, what tester projects report about it becomes its Tester feedback part: the share of testers whose install worked and who hit no runtime errors, blended with ten neutral testers so a small sample cannot score high — `round(100 × (went well + 5) / (testers + 10))`. Reports from after the version left the tester channel, and the author's own, never count; under 3 testers it is not scored yet. The rule is `assessTesterFeedback` in `scripts/bongos/module-assess-installs.js`.
401
401
  - **What a module is strong and weak at** (task 1003799, ADR 0343 D4). Each catalog entry also carries `parts` (the current result of Tests, Clean installs, Reliability, Tester feedback and How-to) and a reading of them: `strengths` (a part scored **80 or more**), `weaknesses` (a part scored **below the floor**) and `unmeasured` (a part with no score yet, which is never counted as weak). Each strength and weakness has a one-sentence `detail` saying what the number rests on. Security is not among them: every listed version has passed it. The reading is `readParts` in `src/bongos/module-listing.js`; the hall's Modules tab shows it in its "In the store" group, so two modules with the same overall score can be told apart.
402
402
  - **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`.
403
+ - **Setting a price** (task 1004894). A module's author sets its price with `bongos module price <key> [--cents N] [--credits N]` (or the same two flags on `bongos module publish`, which sets the price right after the upload), or `POST /store/modules/:key/price { cents?, credits? }` (the `module.submit` permission, the same one that publishes, and only the author passes; a delisted module takes no new price). Each amount is a whole number of 0 or more, or null for "not offered in that currency"; at least one is given, and 0 in either makes the module free. Every change appends a row to the price history (in one transaction, the module row locked) naming who set it and when, so the history is the audit of what each price replaced, and the newest is current: it is what the catalog shows and what acquire charges. A paid price may be set before buying exists (owner, 2026-10-26): the catalog lists the module with its price and `buyable: false`, `bongos module store` prints it as not yet buyable, and acquire keeps refusing it without an entitlement. When buying ships (task 1003788), `BUYING_OPEN` in `src/bongos/module-listing.js` is the one switch to flip. Price is never an input to the score (ADR 0343 D6).
403
404
  - **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`.
404
405
  - **The cap: at most ten per sub-category** (task 1003809, [ADR 0342](adr/0342-module-categories-are-eight-parents-with-approved-sub-categories.md) D2). Above the floor, at most **ten** modules are listed in each approved sub-category: the first ten in the ranking above, so a buyer's `sort` or maximum price reorders or narrows those ten but never lets the eleventh in. The cap is never applied to a parent. A module whose sub-category is only proposed is listed under its parent and counts toward no cap, and a New module is not ranked, so it neither takes a slot nor is held by one. The catalog returns the cap as `cap`. The rule is `applyCap` in `src/bongos/module-listing.js`.
405
406
  - **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`, `over_cap`, `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. Past the cap it is `over_cap`, which names the score, the module's place in its sub-category and the cap, so an author can tell being eleventh apart from being below the floor. Both responses carry the module's `category` (`parent`, `parent_title`, and `sub` and `label` once its sub-category is approved). The rule is `explainPlacement` in `src/bongos/module-listing.js`.
@@ -423,6 +424,8 @@ Run `bongos upgrade` to confirm coreVersion compatibility, then `node scripts/bo
423
424
  - The new version is written beside the old one and swapped in; any failure leaves the installed version in place. Files the new version dropped are gone afterwards.
424
425
  - The on/off switch is untouched. A module bundled with the core is refused; it updates with the core (`bongos upgrade`).
425
426
 
427
+ `bongos module remove <key>` (task 1004896) takes a store module off this project. It refuses a core-shipped module, a module that is switched on (in config/env, or in the hall's Modules tab when the server answers), and a key the store does not know — that is the project's own code, not a store copy. It never deletes: the folder moves to `var/removed-modules/<key>-<version>-<stamp>/` (git-ignored), and the output says how to put it back. The module's database tables are left in place, so reinstalling brings its data back (owner, 2026-10-26), and the entitlement is untouched, so `bongos module install` puts it back without paying again. Nothing on the server is written: update checks and install reports read the modules on disk.
428
+
426
429
  **`bongos upgrade` says which installed modules have an update** (task 1003787), below its compatibility rows, so one command answers "what is stale here" for the core and its modules. It asks `GET /store/modules/latest?keys=…` for the newest version of every module here with an exact version — a read that takes no entitlement — and lists each newer one with `bongos module update <key>`, or says it waits for the core when that version needs a newer one. A delisted module is flagged as getting no further versions (ADR 0338 D2). None of this changes the exit code: an incompatible enabled module still blocks and nothing else does. With no session or no reachable store it says "not checked" and the pre-check stands; `--no-store` skips the read.
427
430
 
428
431
  ## Carving an existing core domain (the `db.js` per-domain pattern)