@bongos/core 1.20.27 → 1.20.28

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 (32) hide show
  1. package/.bongos-core.json +59 -34
  2. package/docs/adr/0335-an-invite-is-a-pre-approved-row-on-the-projects-own-instance.md +1 -1
  3. package/docs/adr/0353-a-hub-invite-is-a-notice-of-the-projects-own-invite.md +1 -1
  4. package/docs/adr/0356-a-recruit-invite-expires-30-days-after-it-is-sent-read-time.md +46 -0
  5. package/docs/adr/README.md +1 -0
  6. package/docs/api/openapi.json +2 -2
  7. package/docs/copy-inventory.md +20 -19
  8. package/docs/copy-registry.json +34 -25
  9. package/docs/module-api-changelog.md +3 -1
  10. package/docs/page-readings.json +9 -9
  11. package/migrations/core_266_access_requests_invited_login_idx.sql +31 -0
  12. package/modules/hall-ui/public/watch.js +9 -3
  13. package/modules/onboarding/routes/access-requests.js +43 -20
  14. package/modules/platform-identity/platform-identity.js +12 -8
  15. package/modules/platform-identity/tests/platform-identity.mjs +4 -2
  16. package/package-lock.json +2 -2
  17. package/package.json +1 -1
  18. package/release-notes.json +10 -0
  19. package/scripts/gds/fitness.js +4 -0
  20. package/scripts/gds/login.js +6 -0
  21. package/scripts/gds/run-unit-tests.js +4 -0
  22. package/src/bongos/db-kernel.js +6 -3
  23. package/src/bongos/invite-expiry.js +47 -0
  24. package/src/module-api.js +10 -1
  25. package/tests/access_requests_invite.mjs +2 -1
  26. package/tests/application_lifecycle.mjs +164 -5
  27. package/tests/bongos_login.mjs +2 -0
  28. package/tests/hub_invite_notices.mjs +5 -2
  29. package/tests/invite_expiry.mjs +37 -0
  30. package/tests/invite_expiry_db.mjs +196 -0
  31. package/tests/module_api.mjs +1 -0
  32. package/tests/project_admin_console_model.mjs +12 -5
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.27",
6
- "core_contract": "1.20.27",
7
- "source_commit": "1fc525416e379b30ced8b97b9999a269eb272392",
5
+ "core_version": "1.20.28",
6
+ "core_contract": "1.20.28",
7
+ "source_commit": "64dc694277165d8cd1227e09df2fda3dd99dd396",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-30T19:45:22.316Z",
9
+ "built_at": "2026-09-30T20:23:58.237Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
- "docs_redacted": 561,
12
+ "docs_redacted": 562,
13
13
  "agent_docs_stubbed": 25,
14
- "functional_verbatim": 2655,
14
+ "functional_verbatim": 2659,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 3242,
20
- "tree_sha256": "c65b104e5fd942b276adceaae176a3d833b2fa53daebe3292ac36d278f890c2e",
19
+ "file_count": 3247,
20
+ "tree_sha256": "06c076a09a9c706567f32a447e0d1600e2e07a868b9bdd4be62088a290714985",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -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": "f22adac7e27f2e83ece9311e44fca35edb0bd571440f108d28a9088775d0b93c"
2150
+ "sha256": "826c6c6ae6e10af9383e518ff63196e2b947bc0ecfa0c1be0288895751bc1352"
2151
2151
  },
