@bongos/core 1.20.21 → 1.20.23

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 (47) hide show
  1. package/.bongos-core.json +78 -48
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +4 -2
  4. package/clients/bongos-client/index.cjs +4 -2
  5. package/clients/bongos-client/index.d.ts +8 -4
  6. package/clients/bongos-client/index.mjs +4 -2
  7. package/docs/adr/0335-an-invite-is-a-pre-approved-row-on-the-projects-own-instance.md +1 -0
  8. package/docs/adr/0353-a-hub-invite-is-a-notice-of-the-projects-own-invite.md +70 -0
  9. package/docs/adr/README.md +1 -0
  10. package/docs/api/openapi.json +137 -72
  11. package/docs/api-reference.md +6 -5
  12. package/docs/architecture.md +14 -0
  13. package/docs/copy-inventory.md +196 -196
  14. package/docs/copy-registry.json +250 -250
  15. package/docs/module-api-changelog.md +4 -0
  16. package/docs/page-inventory.json +2 -1
  17. package/docs/page-readings.json +310 -306
  18. package/migrations/core_265_module_assessments.sql +97 -0
  19. package/modules/hall-ui/public/watch.js +23 -0
  20. package/modules/onboarding/routes/access-requests.js +36 -6
  21. package/modules/platform-identity/invite-notices.js +61 -0
  22. package/modules/platform-identity/module.json +1 -1
  23. package/modules/platform-identity/platform-identity.js +12 -18
  24. package/modules/platform-identity/routes/my-projects.js +6 -15
  25. package/modules/platform-identity/routes/projects.js +22 -15
  26. package/modules/platform-identity/routes/sso-invites.js +82 -0
  27. package/modules/platform-identity/tests/platform-identity.mjs +14 -11
  28. package/modules/public-landing/public/assets/cosmos.css +5 -0
  29. package/modules/public-landing/public/projects.html +35 -29
  30. package/modules/public-landing/public/projects.states.json +1 -0
  31. package/modules/ui-design/kit/serve.js +9 -0
  32. package/package-lock.json +2 -2
  33. package/package.json +1 -1
  34. package/release-notes.json +12 -0
  35. package/src/bongos/auth-admission.js +40 -0
  36. package/src/bongos/auth.js +2 -1
  37. package/src/module-api.js +8 -1
  38. package/tests/access_requests_invite.mjs +39 -1
  39. package/tests/application_lifecycle.mjs +41 -1
  40. package/tests/catalog_only_client.mjs +5 -3
  41. package/tests/hub_invite_notices.mjs +261 -0
  42. package/tests/module_api.mjs +1 -0
  43. package/tests/module_assessments_migration.mjs +96 -0
  44. package/tests/my_projects_surface.mjs +11 -1
  45. package/tests/project_invite_ownership.mjs +12 -7
  46. package/tests/project_invite_ui.mjs +42 -45
  47. package/tests/watch_applications_queue.mjs +33 -2
package/.bongos-core.json CHANGED
@@ -2,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.20.21",
6
- "core_contract": "1.20.21",
7
- "source_commit": "21843bf1398d569536fc45f748fae3c195be4ba2",
5
+ "core_version": "1.20.23",
6
+ "core_contract": "1.20.23",
7
+ "source_commit": "53e1513a015b5e684c3706c493675c2fd9f890b0",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-30T15:20:08.598Z",
9
+ "built_at": "2026-09-30T16:54:47.028Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
- "docs_redacted": 557,
12
+ "docs_redacted": 558,
13
13
  "agent_docs_stubbed": 25,
14
- "functional_verbatim": 2638,
14
+ "functional_verbatim": 2643,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 3221,
20
- "tree_sha256": "07ce496b1770bfc67bcd98bdf44e2bc6a47d1f869682f5fba2db1ded196d0b07",
19
+ "file_count": 3227,
20
+ "tree_sha256": "be76bb6bac10f8f8e03cbc19031c0a0e2866729e3ee9cfce9bf0b9a0704a0c5d",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -327,12 +327,12 @@
327
327
  {
328
328
  "path": "clients/bongos-client/README.md",
329
329
  "mode": "0000644",
330
- "sha256": "680d37464fa929b11cd7a02af6aa4fb144bedcde5134c3d81edac167d7b95980"
330
+ "sha256": "ff5585737cc618ca1a24c994a27411a6f4b60192d4904063771744788c0b8002"
331
331
  },
332
332
  {
333
333
  "path": "clients/bongos-client/bongos-client.global.js",
334
334
  "mode": "0000644",
335
- "sha256": "c6dac0dab2b2d5fb996568cff8ead84e7f6ac9e036c3fab6a47893c79a99f8f4"
335
+ "sha256": "f0a11cd6519a8ba62cb645c8d50da768f00f7252562a8b7a2f6e089ae0040f5e"
336
336
  },