2152
2152
  {
2153
2153
  "path": "docs/adr/0336-a-guilds-record-is-the-sum-of-what-its-members-already-show.md",
@@ -2237,7 +2237,7 @@
2237
2237
  {
2238
2238
  "path": "docs/adr/0353-a-hub-invite-is-a-notice-of-the-projects-own-invite.md",
2239
2239
  "mode": "0000644",
2240
- "sha256": "5c93de4ab1813f7641ec3b33ad620a609a77fbe6fb3cbaffd99b7e6e5f8282fa"
2240
+ "sha256": "e4ee7809ea3f414ecfdbe0b3e6e4b435c1ccc25084fce9c3cfa3521943fe8b90"
2241
2241
  },
2242
2242
  {
2243
2243
  "path": "docs/adr/0354-a-builder-hosts-one-project-free-and-more-takes-the-fleet-permission.md",
@@ -2249,10 +2249,15 @@
2249
2249
  "mode": "0000644",
2250
2250
  "sha256": "6ad8ab3bd8bcb4f55575232af651f108e3879a5965f5daaac42ab693d00a1f0a"
2251
2251
  },
2252
+ {
2253
+ "path": "docs/adr/0356-a-recruit-invite-expires-30-days-after-it-is-sent-read-time.md",
2254
+ "mode": "0000644",
2255
+ "sha256": "ccbad5d4326b069381db47ea3fc165f9a4020485e3d6ce346393a2f6711ce1f0"
2256
+ },
2252
2257
  {
2253
2258
  "path": "docs/adr/README.md",
2254
2259
  "mode": "0000644",
2255
- "sha256": "8258f16b11aaed9731afca21379b1ae976dc67040d6d35139987e29fd4acd360"
2260
+ "sha256": "d21d0eb8389864b4f7394ba0f4b0d0d809182f0bef82d9fc6824edd00e174b39"
2256
2261
  },
2257
2262
  {
2258
2263
  "path": "docs/api-reference.md",
@@ -2262,7 +2267,7 @@
2262
2267
  {
2263
2268
  "path": "docs/api/openapi.json",
2264
2269
  "mode": "0000644",
2265
- "sha256": "545342fb2849e012cd5caca28d21659fe076a5158c0a0a4c2ca4a2b34abd34da"
2270
+ "sha256": "27b8f6be81f9703a96a68f917dbb01a65048cc02f13adf308b414e96fb022668"
2266
2271
  },
2267
2272
  {
2268
2273
  "path": "docs/architecture.md",
@@ -2287,12 +2292,12 @@
2287
2292
  {
2288
2293
  "path": "docs/copy-inventory.md",
2289
2294
  "mode": "0000644",
2290
- "sha256": "3e6cb5cc724958b42912c60fa5cfe65cd6bd7baa7697c3b501534506053a5cb6"
2295
+ "sha256": "ed9adfda17002adac03024b90ed8ee6a79a9c5a814ce88970bf6c1dfd6fa8e9f"
2291
2296
  },
2292
2297
  {
2293
2298
  "path": "docs/copy-registry.json",
2294
2299
  "mode": "0000644",
2295
- "sha256": "5eb31ba434dd1afbfe397ed33ae62ea76c51b71eaafca9b693bebfbe800e3b14"
2300
+ "sha256": "68b59e9280240b604210d20279e64f47af0bddbac8e2c3344343b4ecf8e21e52"
2296
2301
  },
2297
2302
  {
2298
2303
  "path": "docs/design/apex-pass-2-direction.md",
@@ -2797,7 +2802,7 @@
2797
2802
  {
2798
2803
  "path": "docs/module-api-changelog.md",
2799
2804
  "mode": "0000644",
2800
- "sha256": "7360408e833d4775efb81a46a20c2b357495e93532e08468fe59766cede1b0ad"
2805
+ "sha256": "8de783d8efc2109b60de3758da0dfd4fecaf4d4a65dc364dacd1c17e8dac4a7d"
2801
2806
  },
2802
2807
  {
2803
2808
  "path": "docs/modules-contract.md",
@@ -2887,7 +2892,7 @@
2887
2892
  {
2888
2893
  "path": "docs/page-readings.json",
2889
2894
  "mode": "0000644",
2890
- "sha256": "bed8eb280c3798e7def41bfff4d21ac22e8f16ded6174de18487d54e071af768"
2895
+ "sha256": "c9722a4284b34b8773b2fed801a256277bb314833da397ae89af674ebe46acee"
2891
2896
  },
2892
2897
  {
2893
2898
  "path": "docs/project-context.template.md",
@@ -3979,6 +3984,11 @@
3979
3984
  "mode": "0000644",
3980
3985
  "sha256": "ea887c7bcd2863e69121bfcfa36cc62fedc77c5bd5a052e5868e2f7c99cbc103"
3981
3986
  },
3987
+ {
3988
+ "path": "migrations/core_266_access_requests_invited_login_idx.sql",
3989
+ "mode": "0000644",
3990
+ "sha256": "e7dcf016492ab9d011bd0021ac866edd502bf68f2fe06a04365ecb2b49ccbfaa"
3991
+ },
3982
3992
  {
3983
3993
  "path": "modules/agents/CLAUDE.md",
3984
3994
  "mode": "0000644",
@@ -5752,7 +5762,7 @@
5752
5762
  {
5753
5763
  "path": "modules/hall-ui/public/watch.js",
5754
5764
  "mode": "0000644",
5755
- "sha256": "7a5ef4f34be6b0ab7f5b3fa34bf1666c17164a6331031cc908c2791109585b89"
5765
+ "sha256": "6746170acdd097b14bdccce236ac236b4962bdd1679de2c5624ca1a10cf784d0"
5756
5766
  },
5757
5767
  {
5758
5768
  "path": "modules/hall-ui/public/watch.states.json",
@@ -6842,7 +6852,7 @@
6842
6852
  {
6843
6853
  "path": "modules/onboarding/routes/access-requests.js",
6844
6854
  "mode": "0000644",
6845
- "sha256": "1eaac1f70a519ca5a0ea71bb56ab6d33b582f5ff002faf5811fa6c1700480057"
6855
+ "sha256": "7e8a63d388816b1a65ec6b24fdc20390d42efa7afa5c33cdf164d0cfca1ad5d4"
6846
6856
  },
6847
6857
  {
6848
6858
  "path": "modules/onboarding/routes/onboarding.js",
@@ -7092,7 +7102,7 @@
7092
7102
  {
7093
7103
  "path": "modules/platform-identity/platform-identity.js",
7094
7104
  "mode": "0000644",
7095
- "sha256": "9877fd84f74545d96473651cca9d00a76712507e76962c1891d3dd0c3d47e7b2"
7105
+ "sha256": "8f293f2d70e61f90a381153248a748b12452d3bb984e5903f32610fad78d669f"
7096
7106
  },
7097
7107
  {
7098
7108
  "path": "modules/platform-identity/pollers/code-sweep.js",
@@ -7222,7 +7232,7 @@
7222
7232
  {
7223
7233
  "path": "modules/platform-identity/tests/platform-identity.mjs",
7224
7234
  "mode": "0000644",
7225
- "sha256": "d9164f9ae4188a313ce55ebc7c670531aa4e47128f85571aaa55730dbabd1dd3"
7235
+ "sha256": "6dc7617a7d7615fc5ee51817ae77bba8089324c124a6cb1d0fe3ca9400454ee1"
7226
7236
  },
7227
7237
  {
7228
7238
  "path": "modules/platform-identity/ui/.gitkeep",
@@ -8962,12 +8972,12 @@
8962
8972
  {
8963
8973
  "path": "package-lock.json",
8964
8974
  "mode": "0000644",
8965
- "sha256": "b4b4420fef73f720a6180d45b5559b38b857fe80181e148715187c5f2c5ec498"
8975
+ "sha256": "cf64ad1f380ccd8f68a30f1f5b72aa8c03681fc37f044093ae435b7886529cc8"
8966
8976
  },
8967
8977
  {
8968
8978
  "path": "package.json",
8969
8979
  "mode": "0000644",
8970
- "sha256": "9c84d9545af388a0db6fed5fe78bf1c71469ffa1e69ea37386281f0c273cd656"
8980
+ "sha256": "d79bd6231aed473960ba8f6c644b69f2c930850a0937a504635e657aa733ea89"
8971
8981
  },
8972
8982
  {
8973
8983
  "path": "public-docs/index.html",
@@ -8987,7 +8997,7 @@
8987
8997
  {
8988
8998
  "path": "release-notes.json",
8989
8999
  "mode": "0000644",
8990
- "sha256": "f6b10e5e103f5007ef31e7d2f53c95e146a93562f5a5465ea4e85da2baeb3c52"
9000
+ "sha256": "2ec3010abecc3e0ed0d2a9bda13ccad173d34e91d80a13bc4ff4aecace0944e8"
8991
9001
  },
8992
9002
  {
8993
9003
  "path": "scripts/bongos-mcp.js",
@@ -9557,7 +9567,7 @@
9557
9567
  {
9558
9568
  "path": "scripts/gds/fitness.js",
9559
9569
  "mode": "0000644",
9560
- "sha256": "dee3e8db3ad7fff3c1e30a59aa58bab00972cba9710bd463e10ab8a3edc71e38"
9570
+ "sha256": "11c013412eca59780987da1db3af9db4a16202b743e55675cb28812c4091f883"
9561
9571
  },
9562
9572
  {
9563
9573
  "path": "scripts/gds/gate-review.js",
@@ -9782,7 +9792,7 @@
9782
9792
  {
9783
9793
  "path": "scripts/gds/login.js",
9784
9794
  "mode": "0000644",
9785
- "sha256": "0d719e43955fdbcc40aee500438db5b31c6c006490e90d99caadf31b215196f7"
9795
+ "sha256": "75f3a83618340171dba4be280cf41515c341c0468f4cf5dd32e3db8adeb95c9d"
9786
9796
  },
9787
9797
  {
9788
9798
  "path": "scripts/gds/main-audit.js",
@@ -10147,7 +10157,7 @@
10147
10157
  {
10148
10158
  "path": "scripts/gds/run-unit-tests.js",
10149
10159
  "mode": "0000644",
10150
- "sha256": "d891a37574626191f7abc01c50a64178a128e4f8b53ceb17ad6ed6d7439bc56e"
10160
+ "sha256": "1060c8a67619c8e6c0d8168d6a26644b799ed8053ab4995fd9386b55af3660c9"
10151
10161
  },
10152
10162
  {
10153
10163
  "path": "scripts/gds/runner-drift.js",
@@ -10817,7 +10827,7 @@
10817
10827
  {
10818
10828
  "path": "src/bongos/db-kernel.js",
10819
10829
  "mode": "0000644",
10820
- "sha256": "f224d17792980308b3db2762e06fbbdf265bcf50c7d651662f91b818ae5ce146"
10830
+ "sha256": "85032e00d05717fa4cde2d9db45c567d3f11772a549a458b2ae67a18274a0f91"
10821
10831
  },
10822
10832
  {
10823
10833
  "path": "src/bongos/db.js",
@@ -10839,6 +10849,11 @@
10839
10849
  "mode": "0000644",
10840
10850
  "sha256": "5f2e9a548f7c0cd1c8b8245be4ba50e9066edb18d07ee7ded7d72f363040ba00"
10841
10851
  },
10852
+ {
10853
+ "path": "src/bongos/invite-expiry.js",
10854
+ "mode": "0000644",
10855
+ "sha256": "64ae68b30656075c76f420a29e0f25b6499719d745c901580d5859f8c860ca78"
10856
+ },
10842
10857
  {
10843
10858
  "path": "src/bongos/live-channel.js",
10844
10859
  "mode": "0000644",
@@ -11102,7 +11117,7 @@
11102
11117
  {
11103
11118
  "path": "src/module-api.js",
11104
11119
  "mode": "0000644",
11105
- "sha256": "b2361eff082828662c898922d47f4968192f852af7fbf5b03365c7624ffbfc55"
11120
+ "sha256": "40910d211c287582e7066d72f651ff79862d9f5776be6086cd2c931e648136fd"
11106
11121
  },
11107
11122
  {
11108
11123
  "path": "src/module-loader/catalog.js",
@@ -11177,7 +11192,7 @@
11177
11192
  {
11178
11193
  "path": "tests/access_requests_invite.mjs",
11179
11194
  "mode": "0000644",
11180
- "sha256": "1044d48504923ec03a8a08303fa12612b2fc02abed32d85be26bf8d5615807ad"
11195
+ "sha256": "22f633a9216457e37fa5da47b260ce6c7aa0d5b2c500c8170a476807c426c76b"
11181
11196
  },
11182
11197
  {
11183
11198
  "path": "tests/accessibility_replay.mjs",
@@ -11397,7 +11412,7 @@
11397
11412
  {
11398
11413
  "path": "tests/application_lifecycle.mjs",
11399
11414
  "mode": "0000644",
11400
- "sha256": "d35db1b125eaee28d3e0af9c1354b36f589a74bca922b22b507fc29b48bb38c4"
11415
+ "sha256": "f84e1b46315e57514f00545038eaa94a9617a83ebb8b5e4b55814fa3b063e155"
11401
11416
  },
11402
11417
  {
11403
11418
  "path": "tests/architect.mjs",
@@ -11702,7 +11717,7 @@
11702
11717
  {
11703
11718
  "path": "tests/bongos_login.mjs",
11704
11719
  "mode": "0000644",
11705
- "sha256": "1e95957f3c780b4054128000e0deefc76e4df41c3076da76d2fbd187b47c95d2"
11720
+ "sha256": "091ab59bc648f1ef11b309b09283ca2730635c6f3010adfcbf81053e1abb2055"
11706
11721
  },
11707
11722
  {
11708
11723
  "path": "tests/branding.mjs",
@@ -13432,7 +13447,7 @@
13432
13447
  {
13433
13448
  "path": "tests/hub_invite_notices.mjs",
13434
13449
  "mode": "0000644",
13435
- "sha256": "c79b4101c944110166da2e158bada2192235897887b65bbb7b1f9fae3c61eb13"
13450
+ "sha256": "61a4467e59582bd8d640ba82c139089933a2fadf5c3e2bd570bdfb7b11ae26c7"
13436
13451
  },
13437
13452
  {
13438
13453
  "path": "tests/hub_map_orb_states.mjs",
@@ -13724,6 +13739,16 @@
13724
13739
  "mode": "0000644",
13725
13740
  "sha256": "1e01a44dc1ef5d454643e81737f77b5061e577a9275a22f5b98bd05ac541f1d7"
13726
13741
  },
13742
+ {
13743
+ "path": "tests/invite_expiry.mjs",
13744
+ "mode": "0000644",
13745
+ "sha256": "f4e7d57306b988aa5dadb9c2fe3c55f519beb2725980a7d360caa09416b03e8c"
13746
+ },
13747
+ {
13748
+ "path": "tests/invite_expiry_db.mjs",
13749
+ "mode": "0000644",
13750
+ "sha256": "9963890ca3f5ee6ab008079c1623e0dec11bf3ce696ff5f18621ad9e52f76a4d"
13751
+ },
13727
13752
  {
13728
13753
  "path": "tests/join_application_note.mjs",
13729
13754
  "mode": "0000644",
@@ -14042,7 +14067,7 @@
14042
14067
  {
14043
14068
  "path": "tests/module_api.mjs",
14044
14069
  "mode": "0000644",
14045
- "sha256": "be32afe74b2ecd47f136a824c9997b54274b72d41dfe5cd8d1e540e9a05f83f4"
14070
+ "sha256": "35a7e4c2e2afe2cd61e51318e7e3300e7629acd24683b515287aa9dcbe8501ae"
14046
14071
  },
14047
14072
  {
14048
14073
  "path": "tests/module_api_lazy.mjs",
@@ -14512,7 +14537,7 @@
14512
14537
  {
14513
14538
  "path": "tests/project_admin_console_model.mjs",
14514
14539
  "mode": "0000644",
14515
- "sha256": "5afd92ab7143b332572adcff8ddec69b544e5607b64b16ba14075d9160eec7a9"
14540
+ "sha256": "af3664d6b4ce861f126b7f9eca87abf423f25bb5126df36a93b80312c70a218c"
14516
14541
  },
14517
14542
  {
14518
14543
  "path": "tests/project_catalog_backfill.mjs",
@@ -55,7 +55,7 @@ There is no new table. The sign-in gate (`hasInvitedAccessRequest` → `firstTim
55
55
  2. **`created_by` → `builders(id)`, set on invite rows only.** This is the inviter. It needs its own column because a rescind overwrites `resolved_by_builder_id` (ADR 0201 D4.2 records whoever acted last). Without it, a rescinded invite forgets who sent it.
56
56
  3. **`note` needs no new column.** The existing column holds the inviter's note on an invite row, and `kind` says whose words they are.
57
57
  4. **The backfill is exact.** The current invite route sets `resolved_at = now()` in the same statement whose default fills `created_at`, so every row it wrote has `created_at = resolved_at`. No approved application can match that, because an application is always resolved after it was filed. So the migration sets `kind='invite'` and `created_by = resolved_by_builder_id` on `status='invited'` rows where `created_at = resolved_at`. The one case it cannot catch is a direct invite that was later rescinded, because the rescind changes `resolved_at`. That row stays `'application'`, which makes it look like a declined application. It admits nobody either way. The only effect comes after R3: a recruiter who holds `project.recruit` but not `access_request.review` and re-invites that login gets the D3.4 refusal, so a reviewer has to send the invite. That leaves the door stricter, never looser, and applies only to invites rescinded before R2 ships.
58
- 5. **`kind` and `created_by` never enter an admission read.** This is the same rule `core_230` set for the vouch column. An invite and an approved application admit through the identical `status = 'invited'` read. `tests/project_admin_console_model.mjs` checks this.
58
+ 5. **`kind` and `created_by` never enter an admission read.** This is the same rule `core_230` set for the vouch column. An invite and an approved application admit through the identical `status = 'invited'` read. `tests/project_admin_console_model.mjs` checks this. **Amended by [ADR 0356](0356-a-recruit-invite-expires-30-days-after-it-is-sent-read-time.md) (task 1002969):** `kind` now enters admission in one place, the invite expiry, which can close an invite's door after 30 days and never opens one. `created_by` still never enters.
59
59
  6. **Expiry is not part of this.** The 30-day read-time expiry (ADR 0201 D4.3) covers every invited row, whatever its `kind`. Task 1002969 owns it.
60
60
 
61
61
  ### D3 — One new grantable atom, `project.recruit` (floor archon), says which rank may recruit; the verdict stays with `access_request.review`
@@ -58,7 +58,7 @@ The two notice routes are **new**. An older hub answers 404, the sender swallows
58
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. **Amended (task 1004408):** the owner chose to delete them. Migration `<redacted>` deletes every `pending` row older than the hub's first upgrade to core 1.20.22 (the first release carrying this ADR), read from the `core_upgrades` ledger, or every `pending` row if there is no such upgrade. Old rows on `open` doors go too; those invitees can still sign in to the project, and the inviter can re-invite from the project's hall. Proof: `tests/pending_invite_purge_db.mjs`.
59
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
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.
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. **Closed by [ADR 0356](0356-a-recruit-invite-expires-30-days-after-it-is-sent-read-time.md):** the hub stops listing a notice 30 days after it was sent, and a re-invite restamps it.
62
62
 
63
63
  ## Alternatives considered
64
64
 
@@ -0,0 +1,46 @@
1
+ # ADR 0356 — A recruit invite expires 30 days after it is sent, decided at read time
2
+
3
+ - **Status:** accepted
4
+ - **Date:** 2026-09-30
5
+ - **Task:** [task 1002969](https://cloudbongos.com/builders#/task/1002969) (goal 1000110)
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
+ - **Amends:** ADR 0335 D2.5 ("no admission path reads `kind`")
8
+ - **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
+ ## Context
11
+
12
+ An invite writes an `invited` row, and the sign-in gate admits any login holding one. Nothing ever closed it, so an invite sent months ago still let its login walk in. Spec D4 says a recruit invite's door closes after 30 days, enforced when it is read: no sweeper and no cron. ADR 0353 left the hub side of the same question to this task, because an expired invite's hub notice still said "Open project →".
13
+
14
+ ## Decision
15
+
16
+ **D1 — One definition, computed at read time.** `src/bongos/invite-expiry.js` holds `INVITE_EXPIRY_DAYS` (30) and the SQL for "this row is an invite past its window", `kind = 'invite' AND created_at <= now() - interval '30 days'`. The sign-in gate (`hasInvitedAccessRequest`), the waiting-page probe (`accessStateByLogin`), the onboarding routes and the hub all read it: core directly, and modules through the doorway's `inviteExpiry`. Nothing is stored, so the day a door closes cannot drift between readers, and nothing has to run for it to close.
17
+
18
+ **D2 — Only invites expire.** An approved application (`kind = 'application'`) stays open: its applicant asked, and D4 is about invites. Goal invites get no expiry either.
19
+
20
+ **D3 — The gate now reads `kind`, and only to be stricter (amends ADR 0335 D2.5).** D2.5 kept `kind` out of admission because it was descriptive. It is now read in one place, inside the expiry unit, and that unit can only close a door. A row that is anything but an invite behaves exactly as before, so a mis-kinded row fails toward the old gate and never past it. Only server code writes `kind`: the public POST cannot set it. `created_by` is still never read. `tests/application_lifecycle.mjs` enforces both: its admission statements may mention `kind` only inside the expiry unit.
21
+
22
+ **D4 — What an expired invite looks like.**
23
+ - 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
+ - A re-invite writes a fresh row, which opens a fresh door. The expired row stays as history.
25
+ - 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
+ - A rescind that leaves only expired rows clears the hub notice.
28
+
29
+ **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.
30
+
31
+ ## Consequences
32
+
33
+ - **Invites already older than 30 days close at deploy.** That is the spec's rule applied to existing rows. Their senders re-invite.
34
+ - **Wire skew (ADR 0352).** A newer hub with an older instance hides a notice after 30 days while that instance's door stays open. Only the nudge is lost. An older hub with a newer instance keeps showing an expired notice, which is today's behaviour.
35
+ - **The hub's clock is the notice's, not the invite's.** A notice healed by a late re-send (the hub missed the first) can outlive its invite by up to the gap between them. A lost clear can hide a live notice early. In both cases the next re-invite fixes it.
36
+ - **The outstanding list's cost stays that of its own scan.** "Does this login have a live invite" is a window (`bool_or … OVER (PARTITION BY lower(github_login))`) over the rows the list already reads, not a correlated lookup per row: `access_requests` has no index on `lower(github_login)` outside pending rows, and invite rows are kept as history. The sender filter (`?mine=1`) sits outside the window. Migration `core_266` adds the index those reads lacked, partial on `status = 'invited'` over `lower(github_login)`, which serves the sign-in gate, the standing-invite probe and this list. The owner approved it on 2026-09-30 after a grader finding.
37
+ - **Declining still closes only the hub's notice.** The project's row stays open until it expires. Task 1004444 owns the relay.
38
+
39
+ ## Alternatives considered
40
+
41
+ - **A sweeper that flips expired rows to `dismissed`.** Spec D4 rules it out. It would also mislabel an expiry as a decline in the history.
42
+ - **Expire every `invited` row, applications included.** That is wider than D4, and at deploy it would lock out every applicant approved more than 30 days ago.
43
+ - **Dismiss the expired row when re-inviting.** That keeps the list tidy, but the hall renders `dismissed` as "Declined", which would be false.
44
+ - **Carry the invite's `created_at` to the hub in a header, making its clock exact.** That is a new wire field on the notify route for an edge case a re-invite already heals.
45
+
46
+ Proof: `tests/invite_expiry.mjs` (the definition), `tests/application_lifecycle.mjs` (the gate, queues, probe, re-invite and notice clear, interpreted), `tests/invite_expiry_db.mjs` (the same statements on real Postgres, plus the hub's notice), `tests/hub_invite_notices.mjs` and `modules/platform-identity/tests/platform-identity.mjs` (the notice statement's shape), `tests/bongos_login.mjs` (the CLI stops on `expired`).
@@ -474,3 +474,4 @@ These 20 numbers are each shared by exactly two files. They are **accepted histo
474
474
  | 0353 | [**A hub invite is a notice of the project's own invite, and signing in accepts it** ([task 1002818](https://cloudbongos.com/builders#/task/1002818), goal 1000110 — R6 of the admin console wave). The hub's own invite wrote a `pending` membership that no project reads, so an "accepted" invite met a locked door (new projects default to `apply`). **D1:** that row is now a NOTICE the PROJECT writes: its invite route calls `POST /sso/invites/notify` and a dismissal that leaves no invited row calls `/sso/invites/clear` (client credentials, pending rows only, always `{ ok: true }` so no account oracle); core's `notifyHubOfInvite` is fire-and-forget. **D2:** the hub-side accept route is deleted; signing in to the project accepts, and the hub's own sign-in witness turns the row into a membership. **D3 (owner):** `POST /projects/invite` writes nothing and answers `{ invited: false, invite_url }`, the project's hall invite link (`/builders/watch?invite=<login>`, which only pre-fills); the signed-assertion relay is deferred. **D4:** new routes, so an older hub 404s and only the notice is lost (ADR 0352).](0353-a-hub-invite-is-a-notice-of-the-projects-own-invite.md) | platform identity / federation |
475
475
  | 0354 | [**A builder hosts one project for free, and more takes the fleet permission** ([task 1004412](https://cloudbongos.com/builders#/task/1004412), goal 1000106 — working area 1, the owner's ruling of 2026-09-30). The create wizard always asks for `cloud-host`, which task 1003370 had gated behind `provisioning.fleet.manage`, so every new (Xenos) user was refused at "Create my project" — against ADR 0345 decision 1. **D1:** a builder's first live `cloud-host` project needs no permission; a further one needs the fleet permission (a retry of your own slug is the same project; only `torn_down` frees the place; an unreadable count refuses). **D2:** one first-free request per builder at a time (409 `create_in_progress`), held as a Postgres advisory lock so it holds across processes. **D3:** the wizard states `permission_forbidden` and `box_full` in plain words. Nothing yet caps a hosted project's memory.](0354-a-builder-hosts-one-project-free-and-more-takes-the-fleet-permission.md) | provisioning / security / project creation |
476
476
  | 0355 | [**A soft limit is a rule with a reason, and nobody enforces it** ([task 1004416](https://cloudbongos.com/builders#/task/1004416), goal 1000121 — BV2.PS02). The startup flow's seven planet-physics answers and what they rule out. **D1:** the answers are more provisioning `detail` keys under `team_shape`'s contract (no defaults, refused loudly, `null` un-answers), with two new kinds — a list (`many`) and a whole number (`count`); the country is a closed ISO list. **D2:** answers must agree only within one request, so a merge patch's edit order never matters. **D3:** one declarative soft-limit table (`soft-limits.js`) — data conditions, `hide`/`grey`/`note`, a reason on every rule — and every resolution says `enforced: false`. **D4:** screening is a seam (`screening.js`) with a deliberately small rule set; a stop is 422 `screened_out` before anything exists. **D5:** the edit route screens a new description too.](0355-a-soft-limit-is-a-rule-with-a-reason-and-nobody-enforces-it.md) | provisioning / project startup |
477
+ | 0356 | [**A recruit invite expires 30 days after it is sent, decided at read time** ([task 1002969](https://cloudbongos.com/builders#/task/1002969), goal 1000110 — privacy spec D4, owner-signed). **D1:** one definition, `src/bongos/invite-expiry.js` (`INVITE_EXPIRY_DAYS` + the SQL), read by the sign-in gate, the waiting-page probe, the onboarding routes and, through the doorway's `inviteExpiry`, the hub. Nothing is stored and nothing sweeps. **D2:** only `kind='invite'` rows expire; approved applications and goal invites do not. **D3:** amends ADR 0335 D2.5: the gate reads `kind` only inside the expiry unit, which can close a door and never open one. **D4:** an expired invite is refused, marked `expired` + `expires_at` in the queues, reported `expired` by the status probe (the CLI stops), and replaced by a re-invite's fresh row. **D5:** the hub hides a `pending` notice past 30 days and a re-invite restamps it. Split out: `seen_at` (task 1004443), the decline relay (task 1004444).](0356-a-recruit-invite-expires-30-days-after-it-is-sent-read-time.md) | platform identity / admission / invites |
@@ -440,7 +440,7 @@
440
440
  "access-requests"
441
441
  ],
442
442
  "summary": "GET /access-requests/invites",
443
- "description": "GET /access-requests/invites[?mine=1] — the outstanding invites: kind='invite', still 'invited' (not rescinded), and no builder row yet (not yet accepted by signing in), each naming its sender; ?mine=1 keeps only the caller's own (ADR 0335 D2; task 1002814). Rescind one with PATCH /access-requests/:id. rank: archon — it names logins that are not members yet.\n\n**Rank:** `archon` — Archon only (rank and identity management + the escalation keys — the trust boundary).\n\n**Permissions:** `access_request.review` (all required).",
443
+ "description": "GET /access-requests/invites[?mine=1] — the outstanding invites: kind='invite', still 'invited' (not rescinded), and no builder row yet (not yet accepted by signing in), each naming its sender; ?mine=1 keeps only the caller's own (ADR 0335 D2; task 1002814). Rescind one with PATCH /access-requests/:id. Each carries `expires_at`, and `expired` once its 30 days are up (task 1002969): listed so the sender knows to invite again, and dropped once a newer invite for the same login is live, since that one already replaced it. rank: archon — it names logins that are not members yet.\n\n**Rank:** `archon` — Archon only (rank and identity management + the escalation keys — the trust boundary).\n\n**Permissions:** `access_request.review` (all required).",
444
444
  "x-rank": "archon",
445
445
  "x-source": "modules/onboarding/routes/access-requests.js",
446
446
  "x-permissions": [
@@ -481,7 +481,7 @@
481
481
  "access-requests"
482
482
  ],
483
483
  "summary": "GET /access-requests/status",
484
- "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 | pending | dismissed | none; admitted = member||invited. 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.",
484
+ "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.",
485
485
  "x-rank": "public",
486
486
  "x-source": "modules/onboarding/routes/access-requests.js",
487
487
  "responses": {
@@ -4,13 +4,13 @@
4
4
  > Deterministic on a given tree — regenerate and commit rather than hand-editing.
5
5
  > Machine-readable twin: [`copy-registry.json`](copy-registry.json).
6
6
 
7
- **2188 strings** across 3 surfaces · 1888 distinct pieces of copy · 2188 certain, 0 heuristic · 207 shown in more than one place.
7
+ **2189 strings** across 3 surfaces · 1889 distinct pieces of copy · 2189 certain, 0 heuristic · 207 shown in more than one place.
8
8
 
9
9
  ## Surfaces
10
10
 
11
11
  | surface | lives at | files | strings |
12
12
  |---|---|---|---|
13
- | `builders-hall` | `modules/hall-ui/public` | 109 | 1617 |
13
+ | `builders-hall` | `modules/hall-ui/public` | 109 | 1618 |
14
14
  | `status-dashboard` | `modules/status-ui/public` | 4 | 100 |
15
15
  | `landing` | `modules/public-landing/public` | 6 | 471 |
16
16
 
@@ -78,7 +78,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
78
78
  | `064c72453555` | 3x | goals · | modules/hall-ui/public/roadmap.js:218, modules/hall-ui/public/sky-interaction.js:38, modules/hall-ui/public/sky-interaction.js:129 |
79
79
  | … | | _167 more in the JSON registry_ | |
80
80
 
81
- ## `builders-hall` — 1617 strings
81
+ ## `builders-hall` — 1618 strings
82
82
 
83
83
  | id | conf | text | where |
84
84
  |---|---|---|---|
@@ -190,7 +190,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
190
190
  | `688cc6dd38ee` | certain | Asked of you | `modules/hall-ui/public/collab.html:48` |
191
191
  | `688cc6dd38ee` | certain | Asked of you | `modules/hall-ui/public/collab.html:122` |
192
192
  | `b2656d0b2402` | certain | Asked {…} | `modules/hall-ui/public/collab.js:91` |
193
- | `b2656d0b2402` | certain | Asked {…} | `modules/hall-ui/public/watch.js:511` |
193
+ | `b2656d0b2402` | certain | Asked {…} | `modules/hall-ui/public/watch.js:517` |
194
194
  | `8c8ec2597036` | certain | Assigned {…} to builder {…}. | `modules/hall-ui/public/government.js:463` |
195
195
  | `46714066f2da` | certain | Audit log | `modules/hall-ui/public/watch.html:141` |
196
196
  | `d7c574ba6b2a` | certain | Autobongos runner | `modules/hall-ui/public/gate.html:53` |
@@ -309,8 +309,8 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
309
309
  | `e269ff7b3144` | certain | Could not add that builder just now. | `modules/hall-ui/public/goals-render.js:1311` |
310
310
  | `7bcaeffb742f` | certain | Could not approve: {…} | `modules/hall-ui/public/gate.js:123` |
311
311
  | `c922293e668c` | certain | Could not assign: {…} | `modules/hall-ui/public/government.js:468` |
312
- | `e1fc7cedf4e7` | certain | Could not change rank: {…} | `modules/hall-ui/public/watch.js:883` |
313
- | `b9e53fbd90e6` | certain | Could not change standing: {…} | `modules/hall-ui/public/watch.js:909` |
312
+ | `e1fc7cedf4e7` | certain | Could not change rank: {…} | `modules/hall-ui/public/watch.js:889` |
313
+ | `b9e53fbd90e6` | certain | Could not change standing: {…} | `modules/hall-ui/public/watch.js:915` |
314
314
  | `1ef1efc15b6c` | certain | Could not change that just now. | `modules/hall-ui/public/goals-render.js:1357` |
315
315
  | `6214998a5ebe` | certain | Could not check for a newer version: {…}. | `modules/hall-ui/public/settings-software-update.js:135` |
316
316
  | `9ab613a308d6` | certain | Could not check for updates: {…}. | `modules/hall-ui/public/settings-software-update.js:180` |
@@ -334,7 +334,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
334
334
  | `8b69e445592a` | certain | Could not load the question labels; showing the answers as stored. | `modules/hall-ui/public/idea.js:458` |
335
335
  | `edd5bafbe075` | certain | Could not load the queue just now. | `modules/hall-ui/public/ideas.js:885` |
336
336
  | `02bb0570996a` | certain | Could not load the ranks. | `modules/hall-ui/public/ranks.js:197` |
337
- | `2878778cf9f3` | certain | Could not load this page. | `modules/hall-ui/public/watch.js:1345` |
337
+ | `2878778cf9f3` | certain | Could not load this page. | `modules/hall-ui/public/watch.js:1351` |
338
338
  | `df4c2f78a3f6` | certain | Could not load this project’s skills — {…} | `modules/hall-ui/public/settings-specialities.js:311` |
339
339
  | `4b0c8d94541c` | certain | Could not load your account. Reload the page to try again. | `modules/hall-ui/public/settings.js:1156` |
340
340
  | `2a9fb4046bc4` | certain | Could not load your rank. Return to the hall and try again. | `modules/hall-ui/public/ranks.js:196` |
@@ -343,7 +343,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
343
343
  | `de244c45d916` | certain | Could not load {…} | `modules/hall-ui/public/task.js:1027` |
344
344
  | `2366356926b4` | certain | Could not mark the primer just now. Try again. | `modules/hall-ui/public/primer.js:259` |
345
345
  | `753987b5da13` | certain | Could not reach the submit endpoint. | `modules/hall-ui/public/modules.js:298` |
346
- | `6f500726f0ac` | certain | Could not read the report: {…} | `modules/hall-ui/public/watch.js:663` |
346
+ | `6f500726f0ac` | certain | Could not read the report: {…} | `modules/hall-ui/public/watch.js:669` |
347
347
  | `21c2f33168a3` | certain | Could not read this project's deploy state: {…} | `modules/hall-ui/public/deploy.js:449` |
348
348
  | `21a782f1af26` | certain | Could not read what this would bring in: {…}. The check below still applies. | `modules/hall-ui/public/deploy.js:368` |
349
349
  | `8b470cebf53d` | certain | Could not remove your key. Try again. | `modules/hall-ui/public/settings.js:977` |
@@ -375,7 +375,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
375
375
  | `e65b392144ac` | certain | Could not set up storage. Try again. | `modules/hall-ui/public/settings.js:956` |
376
376
  | `d4601887a058` | certain | Could not sign out everywhere: {…} | `modules/hall-ui/public/settings-sessions.js:102` |
377
377
  | `3db890b074a7` | certain | Could not update visibility just now. | `modules/hall-ui/public/goals-render.js:1341` |
378
- | `112d12b24879` | certain | Could not verify your access. Go back and try again. | `modules/hall-ui/public/watch.js:1344` |
378
+ | `112d12b24879` | certain | Could not verify your access. Go back and try again. | `modules/hall-ui/public/watch.js:1350` |
379
379
  | `6bd74fd20311` | certain | Could not verify your access. Try again. | `modules/hall-ui/public/sessions.js:436` |
380
380
  | `278e8281c82f` | certain | Could not vote: {…} | `modules/hall-ui/public/board-room.js:396` |
381
381
  | `a48e9f098392` | certain | Could not {…} just now. | `modules/hall-ui/public/goals-render.js:1327` |
@@ -574,6 +574,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
574
574
  | `dad3388b49c9` | certain | Invitation rescinded. | `modules/hall-ui/public/goals-page.js:689` |
575
575
  | `12561be47da9` | certain | Invitation sent — they join once they accept. | `modules/hall-ui/public/goals-render.js:1270` |
576
576
  | `24f9e88c21f6` | certain | Invite by GitHub username | `modules/hall-ui/public/watch.js:295` |
577
+ | `33a35c77d372` | certain | Invite expired {…} — invite them again to reopen the door | `modules/hall-ui/public/watch.js:501` |
577
578
  | `b9cb720ad5d1` | certain | Invited? Sign in | `modules/hall-ui/public/hall-render.js:286` |
578
579
  | `0e48bb2c8c94` | certain | Island index | `modules/hall-ui/public/atlas.html:76` |
579
580
  | `2170b74c6526` | certain | Isometric map of the repository | `modules/hall-ui/public/atlas.html:54` |
@@ -801,7 +802,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
801
802
  | `a2bd76ae030d` | certain | Opens this project's upgrade{…}: you see what would move, confirm it, and it installs, checks itself, and roll | `modules/hall-ui/public/settings-software-update.js:85` |
802
803
  | `569980a263ae` | certain | Optional. This is the ledger link — a copy change ships as a task like every other change. | `modules/hall-ui/public/copy-desk.html:130` |
803
804
  | `f2e9e8f21e8e` | certain | Or add someone without asking… | `modules/hall-ui/public/goals-render.js:1234` |
804
- | `d89d1923ecf9` | certain | Over the last {…} days | `modules/hall-ui/public/watch.js:1182` |
805
+ | `d89d1923ecf9` | certain | Over the last {…} days | `modules/hall-ui/public/watch.js:1188` |
805
806
  | `695ffff13730` | certain | Over time | `modules/hall-ui/public/profile.html:72` |
806
807
  | `5ded44da0cc5` | certain | Override requests | `modules/hall-ui/public/watch.html:136` |
807
808
  | `0046e08db06a` | certain | Owner’s bill / month (est.) | `modules/hall-ui/public/fleet-costs.js:70` |
@@ -852,7 +853,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
852
853
  | `3f85bdc47099` | certain | Protected module scope — join by request; an Archon admits members | `modules/hall-ui/public/goals-render.js:856` |
853
854
  | `e5c292c8b361` | certain | Provides (seams) | `modules/hall-ui/public/atlas.js:288` |
854
855
  | `4de232a05888` | certain | Raise after | `modules/hall-ui/public/project-settings.js:113` |
855
- | `b9c6def6b120` | certain | Rank set to {…}. | `modules/hall-ui/public/watch.js:877` |
856
+ | `b9c6def6b120` | certain | Rank set to {…}. | `modules/hall-ui/public/watch.js:883` |
856
857
  | `ff50a1395cbc` | certain | Rank: {…} | `modules/hall-ui/public/profile.js:50` |
857
858
  | `6824ec470f99` | certain | Ranks — {{worldName}} | `modules/hall-ui/public/ranks.html:6` |
858
859
  | `6824ec470f99` | certain | Ranks — {{worldName}} | `modules/hall-ui/public/ranks.html:6` |
@@ -860,7 +861,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
860
861
  | `101a51902f53` | certain | Re-issue failed: {…} | `modules/hall-ui/public/settings.js:780` |
861
862
  | `fae2f90927bd` | certain | Read an idea, then decide. An objection needs a reason and the section it faults. | `modules/hall-ui/public/board-room.html:51` |
862
863
  | `b03e17693dcd` | certain | Read it as a table | `modules/hall-ui/public/drachmae.js:148` |
863
- | `b03e17693dcd` | certain | Read it as a table | `modules/hall-ui/public/watch.js:1155` |
864
+ | `b03e17693dcd` | certain | Read it as a table | `modules/hall-ui/public/watch.js:1161` |
864
865
  | `b6fcfdd652a7` | certain | Read it here | `modules/hall-ui/public/work.html:45` |
865
866
  | `053df132e22c` | certain | Read the full ask | `modules/hall-ui/public/collab.js:133` |
866
867
  | `c5f19a888a15` | certain | Read the handoff notes | `modules/hall-ui/public/task.js:490` |
@@ -897,7 +898,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
897
898
  | `7d4149cee474` | certain | Repo Atlas — a self-drawing map of the codebase | `modules/hall-ui/public/atlas.html:6` |
898
899
  | `4d40a68605e1` | certain | Repo atlas | `modules/hall-ui/public/atlas.html:30` |
899
900
  | `52d7d4309540` | certain | Repo health | `modules/hall-ui/public/watch.html:104` |
900
- | `e6629398d47c` | certain | Report {…}. | `modules/hall-ui/public/watch.js:597` |
901
+ | `e6629398d47c` | certain | Report {…}. | `modules/hall-ui/public/watch.js:603` |
901
902
  | `ab456f09c0f6` | certain | Request access | `modules/hall-ui/public/hall-render.js:294` |
902
903
  | `ab456f09c0f6` | certain | Request access | `modules/hall-ui/public/hall-render.js:470` |
903
904
  | `820f47ce5f16` | certain | Request sent | `modules/hall-ui/public/profile-connections.js:95` |
@@ -916,7 +917,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
916
917
  | `0887725744f2` | certain | Restricted. | `modules/hall-ui/public/board-room.js:526` |
917
918
  | `0887725744f2` | certain | Restricted. | `modules/hall-ui/public/gate.js:234` |
918
919
  | `0887725744f2` | certain | Restricted. | `modules/hall-ui/public/government.js:890` |
919
- | `0887725744f2` | certain | Restricted. | `modules/hall-ui/public/watch.js:1294` |
920
+ | `0887725744f2` | certain | Restricted. | `modules/hall-ui/public/watch.js:1300` |
920
921
  | `b99a71747432` | certain | Retire task {…}? It leaves the claimable queue. Promote restores it to the backlog. | `modules/hall-ui/public/task.js:966` |
921
922
  | `b33dde402434` | certain | Retire this goal — you will be asked what happens to any unfinished work | `modules/hall-ui/public/goals-render.js:936` |
922
923
  | `d0d5b4300f28` | certain | Retire this task | `modules/hall-ui/public/task.js:802` |
@@ -1030,7 +1031,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1030
1031
  | `a1482eff4ead` | certain | Suggested for you | `modules/hall-ui/public/settings-specialities.js:384` |
1031
1032
  | `1e40f33483eb` | certain | Switch theme | `modules/hall-ui/public/shell.js:491` |
1032
1033
  | `93fa2e97dd35` | certain | System default for this skill | `modules/hall-ui/public/settings.js:72` |
1033
- | `4894b1d9b0de` | certain | System oversight: people, operations and security. | `modules/hall-ui/public/watch.js:1358` |
1034
+ | `4894b1d9b0de` | certain | System oversight: people, operations and security. | `modules/hall-ui/public/watch.js:1364` |
1034
1035
  | `aa8da34fe237` | certain | Take the next page, or pick one from the studio. | `modules/hall-ui/public/tweak-editor.js:564` |
1035
1036
  | `e604fc3ace79` | certain | Task # | `modules/hall-ui/public/blockers.js:139` |
1036
1037
  | `a0f384b022fd` | certain | Task dependencies | `modules/hall-ui/public/goals.html:140` |
@@ -1057,7 +1058,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1057
1058
  | `c0e28f30524d` | certain | That project's own standing, as reported by that project. | `modules/hall-ui/public/profile.js:314` |
1058
1059
  | `e99d4a811e92` | certain | That request is no longer available. | `modules/hall-ui/public/profile-connections.js:314` |
1059
1060
  | `34017119ce22` | certain | That work is genuinely gone | `modules/hall-ui/public/goal-inbox.js:374` |
1060
- | `6e5a324b2972` | certain | That's you | `modules/hall-ui/public/watch.js:826` |
1061
+ | `6e5a324b2972` | certain | That's you | `modules/hall-ui/public/watch.js:832` |
1061
1062
  | `50198c465225` | certain | The 16-value rank enum in three bands, which ranks are live, and the permissions each rank holds. | `modules/hall-ui/public/diagrams.html:59` |
1062
1063
  | `c5e6bdd32fe3` | certain | The Board Room is open to the builders this project’s constitution seats on its board. | `modules/hall-ui/public/board-room.js:529` |
1063
1064
  | `6e93ef49d63f` | certain | The adversarial panel. Each worker reports pass, concerns or fail on its own; the gating workers must all pass | `modules/hall-ui/public/task.js:440` |
@@ -1313,7 +1314,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1313
1314
  | `6dadadffa36e` | certain | Your name cannot be blank. | `modules/hall-ui/public/settings.js:606` |
1314
1315
  | `bd77210a8c90` | certain | Your name must be {…} characters or fewer. | `modules/hall-ui/public/settings.js:611` |
1315
1316
  | `c0a01a95e97e` | certain | Your own idea. You may vote, but it can’t be decided by your vote alone, and a vote on your own idea earns no | `modules/hall-ui/public/board-room.js:366` |
1316
- | `5e0ec4cd1cfe` | certain | Your own row carries no controls. Nobody can change their own rank or standing — that guard is what stops the | `modules/hall-ui/public/watch.js:858` |
1317
+ | `5e0ec4cd1cfe` | certain | Your own row carries no controls. Nobody can change their own rank or standing — that guard is what stops the | `modules/hall-ui/public/watch.js:864` |
1317
1318
  | `8fc605026acf` | certain | Your pending join {…}: {…} — awaiting approval. | `modules/hall-ui/public/goal-inbox.js:88` |
1318
1319
  | `e91418db3962` | certain | Your preferences, {…}. | `modules/hall-ui/public/settings.js:630` |
1319
1320
  | `e91418db3962` | certain | Your preferences, {…}. | `modules/hall-ui/public/settings.js:1173` |
@@ -1526,7 +1527,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1526
1527
  | `4d8ee7d68974` | certain | task {…} | `modules/hall-ui/public/idea.js:234` |
1527
1528
  | `4d8ee7d68974` | certain | task {…} | `modules/hall-ui/public/ideas.js:201` |
1528
1529
  | `4d8ee7d68974` | certain | task {…} | `modules/hall-ui/public/ideas.js:782` |
1529
- | `4d8ee7d68974` | certain | task {…} | `modules/hall-ui/public/watch.js:1265` |
1530
+ | `4d8ee7d68974` | certain | task {…} | `modules/hall-ui/public/watch.js:1271` |
1530
1531
  | `e4e423344746` | certain | task · {…} | `modules/hall-ui/public/sky-interaction.js:36` |
1531
1532
  | `e4e423344746` | certain | task · {…} | `modules/hall-ui/public/sky-panel.js:95` |
1532
1533
  | `a41659dc2bee` | certain | tasks remaining | `modules/hall-ui/public/roadmap.js:298` |
@@ -1634,7 +1635,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1634
1635
  | `1a8a6267e490` | certain | {…} unfinished task{…} — say what happens to each | `modules/hall-ui/public/goals-render.js:1015` |
1635
1636
  | `3ad8f32dd43b` | certain | {…} you have sent | `modules/hall-ui/public/profile-connections.js:146` |
1636
1637
  | `8186b562553a` | certain | {…} {…} waiting | `modules/hall-ui/public/work.js:432` |
1637
- | `0d4016a5531c` | certain | {…} {…}{…} | `modules/hall-ui/public/watch.js:497` |
1638
+ | `0d4016a5531c` | certain | {…} {…}{…} | `modules/hall-ui/public/watch.js:503` |
1638
1639
  | `0d4016a5531c` | certain | {…} {…}{…} | `modules/hall-ui/public/work.js:265` |
1639
1640
  | `b3757ba3bb46` | certain | {…} · Stored encrypted; never shown again after saving. | `modules/hall-ui/public/settings.js:834` |
1640
1641
  | `a6e4ed98fbea` | certain | {…} · revoke it any time under Active sessions. | `modules/hall-ui/public/settings.js:766` |