337
337
  {
338
338
  "path": "clients/bongos-client/examples/hello-world.mjs",
@@ -342,17 +342,17 @@
342
342
  {
343
343
  "path": "clients/bongos-client/index.cjs",
344
344
  "mode": "0000644",
345
- "sha256": "baa7c833c3d18bf8250590fd94c499b3032ba05c6b9aa0af36b4660da295c799"
345
+ "sha256": "60673c698acad267f556a78b0e4d13b60d5357208a8772fe0d69cbd60b2b1c2c"
346
346
  },
347
347
  {
348
348
  "path": "clients/bongos-client/index.d.ts",
349
349
  "mode": "0000644",
350
- "sha256": "e9b2837873e241f20472ebde570b550718365a663282d4a85c255d98a8c8e21c"
350
+ "sha256": "2697fd1139e28cf2bf4ca8a5d4218d22060399bb4050cfdaa9457eae6c639b79"
351
351
  },
352
352
  {
353
353
  "path": "clients/bongos-client/index.mjs",
354
354
  "mode": "0000644",
355
- "sha256": "79240ff449f616e50aca58fc310a3b0670af84dc30d7a93991d84c0d0f4b87d2"
355
+ "sha256": "37028d9008247d9afd900f38437e1f8e335c19633c55379864177033d4f1bc89"
356
356
  },
357
357
  {
358
358
  "path": "clients/bongos-client/package.json",
@@ -2147,7 +2147,7 @@
2147
2147
  {
2148
2148
  "path": "docs/adr/0335-an-invite-is-a-pre-approved-row-on-the-projects-own-instance.md",
2149
2149
  "mode": "0000644",
2150
- "sha256": "ac7b159e2f894e9d9ce4c27506d84709aaa76851e325c5054fd45b619d2fea2a"
2150
+ "sha256": "f22adac7e27f2e83ece9311e44fca35edb0bd571440f108d28a9088775d0b93c"
2151
2151
  },
2152
2152
  {
2153
2153
  "path": "docs/adr/0336-a-guilds-record-is-the-sum-of-what-its-members-already-show.md",
@@ -2234,25 +2234,30 @@
2234
2234
  "mode": "0000644",
2235
2235
  "sha256": "d74b4f6262d4f23fd67f73771bbe8187e9cb92fa321b83ffb33ef03cf96d1236"
2236
2236
  },
2237
+ {
2238
+ "path": "docs/adr/0353-a-hub-invite-is-a-notice-of-the-projects-own-invite.md",
2239
+ "mode": "0000644",
2240
+ "sha256": "4445d9c306a22fb3b7e72467bedad262f4653c092bf4866f8a39410248da2724"
2241
+ },
2237
2242
  {
2238
2243
  "path": "docs/adr/README.md",
2239
2244
  "mode": "0000644",
2240
- "sha256": "943ac0f8c9c0b2dc5ef2a05241b0bac329c5fa986b09425841e12e62f4f9b936"
2245
+ "sha256": "6be10fb1925fd406729e08b4d604569b1123b7c5f6091300559df8532831ab69"
2241
2246
  },
2242
2247
  {
2243
2248
  "path": "docs/api-reference.md",
2244
2249
  "mode": "0000644",
2245
- "sha256": "36a01165649c2ca2f1e29c592332efe08f3cffcd926a547e8de4121ca05c0469"
2250
+ "sha256": "d6a19f9db87e4d936f317efe07e1a4dfaaa75c5a6797a4c3e4939634adec8494"
2246
2251
  },
2247
2252
  {
2248
2253
  "path": "docs/api/openapi.json",
2249
2254
  "mode": "0000644",
2250
- "sha256": "37b9cfbb4cce621c7ba60d358a66b618d60f6706b4b1c2dec7215c88451cff1c"
2255
+ "sha256": "f44cf9111baee003af84ab6c83503e97a4a723c4cb2ad59ad367cce75f8f717e"
2251
2256
  },
2252
2257
  {
2253
2258
  "path": "docs/architecture.md",
2254
2259
  "mode": "0000644",
2255
- "sha256": "682588d5c6f8dc16bfd25c23b441d333691678ac49f55500e6745fedf7c7851d"
2260
+ "sha256": "4149011e96acb427e32a55b0e48026ad0aa5a5ac2d3fa5054da9e5ef0e4f9a3f"
2256
2261
  },
2257
2262
  {
2258
2263
  "path": "docs/branding-contract.md",
@@ -2272,12 +2277,12 @@
2272
2277
  {
2273
2278
  "path": "docs/copy-inventory.md",
2274
2279
  "mode": "0000644",
2275
- "sha256": "08ef6d32a1c5cfea14e7ddd54a4e7ab2e34818199a45e0422a4fce31b72567a7"
2280
+ "sha256": "5034d255bbdcdd29c52831ce069561494a5ad7cc028b5e5711a0ab7a95a73a2b"
2276
2281
  },
2277
2282
  {
2278
2283
  "path": "docs/copy-registry.json",
2279
2284
  "mode": "0000644",
2280
- "sha256": "a405d02a6308a53e9322437f21daf86cb7e7f0d6d8b15a25639b7fdbcb7c2fd1"
2285
+ "sha256": "e75a8e11138b151ea81f378dde8f4e7fe395b44ba9369e7c98e865ff6516eeb4"
2281
2286
  },
2282
2287
  {
2283
2288
  "path": "docs/design/apex-pass-2-direction.md",
@@ -2777,7 +2782,7 @@
2777
2782
  {
2778
2783
  "path": "docs/module-api-changelog.md",
2779
2784
  "mode": "0000644",
2780
- "sha256": "5be7dcf0c4dbcd61706bbaf8fec0edc509d32b721b7be1abae250dcff896f95e"
2785
+ "sha256": "e4970a8a4af13ed70d73aafe8ec6b5c96999a8f23764dd32da86f96d538dfdc6"
2781
2786
  },
2782
2787
  {
2783
2788
  "path": "docs/modules-contract.md",
@@ -2862,12 +2867,12 @@
2862
2867
  {
2863
2868
  "path": "docs/page-inventory.json",
2864
2869
  "mode": "0000644",
2865
- "sha256": "9e3397a30b8fa81b392a32b65f275209cb67021b7e74a4700e0ae9ff6d3c5e6b"
2870
+ "sha256": "7a607ea5f1d29bd078ddbb6bfa5bcfb68e689095ca47bf290194cc7578794ed9"
2866
2871
  },
2867
2872
  {
2868
2873
  "path": "docs/page-readings.json",
2869
2874
  "mode": "0000644",
2870
- "sha256": "6ba44419e75dcb245e386e077eba647e744c56861b8b81c9002bbe7528369f0c"
2875
+ "sha256": "dd7227bb9daface49cfb1e2a8702cb81b95a5d19f0169038ddfd7a6c80e04225"
2871
2876
  },
2872
2877
  {
2873
2878
  "path": "docs/project-context.template.md",
@@ -3954,6 +3959,11 @@
3954
3959
  "mode": "0000644",
3955
3960
  "sha256": "2f578b536ed624b204fc201c0a5e8033a2417c189c9caaa8f2148408f76ae481"
3956
3961
  },
3962
+ {
3963
+ "path": "migrations/core_265_module_assessments.sql",
3964
+ "mode": "0000644",
3965
+ "sha256": "ea887c7bcd2863e69121bfcfa36cc62fedc77c5bd5a052e5868e2f7c99cbc103"
3966
+ },
3957
3967
  {
3958
3968
  "path": "modules/agents/CLAUDE.md",
3959
3969
  "mode": "0000644",
@@ -5717,7 +5727,7 @@
5717
5727
  {
5718
5728
  "path": "modules/hall-ui/public/watch.js",
5719
5729
  "mode": "0000644",
5720
- "sha256": "65e86c3526dc42eb82d6e7f9df88af5ff4bb8c78aef52326301e1cc57d6d7835"
5730
+ "sha256": "7a5ef4f34be6b0ab7f5b3fa34bf1666c17164a6331031cc908c2791109585b89"
5721
5731
  },
5722
5732
  {
5723
5733
  "path": "modules/hall-ui/public/watch.states.json",
@@ -6807,7 +6817,7 @@
6807
6817
  {
6808
6818
  "path": "modules/onboarding/routes/access-requests.js",
6809
6819
  "mode": "0000644",
6810
- "sha256": "69e0757e08ff5fdac708036cca67a6e11fd26820b683d7f255bc3c1c0e30cce9"
6820
+ "sha256": "1eaac1f70a519ca5a0ea71bb56ab6d33b582f5ff002faf5811fa6c1700480057"
6811
6821
  },
6812
6822
  {
6813
6823
  "path": "modules/onboarding/routes/onboarding.js",
@@ -6894,6 +6904,11 @@
6894
6904
  "mode": "0000644",
6895
6905
  "sha256": "2eb5577450d3bc1d67b7eab9a13256889aca945c6ac6a6a0ca919e195952b7a1"
6896
6906
  },
6907
+ {
6908
+ "path": "modules/platform-identity/invite-notices.js",
6909
+ "mode": "0000644",
6910
+ "sha256": "1391ac415c073c88942f517b4467514142e15fb0fadb2e156c44db4e75ee0763"
6911
+ },
6897
6912
  {
6898
6913
  "path": "modules/platform-identity/invite-suggestions.js",
6899
6914
  "mode": "0000644",
@@ -7037,7 +7052,7 @@
7037
7052
  {
7038
7053
  "path": "modules/platform-identity/module.json",
7039
7054
  "mode": "0000644",
7040
- "sha256": "48c9d82d9ec647430a97ba19f403b22e7eba13e16ff553b6bbd08691a8918a7b"
7055
+ "sha256": "c5efc01e3be9725d2f78d3cd5f89db68731c2958c627374fc005b5afb1fdc51f"
7041
7056
  },
7042
7057
  {
7043
7058
  "path": "modules/platform-identity/origin-containment.js",
@@ -7047,7 +7062,7 @@
7047
7062
  {
7048
7063
  "path": "modules/platform-identity/platform-identity.js",
7049
7064
  "mode": "0000644",
7050
- "sha256": "a823bd5e79795f93aa011470088b5840bf2ada6fb696fbf6b9b1c6c2ea85e5ef"
7065
+ "sha256": "9877fd84f74545d96473651cca9d00a76712507e76962c1891d3dd0c3d47e7b2"
7051
7066
  },
7052
7067
  {
7053
7068
  "path": "modules/platform-identity/pollers/code-sweep.js",
@@ -7122,7 +7137,7 @@
7122
7137
  {
7123
7138
  "path": "modules/platform-identity/routes/my-projects.js",
7124
7139
  "mode": "0000644",
7125
- "sha256": "56700357273c03d412afce1ef9937ba811257d0c5110f08d757bbef3e517a416"
7140
+ "sha256": "c9a3541f976aa872d4645dccb6101b5ca046a7ea963b1665e9cb6ed9d3796f5c"
7126
7141
  },
7127
7142
  {
7128
7143
  "path": "modules/platform-identity/routes/profile.js",
@@ -7132,7 +7147,7 @@
7132
7147
  {
7133
7148
  "path": "modules/platform-identity/routes/projects.js",
7134
7149
  "mode": "0000644",
7135
- "sha256": "575d89666362e57e04ac0fd6dcd13b3f5125ba46e5cdb371cb3456f9ee383d88"
7150
+ "sha256": "094c1d54e11794acb09066d0d95b18dbe9100f8b87a67cd7eb1f56be30026f8e"
7136
7151
  },
7137
7152
  {
7138
7153
  "path": "modules/platform-identity/routes/public-profile.js",
@@ -7144,6 +7159,11 @@
7144
7159
  "mode": "0000644",
7145
7160
  "sha256": "3fbaaafe605fd891c449499ca85a716f9b8780bb1869f25b32162593ada8fedd"
7146
7161
  },
7162
+ {
7163
+ "path": "modules/platform-identity/routes/sso-invites.js",
7164
+ "mode": "0000644",
7165
+ "sha256": "af4efb51c20e8cfc3827fbebb17918bcae08f4b07cab982005f52ec34c7c1978"
7166
+ },
7147
7167
  {
7148
7168
  "path": "modules/platform-identity/routes/sso.js",
7149
7169
  "mode": "0000644",
@@ -7172,7 +7192,7 @@
7172
7192
  {
7173
7193
  "path": "modules/platform-identity/tests/platform-identity.mjs",
7174
7194
  "mode": "0000644",
7175
- "sha256": "93d74c10d601eab840da83b5d33d7bbf51907830626401cc27fadb138441fcc3"
7195
+ "sha256": "d9164f9ae4188a313ce55ebc7c670531aa4e47128f85571aaa55730dbabd1dd3"
7176
7196
  },
7177
7197
  {
7178
7198
  "path": "modules/platform-identity/ui/.gitkeep",
@@ -7787,7 +7807,7 @@
7787
7807
  {
7788
7808
  "path": "modules/public-landing/public/assets/cosmos.css",
7789
7809
  "mode": "0000644",
7790
- "sha256": "1269d1e7a9e84459ce023a4c385e7f733dc291df5d46276092f47e845f5b55f8"
7810
+ "sha256": "865e576b309a0ad4b160dc10ff8907e2a6210d2aaea7138155d70143caf7686c"
7791
7811
  },
7792
7812
  {
7793
7813
  "path": "modules/public-landing/public/assets/verro-medallion.png",
@@ -8047,7 +8067,7 @@
8047
8067
  {
8048
8068
  "path": "modules/public-landing/public/projects.html",
8049
8069
  "mode": "0000644",
8050
- "sha256": "c80a43d899cd11ab05fa56b35920da0f2ecb661bab6d9b44a515bdba53756dbc"
8070
+ "sha256": "b2780277742b8f560dc431ce77498ffdefaf1a027ffde893caa472e59da512f3"
8051
8071
  },
8052
8072
  {
8053
8073
  "path": "modules/public-landing/public/projects.probes.json",
@@ -8057,7 +8077,7 @@
8057
8077
  {
8058
8078
  "path": "modules/public-landing/public/projects.states.json",
8059
8079
  "mode": "0000644",
8060
- "sha256": "53529e467824952eca9c4753fb2374caa05f2406aa9b0c03e8824b77e4996540"
8080
+ "sha256": "5ce71b5f4c9cf3b9c955bd6e86a8f9b2c8fc53a917955a57577272972ab31693"
8061
8081
  },
8062
8082
  {
8063
8083
  "path": "modules/public-landing/public/terms.html",
@@ -8582,7 +8602,7 @@
8582
8602
  {
8583
8603
  "path": "modules/ui-design/kit/serve.js",
8584
8604
  "mode": "0000644",
8585
- "sha256": "0d039ac06759ab813a4cf67d2c989662280f2c31c625a3e8ca02eb1f2e594418"
8605
+ "sha256": "9b74bbbdaeee790c88a0f6153ff12f8e7062442257542f308a4a9ab188bbbe2d"
8586
8606
  },
8587
8607
  {
8588
8608
  "path": "modules/ui-design/kit/tells.js",
@@ -8892,12 +8912,12 @@
8892
8912
  {
8893
8913
  "path": "package-lock.json",
8894
8914
  "mode": "0000644",
8895
- "sha256": "7ca4dcc58b8138d0f20002590f565c03952db6a97c8a740c09307c91b24872bf"
8915
+ "sha256": "417e99397bbe709aa8cd87385188a162f83ee135c12954f2cf92a23a499f7b3f"
8896
8916
  },
8897
8917
  {
8898
8918
  "path": "package.json",
8899
8919
  "mode": "0000644",
8900
- "sha256": "fc858fcc7b0fef1d6850cca8b5d5825250568cb16754f3452dc5a5d8f42545d5"
8920
+ "sha256": "2cdc66718b40d75fb0046f4b7dc17ae36ac4c18451ab90e86d5a32b93e34bb4c"
8901
8921
  },
8902
8922
  {
8903
8923
  "path": "public-docs/index.html",
@@ -8917,7 +8937,7 @@
8917
8937
  {
8918
8938
  "path": "release-notes.json",
8919
8939
  "mode": "0000644",
8920
- "sha256": "1739d308859639e1ff64b6716cd4e04f117c094868c1ad8ad1f1fa1272232221"
8940
+ "sha256": "a37f6b56d6237e71dbb1f2d70cb703acd869f8a0ac5ca8e1d27550b3f8fe1bc1"
8921
8941
  },
8922
8942
  {
8923
8943
  "path": "scripts/bongos-mcp.js",
@@ -10697,7 +10717,7 @@
10697
10717
  {
10698
10718
  "path": "src/bongos/auth-admission.js",
10699
10719
  "mode": "0000644",
10700
- "sha256": "9fd9311df751a418985073db3aed97825e52d2a45ca7501b93f93cf5019c1fe6"
10720
+ "sha256": "5e8147efb07016de8648dc6d3dc09d437b27dfc768484bc0aa94582acb6774a2"
10701
10721
  },
10702
10722
  {
10703
10723
  "path": "src/bongos/auth-config.js",
@@ -10722,7 +10742,7 @@
10722
10742
  {
10723
10743
  "path": "src/bongos/auth.js",
10724
10744
  "mode": "0000644",
10725
- "sha256": "a46826214509f317fbe982265c0045edf9c3aa4c94bd2fa54232af51cbc51573"
10745
+ "sha256": "fad15cdbd6652b2a699f48769baaaed0e59314ac6bef326e663d6c7b06b1eaae"
10726
10746
  },
10727
10747
  {
10728
10748
  "path": "src/bongos/cascade-dispatch.js",
@@ -11032,7 +11052,7 @@
11032
11052
  {
11033
11053
  "path": "src/module-api.js",
11034
11054
  "mode": "0000644",
11035
- "sha256": "7e01ad4572ca48ef4dba7c5776246f2e7ba1a251a1bc079f1ae0566b9dc67593"
11055
+ "sha256": "a1d0a3dd719c8550dea1190993a548ea01cad01414e143b32055f18e2cb4e10d"
11036
11056
  },
11037
11057
  {
11038
11058
  "path": "src/module-loader/catalog.js",
@@ -11107,7 +11127,7 @@
11107
11127
  {
11108
11128
  "path": "tests/access_requests_invite.mjs",
11109
11129
  "mode": "0000644",
11110
- "sha256": "4148ce39a074fa16cb6360027a7fd6c1ea9b26cea6423209591ee45f23ec894f"
11130
+ "sha256": "1044d48504923ec03a8a08303fa12612b2fc02abed32d85be26bf8d5615807ad"
11111
11131
  },
11112
11132
  {
11113
11133
  "path": "tests/accessibility_replay.mjs",
@@ -11327,7 +11347,7 @@
11327
11347
  {
11328
11348
  "path": "tests/application_lifecycle.mjs",
11329
11349
  "mode": "0000644",
11330
- "sha256": "c13c0a4b5244b72ec093a2d80029bc3b1ceb8f0828aaad69274054be9e927afe"
11350
+ "sha256": "d35db1b125eaee28d3e0af9c1354b36f589a74bca922b22b507fc29b48bb38c4"
11331
11351
  },
11332
11352
  {
11333
11353
  "path": "tests/architect.mjs",
@@ -11702,7 +11722,7 @@
11702
11722
  {
11703
11723
  "path": "tests/catalog_only_client.mjs",
11704
11724
  "mode": "0000644",
11705
- "sha256": "f973b73aa9c0063dd6a10c60e6ea08729abb5e405e0ff893b2e3c907488d34dc"
11725
+ "sha256": "ea7d5c8546966c84ce8831484355f059b7dc42558954090afa90e17d89165454"
11706
11726
  },
11707
11727
  {
11708
11728
  "path": "tests/category_advisory_guard.mjs",
@@ -13349,6 +13369,11 @@
13349
13369
  "mode": "0000644",
13350
13370
  "sha256": "5daff8488df269752d2e5992e60a03bb75cf85a5b8d5fcaa15703b12a5bb6b94"
13351
13371
  },
13372
+ {
13373
+ "path": "tests/hub_invite_notices.mjs",
13374
+ "mode": "0000644",
13375
+ "sha256": "c79b4101c944110166da2e158bada2192235897887b65bbb7b1f9fae3c61eb13"
13376
+ },
13352
13377
  {
13353
13378
  "path": "tests/hub_map_orb_states.mjs",
13354
13379
  "mode": "0000644",
@@ -13957,13 +13982,18 @@
13957
13982
  {
13958
13983
  "path": "tests/module_api.mjs",
13959
13984
  "mode": "0000644",
13960
- "sha256": "264d34d76d2112c0c8d118d431f7c53342343928b4b61e13729b146c34acd0e0"
13985
+ "sha256": "be32afe74b2ecd47f136a824c9997b54274b72d41dfe5cd8d1e540e9a05f83f4"
13961
13986
  },
13962
13987
  {
13963
13988
  "path": "tests/module_api_lazy.mjs",
13964
13989
  "mode": "0000644",
13965
13990
  "sha256": "9183d78e4d195428afbee8c71746e12db2455f8529ac1223d690a7b8f7ded538"
13966
13991
  },
13992
+ {
13993
+ "path": "tests/module_assessments_migration.mjs",
13994
+ "mode": "0000644",
13995
+ "sha256": "bb71efed35265acaf82f475cbe5221c63eda8a0af452fe4154dfe05fcb7c920a"
13996
+ },
13967
13997
  {
13968
13998
  "path": "tests/module_asset_subdomain.mjs",
13969
13999
  "mode": "0000644",
@@ -14072,7 +14102,7 @@
14072
14102
  {
14073
14103
  "path": "tests/my_projects_surface.mjs",
14074
14104
  "mode": "0000644",
14075
- "sha256": "8aa62a0d98c6f667d66280e13ff849b0a3e7684b528a628042594863b02f63ae"
14105
+ "sha256": "9055da573a4e210daf6d2910938df77f711f54a6701a33ebc8995ef11a95fc52"
14076
14106
  },
14077
14107
  {
14078
14108
  "path": "tests/nav_permission_atoms.mjs",
@@ -14447,7 +14477,7 @@
14447
14477
  {
14448
14478
  "path": "tests/project_invite_ownership.mjs",
14449
14479
  "mode": "0000644",
14450
- "sha256": "743436b1191353bf687d8fb553d423a6003707a25a548367644928478569f171"
14480
+ "sha256": "bc77a25e814de321faae41780b83504bb4ce87a73bb944d0d231a301532e5364"
14451
14481
  },
14452
14482
  {
14453
14483
  "path": "tests/project_invite_suggestions.mjs",
@@ -14457,7 +14487,7 @@
14457
14487
  {
14458
14488
  "path": "tests/project_invite_ui.mjs",
14459
14489
  "mode": "0000644",
14460
- "sha256": "36a7ff3e5b8c4b9edd495d8a7e5b50dcecded8e608f148a62b4e37841c0eb36a"
14490
+ "sha256": "2b6d62d93878b43896dbe17247e7879ac86c842d9300cdf34876c5010ab02d1f"
14461
14491
  },
14462
14492
  {
14463
14493
  "path": "tests/project_join_door.mjs",
@@ -16047,7 +16077,7 @@
16047
16077
  {
16048
16078
  "path": "tests/watch_applications_queue.mjs",
16049
16079
  "mode": "0000644",
16050
- "sha256": "a8020b3e3f30e93a872f94567a03e775d279f130a20a2c61c64caaafa5d752a7"
16080
+ "sha256": "6f0dc184d552c4db4048894286cff96ade91e44c8070fae66fdeef324ba09a7d"
16051
16081
  },
16052
16082
  {
16053
16083
  "path": "tests/watch_roster_self_row.mjs",
@@ -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
- - 462 operations across 70 resource groups
8
+ - 463 operations across 70 resource groups
9
9
 
10
10
  ## Use it from your project
11
11
 
@@ -729,8 +729,6 @@ function createClient(opts = {}) {
729
729
  postMyProjectsClientIdLeave: (args) => request("POST", "/my-projects/{clientId}/leave", { hasBody: true }, args),
730
730
  // GET /my-projects/invites — rank: any-builder — GET /my-projects/invites
731
731
  getMyProjectsInvites: (args) => request("GET", "/my-projects/invites", { hasBody: false }, args),
732
- // POST /my-projects/invites/{clientId}/accept — rank: any-builder — POST /my-projects/invites/:clientId/accept
733
- postMyProjectsInvitesClientIdAccept: (args) => request("POST", "/my-projects/invites/{clientId}/accept", { hasBody: true }, args),
734
732
  // POST /my-projects/invites/{clientId}/decline — rank: any-builder — POST /my-projects/invites/:clientId/decline
735
733
  postMyProjectsInvitesClientIdDecline: (args) => request("POST", "/my-projects/invites/{clientId}/decline", { hasBody: true }, args),
736
734
  // POST /my-projects/join — rank: any-builder — POST /my-projects/join
@@ -1015,6 +1013,10 @@ function createClient(opts = {}) {
1015
1013
  postSsoDevicePoll: (args) => request("POST", "/sso/device/poll", { hasBody: true }, args),
1016
1014
  // POST /sso/device/start — rank: public — POST /sso/device/start
1017
1015
  postSsoDeviceStart: (args) => request("POST", "/sso/device/start", { hasBody: true }, args),
1016
+ // POST /sso/invites/clear — rank: public — POST /sso/invites/clear
1017
+ postSsoInvitesClear: (args) => request("POST", "/sso/invites/clear", { hasBody: true }, args),
1018
+ // POST /sso/invites/notify — rank: public — POST /sso/invites/notify
1019
+ postSsoInvitesNotify: (args) => request("POST", "/sso/invites/notify", { hasBody: true }, args),
1018
1020
  // POST /sso/membership/check-in — rank: public — POST /sso/membership/check-in
1019
1021
  postSsoMembershipCheckIn: (args) => request("POST", "/sso/membership/check-in", { hasBody: true }, args),
1020
1022
  // GET /sso/pubkey — rank: public — GET /sso/pubkey
@@ -728,8 +728,6 @@ function createClient(opts = {}) {
728
728
  postMyProjectsClientIdLeave: (args) => request("POST", "/my-projects/{clientId}/leave", { hasBody: true }, args),
729
729
  // GET /my-projects/invites — rank: any-builder — GET /my-projects/invites
730
730
  getMyProjectsInvites: (args) => request("GET", "/my-projects/invites", { hasBody: false }, args),
731
- // POST /my-projects/invites/{clientId}/accept — rank: any-builder — POST /my-projects/invites/:clientId/accept
732
- postMyProjectsInvitesClientIdAccept: (args) => request("POST", "/my-projects/invites/{clientId}/accept", { hasBody: true }, args),
733
731
  // POST /my-projects/invites/{clientId}/decline — rank: any-builder — POST /my-projects/invites/:clientId/decline
734
732
  postMyProjectsInvitesClientIdDecline: (args) => request("POST", "/my-projects/invites/{clientId}/decline", { hasBody: true }, args),
735
733
  // POST /my-projects/join — rank: any-builder — POST /my-projects/join
@@ -1014,6 +1012,10 @@ function createClient(opts = {}) {
1014
1012
  postSsoDevicePoll: (args) => request("POST", "/sso/device/poll", { hasBody: true }, args),
1015
1013
  // POST /sso/device/start — rank: public — POST /sso/device/start
1016
1014
  postSsoDeviceStart: (args) => request("POST", "/sso/device/start", { hasBody: true }, args),
1015
+ // POST /sso/invites/clear — rank: public — POST /sso/invites/clear
1016
+ postSsoInvitesClear: (args) => request("POST", "/sso/invites/clear", { hasBody: true }, args),
1017
+ // POST /sso/invites/notify — rank: public — POST /sso/invites/notify
1018
+ postSsoInvitesNotify: (args) => request("POST", "/sso/invites/notify", { hasBody: true }, args),
1017
1019
  // POST /sso/membership/check-in — rank: public — POST /sso/membership/check-in
1018
1020
  postSsoMembershipCheckIn: (args) => request("POST", "/sso/membership/check-in", { hasBody: true }, args),
1019
1021
  // GET /sso/pubkey — rank: public — GET /sso/pubkey
@@ -382,13 +382,13 @@ export interface PostModulesKeyEnableResponse { ok: boolean; key: unknown; enabl
382
382
  export interface PostModulesKeySubmitRequest { signed_by?: string }
383
383
  export interface PostModulesKeySubmitResponse { ok: boolean; submission: unknown }
384
384
  export interface PostMyProjectsClientIdLeaveResponse { ok: boolean; left: unknown }
385
- export interface PostMyProjectsInvitesClientIdAcceptResponse { ok: boolean; accepted: boolean }
386
385
  export interface PostMyProjectsInvitesClientIdDeclineResponse { ok: boolean; accepted: boolean }
387
386
  export interface PostMyProjectsJoinRequest { origin: string; note?: string }
388
387
  export interface PostMyProjectsJoinResponse { ok: boolean; status: unknown; project: unknown; message: unknown }
389
388
  export interface PostNpmReleasePreviewRequest { version: string }
390
389
  export interface PostOverrideRequestsIdDecideRequest { decision: string; note?: string }
391
390
  export interface PostProjectsInviteRequest { client_id: string; github_login: string }
391
+ export interface PostProjectsInviteResponse { invited: boolean; invite_url: unknown }
392
392
  export interface PostProjectsProjectIdSpecialitiesRequest { name: string; discipline: string; summary?: string; contract_md?: string; visibility?: string; sellable?: boolean; skills?: string[] }
393
393
  export interface PostProjectsProjectIdSpecialitiesResponse { speciality: unknown }
394
394
  export interface PostProjectsRequest { origin: string; name: string; tagline?: string; description?: string; art_url?: string; type?: string; template?: string }
@@ -443,6 +443,8 @@ export interface PostSsoApplicantProfileRequest { client_id: string; client_secr
443
443
  export interface PostSsoApplicantProfileResponse { profile: unknown }
444
444
  export interface PostSsoDevicePollRequest { device_code: string }
445
445
  export interface PostSsoDeviceStartRequest { origin: string }
446
+ export interface PostSsoInvitesClearRequest { client_id: string; client_secret: string; github_login: string }
447
+ export interface PostSsoInvitesNotifyRequest { client_id: string; client_secret: string; github_login: string }
446
448
  export interface PostSsoMembershipCheckInRequest { client_id: string; client_secret: string; github_id: StringifiableId; github_login: string; display_name?: string; avatar_url?: string; membership_kind?: string }
447
449
  export interface PostSsoMembershipCheckInResponse { ok: boolean }
448
450
  export interface PostSsoTokenRequest { client_id: string; client_secret: string; code: string; redirect_uri: string }
@@ -1156,8 +1158,6 @@ export interface BongosClient {
1156
1158
  postMyProjectsClientIdLeave(args?: RequestArgs): Promise<PostMyProjectsClientIdLeaveResponse>;
1157
1159
  /** GET /my-projects/invites — rank: any-builder */
1158
1160
  getMyProjectsInvites(args?: RequestArgs): Promise<GetMyProjectsInvitesResponse>;
1159
- /** POST /my-projects/invites/{clientId}/accept — rank: any-builder */
1160
- postMyProjectsInvitesClientIdAccept(args?: RequestArgs): Promise<PostMyProjectsInvitesClientIdAcceptResponse>;
1161
1161
  /** POST /my-projects/invites/{clientId}/decline — rank: any-builder */
1162
1162
  postMyProjectsInvitesClientIdDecline(args?: RequestArgs): Promise<PostMyProjectsInvitesClientIdDeclineResponse>;
1163
1163
  /** POST /my-projects/join — rank: any-builder */
@@ -1209,7 +1209,7 @@ export interface BongosClient {
1209
1209
  /** GET /projects/featured — rank: public */
1210
1210
  getProjectsFeatured(args?: RequestArgs): Promise<GetProjectsFeaturedResponse>;
1211
1211
  /** POST /projects/invite — rank: metic+archon */
1212
- postProjectsInvite(args: RequestArgs & { body: PostProjectsInviteRequest }): Promise<ApiResponse>;
1212
+ postProjectsInvite(args: RequestArgs & { body: PostProjectsInviteRequest }): Promise<PostProjectsInviteResponse>;
1213
1213
  };
1214
1214
  "provisioning": {
1215
1215
  /** GET /provisioning/control-plane — rank: archon */
@@ -1442,6 +1442,10 @@ export interface BongosClient {
1442
1442
  postSsoDevicePoll(args: RequestArgs & { body: PostSsoDevicePollRequest }): Promise<ApiResponse>;
1443
1443
  /** POST /sso/device/start — rank: public */
1444
1444
  postSsoDeviceStart(args: RequestArgs & { body: PostSsoDeviceStartRequest }): Promise<ApiResponse>;
1445
+ /** POST /sso/invites/clear — rank: public */
1446
+ postSsoInvitesClear(args: RequestArgs & { body: PostSsoInvitesClearRequest }): Promise<ApiResponse>;
1447
+ /** POST /sso/invites/notify — rank: public */
1448
+ postSsoInvitesNotify(args: RequestArgs & { body: PostSsoInvitesNotifyRequest }): Promise<ApiResponse>;
1445
1449
  /** POST /sso/membership/check-in — rank: public */
1446
1450
  postSsoMembershipCheckIn(args: RequestArgs & { body: PostSsoMembershipCheckInRequest }): Promise<PostSsoMembershipCheckInResponse>;
1447
1451
  /** GET /sso/pubkey — rank: public */
@@ -725,8 +725,6 @@ export function createClient(opts = {}) {
725
725
  postMyProjectsClientIdLeave: (args) => request("POST", "/my-projects/{clientId}/leave", { hasBody: true }, args),
726
726
  // GET /my-projects/invites — rank: any-builder — GET /my-projects/invites
727
727
  getMyProjectsInvites: (args) => request("GET", "/my-projects/invites", { hasBody: false }, args),
728
- // POST /my-projects/invites/{clientId}/accept — rank: any-builder — POST /my-projects/invites/:clientId/accept
729
- postMyProjectsInvitesClientIdAccept: (args) => request("POST", "/my-projects/invites/{clientId}/accept", { hasBody: true }, args),
730
728
  // POST /my-projects/invites/{clientId}/decline — rank: any-builder — POST /my-projects/invites/:clientId/decline
731
729
  postMyProjectsInvitesClientIdDecline: (args) => request("POST", "/my-projects/invites/{clientId}/decline", { hasBody: true }, args),
732
730
  // POST /my-projects/join — rank: any-builder — POST /my-projects/join
@@ -1011,6 +1009,10 @@ export function createClient(opts = {}) {
1011
1009
  postSsoDevicePoll: (args) => request("POST", "/sso/device/poll", { hasBody: true }, args),
1012
1010
  // POST /sso/device/start — rank: public — POST /sso/device/start
1013
1011
  postSsoDeviceStart: (args) => request("POST", "/sso/device/start", { hasBody: true }, args),
1012
+ // POST /sso/invites/clear — rank: public — POST /sso/invites/clear
1013
+ postSsoInvitesClear: (args) => request("POST", "/sso/invites/clear", { hasBody: true }, args),
1014
+ // POST /sso/invites/notify — rank: public — POST /sso/invites/notify
1015
+ postSsoInvitesNotify: (args) => request("POST", "/sso/invites/notify", { hasBody: true }, args),
1014
1016
  // POST /sso/membership/check-in — rank: public — POST /sso/membership/check-in
1015
1017
  postSsoMembershipCheckIn: (args) => request("POST", "/sso/membership/check-in", { hasBody: true }, args),
1016
1018
  // GET /sso/pubkey — rank: public — GET /sso/pubkey
@@ -4,6 +4,7 @@
4
4
  - **Date:** 2026-09-24
5
5
  - **Task:** [task 1002813](https://cloudbongos.com/builders#/task/1002813) (R1 of the project admin console wave, goal 1000110 — BONGOS-V2)
6
6
  - **Deciders:** the three decisions come from the spike ([task 1002786](https://cloudbongos.com/builders#/task/1002786), [`docs/specs/<redacted>.md`](../specs/<redacted>.md), 2026-08-14). The spike asked the owner for one sign-off: what "widenable to metics" means. The owner answered on blocker 1000122, and [ADR 0217](0217-rank-gates-inviting-not-viewing.md) D1 records the answer: a per-project invite floor that defaults to archon and is set by that project's own archons. This ADR does three things. It records the decisions, reconciles them with what has shipped in the six weeks since, and says what each build task still has to build.
7
+ - **Followed by:** [ADR 0353](0353-a-hub-invite-is-a-notice-of-the-projects-own-invite.md) — R6 (task 1002818): the hub's invite row became a notice the project writes, the hub-side accept was retired, and the wizard's invite step hands the owner to the project's hall. The "Until R6 lands" item below is closed by it.
7
8
  - **Supersedes:** only the "Invitations (deferred slice)" paragraph of [ADR 0141](<redacted>.md) §5. The rest of ADR 0141 stands.
8
9
  - **Revises:** [ADR 0210](0210-scouting-graduates-on-an-instance-asserted-owner-claim.md), [ADR 0215](0215-the-recruiter-sliver-is-a-ceiling-not-a-step.md) and [ADR 0248](0248-a-hub-project-s-owner-is-the-provisioning-row-carried-on-a-port.md) predicted that `project.recruit` would replace `project.curate` on the hub's recruiting doors. D3.6 revises that prediction.
9
10
  - **Related:** [ADR 0016](0016-trust-boundary-server-enforced-permissions.md), [0108](0108-instance-composition-configurable-root.md), [0112](0112-private-goals-invitations-join-requests.md), [0151](0151-governance-permissions-as-atom-ranks-as-roles.md), [0157](0157-archon-is-rank-and-identity-only.md), [0182](0182-project-visibility-two-axes.md), [0194](0194-joinability-one-door-decision.md), [0201](0201-cross-project-application-vouched-row-live-record.md), [0247](0247-the-join-door-is-one-composed-answer-and-dark-matter-has-no-live-verdict-yet.md), [0328](0328-a-hosted-projects-authority-is-its-membership-row-not-its-owner-column.md)
@@ -0,0 +1,70 @@
1
+ # ADR 0353 — A hub invite is a notice of the project's own invite, and signing in accepts it
2
+
3
+ - **Status:** accepted
4
+ - **Date:** 2026-09-30
5
+ - **Task:** [task 1002818](https://cloudbongos.com/builders#/task/1002818) (R6 of the project admin console wave, goal 1000110 — BONGOS-V2)
6
+ - **Builds on:** [ADR 0335](0335-an-invite-is-a-pre-approved-row-on-the-projects-own-instance.md) D1/D2 (authority lives on the project's own instance; an invite is a pre-approved `access_requests` row; signing in is the acceptance act)
7
+ - **Interacts with:** [ADR 0141](<redacted>.md) §3 (membership check-in), [ADR 0205](0205-federated-checkin-needs-the-hubs-own-signin-witness.md) (client credentials never mint a membership), [ADR 0352](0352-a-rollup-report-carries-its-as-of-in-a-header.md) (hub↔instance wire skew)
8
+ - **Decided with:** the owner chose the split in D3 on 2026-09-30, over building the signed-assertion relay in this task.
9
+
10
+ ## Context
11
+
12
+ ADR 0335 left one dead end that real users could reach. The hub had its own invite, `POST /projects/invite`, called from the creation wizard's invite step and from a project's manage page. It wrote a `pending` row in `<redacted>`, and accepting it (`POST /my-projects/invites/:clientId/accept`) flipped that row to `member`. No project reads that table to decide who may enter. So the invitee "accepted" and then met the project's own sign-in gate, which knew nothing about them. A hosted project's join door defaults to `apply`, so this was the common case, not an edge case.
13
+
14
+ The project's own invite (`POST /access-requests/invite`, R2) does admit: it writes the `invited` row the sign-in gate reads. But nothing told the invitee it existed, and rescinding it left nothing to clean up on the hub because the hub had never heard of it.
15
+
16
+ The task's done-when: **no state where an accepted invite leads to a locked door.**
17
+
18
+ ## Decision
19
+
20
+ ### D1 — The hub's `pending` row is a NOTICE of the project's own invite
21
+
22
+ The row stays, and My Projects lists it as before, but only the project writes it now. Two new server-to-server routes, authenticated by `client_id` + `client_secret` like the membership check-in:
23
+
24
+ | Route | When the project calls it | What the hub does |
25
+ |---|---|---|
26
+ | `POST /sso/invites/notify` | every outcome of its invite route that leaves the login holding an `invited` row: a fresh invite, a pending application approved in place, and a re-invite of one already standing | `INSERT … 'pending' ON CONFLICT DO NOTHING` for the account that login resolves to |
27
+ | `POST /sso/invites/clear` | a dismissal after which the login holds no `invited` row | `DELETE` that `pending` row, and only a `pending` one |
28
+
29
+ Both calls touch a `pending` row only, so client credentials still cannot create, change or remove a membership (ADR 0205). A login with no hub account is a no-op, because there is nobody on the hub to notify; the project's row still admits them. **The answer is always `{ ok: true }` once the client authenticates**, whether or not anything was written, so a project cannot use the route to learn which GitHub logins have platform accounts.
30
+
31
+ The sender is core's `notifyHubOfInvite` (`src/bongos/auth-admission.js`). It sits in the kernel beside the other hub calls that hold the client secret, and modules reach it as a narrow doorway port. It is fire-and-forget, time-bounded and swallows every failure: an invite must never wait on the hub or fail because of it. Re-inviting is how a notice the hub missed is healed, since the hub's insert is idempotent.
32
+
33
+ Only a dismissal that leaves no `invited` row clears the notice. If two archons invited the same login and one rescinds, the other invite still admits, so the notice stays. A declined application also sends a clear. That is a no-op unless the hub still holds a pre-ADR-0353 row for that login, which would be a dead end anyway.
34
+
35
+ ### D2 — Accepting is signing in to the project; the hub-side accept is retired
36
+
37
+ `POST /my-projects/invites/:clientId/accept` is deleted, and `resolveInvite` becomes the decline-only `removePendingInvite`. The hub page has not called accept since the invitations list came back (task 1003774): each invitation renders as **Open project →** with "Sign in there to accept." What was missing was the server half, so no caller (an old cached page, a script) can still flip a row and admit nobody.
38
+
39
+ Signing in closes the loop without any new code. The hub's own sign-in witness (`recordFederatedSignin`, stamped at `/sso/token` and `/sso/device/poll`) already turns a `pending` row into `member` when the invitee signs in to that project.
40
+
41
+ ### D3 — The hub's own invite writes nothing; it hands the owner to the project's hall
42
+
43
+ `POST /projects/invite` keeps its owner-or-curator gate and its byte-identical 403, and it still resolves the catalog client. It no longer writes. It answers `200 { invited: false, invite_url }`: the project's hall invite box with the login filled in, `<origin>/builders/watch?invite=<login>`. A 200 rather than a refusal, because it is the answer every admitted caller gets, and a 4xx on that path would log a failed request in the owner's browser on every invite. Every instance already canonicalises `/builders/watch` to its hall (a one-host instance serves `/watch`, a two-host one redirects to `builders.<apex>/watch`, both keeping the query), so the hub needs no hall address of its own. The sign-in redirect carries the full URL, so the name survives signing in.
44
+
45
+ The wizard and manage-page widget keep their field, their suggestions and their Skip button. Each name becomes a line reading "not sent yet — finish it on your project's hall" with a **Send the invite →** link. It is a link the owner clicks, never a window opened for them: an open triggered by a network answer is blocked by browsers, and a surprise navigation would lose the wizard.
46
+
47
+ The hall's Watch page reads `?invite=` once, only if it is a valid GitHub username, and **only fills the box**. Sending stays the owner's click, because a link anyone can craft must never be the act that admits.
48
+
49
+ The owner chose this over building the relay ADR 0335 foresaw (see Alternatives) in this task.
50
+
51
+ ### D4 — Wire skew (ADR 0352's rule)
52
+
53
+ The two notice routes are **new**. An older hub answers 404, the sender swallows it, and the invite still admits: only the notice is lost. A newer hub with an older instance simply receives no notices. Each route's body is validated strictly (three fields), so a field added later must ride a header, or ship on the hub and be released before any instance sends it.
54
+
55
+ ## Consequences
56
+
57
+ - **The done-when holds for every invite created from now on.** A hub notice exists only because the project wrote an `invited` row, and a rescind that closes the door removes the notice. The hub's own invite creates no row at all.
58
+ - **Rows written before this change are untouched.** Pending rows the old hub invite wrote still render as **Open project →**, and on a door other than `open` they still lead to the project's gate. The invitee can decline them, and the project can clear one by inviting and then rescinding. Deleting them at deploy would need a data migration, and it would also delete notices that do work (on `open` doors). That is left to the owner (see the task's handoff), not done silently here.
59
+ - **The hub's invite now reaches people with no platform account.** The project's own invite takes any GitHub login, so the old `account_not_found` refusal is gone. Such an invitee sees no hub notice, because there is no account to show it to, but they are admitted when they sign in.
60
+ - **Three things still bypass the notice**, all by scope: the reviewer queue's approve (`PATCH` pending → invited), the Discord approval reaction, and R3's future recruiter rescind route. The first two are approvals of an application the applicant already knows about. R3 (task 1002815) must call the same port when it adds its rescind.
61
+ - **The 30-day invite expiry** (task 1002969) is read-time on the instance. An expired invite's hub notice stays until the invitee declines it or the project re-invites and rescinds. Task 1002969 owns closing that.
62
+
63
+ ## Alternatives considered
64
+
65
+ - **The signed-assertion relay (ADR 0335's foreseen design).** The hub mints a new `purpose: 'invite'` token for the inviter, and a new token-authenticated route on the project checks that builder's own permission and writes the real row. It keeps the in-place form, but it adds an authentication path to every instance (security-sensitive), roughly doubles this task, needs a fallback for every older instance, and cannot work until the owner has signed in to a new project once (there is no local builder to check the permission against). The owner deferred it. D3's hand-off leaves room for it: if it is built, the route's `invited: false` becomes `true` for instances that support it.
66
+ - **Seed the hub row only when the project's door is `open`.** An owner who later closes the door strands every such notice in front of a locked door, and a self-hosted project's door is not known to the hub reliably.
67
+ - **Keep the hub-side accept, but have the instance read hub memberships at sign-in.** This puts a hub call inside the admission gate and moves a verdict onto the hub, which ADR 0335 D1 forbids.
68
+ - **A body field on the membership check-in instead of new routes.** The check-in body is validated strictly, so an older hub would 400 the whole check-in (ADR 0352), and a check-in is sent at sign-in, not at invite time.
69
+
70
+ Proof: `tests/hub_invite_notices.mjs` (the hub routes over the real domain functions: pending-only writes keyed on the authenticated client, the identical answer for an unknown login, the credential refusals, the three-field body, the hand-off link, and the sender), `tests/access_requests_invite.mjs` and `tests/application_lifecycle.mjs` (which invite outcomes notify and which dismissals clear), `tests/project_invite_ownership.mjs` and `tests/catalog_only_client.mjs` (the gate is unchanged and nothing is written), `tests/project_invite_ui.mjs` (the hand-off line, no non-http link, no window opened), `tests/watch_applications_queue.mjs` (the prefill fills and never sends), `tests/my_projects_surface.mjs` and `modules/platform-identity/tests/platform-identity.mjs` (the accept route and flip are gone).