@bongos/core 1.24.1 → 1.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.bongos-core.json +64 -39
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +6 -0
  4. package/clients/bongos-client/index.cjs +6 -0
  5. package/clients/bongos-client/index.d.ts +6 -0
  6. package/clients/bongos-client/index.mjs +6 -0
  7. package/docs/adr/0385-money-lives-with-a-processor-credits-stay-a-project-meter.md +12 -1
  8. package/docs/adr/README.md +1 -1
  9. package/docs/api/openapi.json +140 -1
  10. package/docs/api-reference.md +5 -2
  11. package/docs/copy-inventory.md +11 -6
  12. package/docs/copy-registry.json +63 -8
  13. package/docs/module-api-changelog.md +2 -0
  14. package/docs/page-inventory.json +2 -1
  15. package/docs/page-readings.json +29 -20
  16. package/modules/government/access-routes.js +70 -0
  17. package/modules/government/access.js +88 -0
  18. package/modules/government/asks.js +5 -2
  19. package/modules/government/db.js +38 -0
  20. package/modules/government/docket.js +12 -2
  21. package/modules/government/migrations/government_036_access_holders.sql +33 -0
  22. package/modules/government/routes/government.js +9 -0
  23. package/modules/government/routing.js +11 -3
  24. package/modules/government/waiting.js +4 -0
  25. package/modules/hall-ui/public/blockers-by-person.js +2 -0
  26. package/modules/hall-ui/public/government-access.js +108 -0
  27. package/modules/hall-ui/public/government.html +21 -0
  28. package/modules/hall-ui/records/blockers-people.md +14 -0
  29. package/package-lock.json +2 -2
  30. package/package.json +1 -1
  31. package/release-notes.json +10 -0
  32. package/scripts/bongos/government-walk.js +3 -0
  33. package/src/bongos/route-rank-check.js +4 -0
  34. package/src/module-api.js +1 -1
  35. package/tests/access_holders.mjs +214 -0
  36. package/tests/government_board_membership.mjs +4 -1
  37. package/tests/government_routes.mjs +1 -1
  38. package/tests/government_seed.mjs +1 -0
@@ -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
- **3106 strings** across 3 surfaces · 2446 distinct pieces of copy · 3106 certain, 0 heuristic · 260 shown in more than one place.
7
+ **3111 strings** across 3 surfaces · 2450 distinct pieces of copy · 3111 certain, 0 heuristic · 261 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` | 159 | 2008 |
13
+ | `builders-hall` | `modules/hall-ui/public` | 160 | 2013 |
14
14
  | `status-dashboard` | `modules/status-ui/public` | 4 | 91 |
15
15
  | `landing` | `modules/public-landing/public` | 10 | 1007 |
16
16
 
@@ -76,9 +76,9 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
76
76
  | `eff70e59d12e` | 4x | Task {…} | modules/hall-ui/public/task.js:491, modules/hall-ui/public/task.js:527, modules/hall-ui/public/task.js:1022 |
77
77
  | `72a29648399b` | 4x | Tweak a page | modules/hall-ui/public/tweak-editor.html:32, modules/hall-ui/public/tweak-editor.html:32, modules/hall-ui/public/tweak-editor.html:55 |
78
78
  | `ed394c66d49c` | 4x | What the choice changes | modules/public-landing/public/projects.html:1342, modules/public-landing/public/projects.html:1430, modules/public-landing/public/projects.html:1442 |
79
- | … | | _220 more in the JSON registry_ | |
79
+ | … | | _221 more in the JSON registry_ | |
80
80
 
81
- ## `builders-hall` — 2008 strings
81
+ ## `builders-hall` — 2013 strings
82
82
 
83
83
  | id | conf | text | where |
84
84
  |---|---|---|---|
@@ -251,6 +251,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
251
251
  | `da910fee01d7` | certain | Browse goals → | `modules/hall-ui/public/goals.js:105` |
252
252
  | `25b4ab94728a` | certain | Build from your own machine — get a copy of the project, sign in once, then work in Claude. | `modules/hall-ui/public/hall-render.js:817` |
253
253
  | `08bfcc9198ba` | certain | Builder id | `modules/hall-ui/public/government.html:107` |
254
+ | `08bfcc9198ba` | certain | Builder id | `modules/hall-ui/public/government.html:127` |
254
255
  | `fd1903586e8e` | certain | Builders hall · this session | `modules/hall-ui/public/craft-mode.js:670` |
255
256
  | `306bc316184d` | certain | Builders you may know | `modules/hall-ui/public/profile-connections.js:158` |
256
257
  | `60b248791dc3` | certain | Builders — {{worldName}} | `modules/hall-ui/public/roster.html:6` |
@@ -270,7 +271,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
270
271
  | `181cfa50b127` | certain | By the numbers | `modules/hall-ui/public/profile.html:64` |
271
272
  | `a015a43bfc7f` | certain | CLI access | `modules/hall-ui/public/settings.html:141` |
272
273
  | `09e863eb90af` | certain | Can: {…} | `modules/hall-ui/public/government.js:911` |
273
- | `084eb64bce72` | certain | Capabilities held by a machine identity, never by a person or a rank. Shown for completeness; nothing here can | `modules/hall-ui/public/government.html:121` |
274
+ | `084eb64bce72` | certain | Capabilities held by a machine identity, never by a person or a rank. Shown for completeness; nothing here can | `modules/hall-ui/public/government.html:141` |
274
275
  | `c31f00bce184` | certain | Card added. | `modules/hall-ui/public/settings-passkeys.js:92` |
275
276
  | `3cd965c9e3b1` | certain | Card name | `modules/hall-ui/public/settings.html:186` |
276
277
  | `6fad39976eaf` | certain | Card removed. | `modules/hall-ui/public/settings-passkeys.js:59` |
@@ -757,7 +758,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
757
758
  | `9da6a9b67eea` | certain | Look around first | `modules/hall-ui/public/welcome-boot.js:197` |
758
759
  | `4017ee030bc2` | certain | Lookback window | `modules/hall-ui/public/profile-charts.js:540` |
759
760
  | `73677b6839d7` | certain | Looks good | `modules/hall-ui/public/studio.js:196` |
760
- | `06b4ede3fb58` | certain | Machine principals | `modules/hall-ui/public/government.html:120` |
761
+ | `06b4ede3fb58` | certain | Machine principals | `modules/hall-ui/public/government.html:140` |
761
762
  | `b8a2b32fdbb1` | certain | Main role | `modules/hall-ui/public/settings.html:125` |
762
763
  | `4088dac7b364` | certain | Make it a law | `modules/hall-ui/public/my-agents.js:418` |
763
764
  | `d535454fac6d` | certain | Make it again | `modules/hall-ui/public/ai-studio.js:416` |
@@ -874,6 +875,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
874
875
  | `1967d2176f2e` | certain | Nobody has an open ask right now. | `modules/hall-ui/public/collab.html:95` |
875
876
  | `c65c9f5c02e3` | certain | Nobody is waiting on you. Requests reach you by role — | `modules/hall-ui/public/collab.html:54` |
876
877
  | `d5cf262e671d` | certain | Nobody is writing this page. Take it, and every line here is yours to type over. | `modules/hall-ui/public/tweak-editor.js:323` |
878
+ | `380b924cd4a0` | certain | Nobody recorded — it falls to the owner | `modules/hall-ui/public/government-access.js:35` |
877
879
  | `91c409a4aea5` | certain | Nobody vouched for this name — anyone can file a request naming anyone. Confirm who is asking before you admit | `modules/hall-ui/public/watch.js:397` |
878
880
  | `4016b9cf8d57` | certain | None of your asks have been settled yet. | `modules/hall-ui/public/collab.html:145` |
879
881
  | `36c97d6094bf` | certain | None yet. Yours will be the first. | `modules/hall-ui/public/lightbulb.js:90` |
@@ -983,6 +985,8 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
983
985
  | `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:130` |
984
986
  | `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` |
985
987
  | `f2e9e8f21e8e` | certain | Or add someone without asking… | `modules/hall-ui/public/goals-render.js:1258` |
988
+ | `8b1e7aae1e5f` | certain | Outside access | `modules/hall-ui/public/government.html:123` |
989
+ | `20cf1adf4154` | certain | Outside access could not be read just now. | `modules/hall-ui/public/government-access.js:72` |
986
990
  | `d89d1923ecf9` | certain | Over the last {…} days | `modules/hall-ui/public/watch.js:1287` |
987
991
  | `695ffff13730` | certain | Over time | `modules/hall-ui/public/profile.html:76` |
988
992
  | `5ded44da0cc5` | certain | Override requests | `modules/hall-ui/public/watch.html:137` |
@@ -1592,6 +1596,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1592
1596
  | `f9ab9eb870a6` | certain | While you wait: install the Claude desktop app at | `modules/hall-ui/public/hall-render.js:471` |
1593
1597
  | `74d8c58ef235` | certain | Who builds on this project, ranked by work shipped. This project's own ledger — everyone who builds here is li | `modules/hall-ui/public/roster.html:28` |
1594
1598
  | `b79ea4077bc5` | certain | Who can use it | `modules/hall-ui/public/settings.html:306` |
1599
+ | `c6d830ca9e08` | certain | Who holds the keys outside the project, so a blocker that needs one reaches them. This is a record of who to a | `modules/hall-ui/public/government.html:124` |
1595
1600
  | `045c629729b7` | certain | Who’s on this goal, and what they’ve contributed to it. | `modules/hall-ui/public/goals.html:219` |
1596
1601
  | `5cc53380b1e8` | certain | Why (optional) | `modules/hall-ui/public/goals-render.js:1029` |
1597
1602
  | `62ab2e60f629` | certain | Why is this better? | `modules/hall-ui/public/copy-desk.html:164` |
@@ -3,17 +3,17 @@
3
3
  "generator": "scripts/bongos/copy-inventory.js",
4
4
  "goal": 1000074,
5
5
  "counts": {
6
- "strings": 3106,
7
- "unique_ids": 2446,
8
- "certain": 3106,
6
+ "strings": 3111,
7
+ "unique_ids": 2450,
8
+ "certain": 3111,
9
9
  "heuristic": 0,
10
- "duplicate_ids": 260
10
+ "duplicate_ids": 261
11
11
  },
12
12
  "surfaces": {
13
13
  "builders-hall": {
14
14
  "at": "modules/hall-ui/public",
15
- "files": 159,
16
- "strings": 2008
15
+ "files": 160,
16
+ "strings": 2013
17
17
  },
18
18
  "status-dashboard": {
19
19
  "at": "modules/status-ui/public",
@@ -1258,6 +1258,16 @@
1258
1258
  "modules/hall-ui/public/goals.js:105"
1259
1259
  ]
1260
1260
  },
1261
+ {
1262
+ "id": "08bfcc9198ba",
1263
+ "surface": "builders-hall",
1264
+ "text": "Builder id",
1265
+ "occurrences": 2,
1266
+ "at": [
1267
+ "modules/hall-ui/public/government.html:107",
1268
+ "modules/hall-ui/public/government.html:127"
1269
+ ]
1270
+ },
1261
1271
  {
1262
1272
  "id": "60b248791dc3",
1263
1273
  "surface": "builders-hall",
@@ -4561,6 +4571,15 @@
4561
4571
  "origin": "html-text",
4562
4572
  "confidence": "certain"
4563
4573
  },
4574
+ {
4575
+ "id": "08bfcc9198ba",
4576
+ "surface": "builders-hall",
4577
+ "text": "Builder id",
4578
+ "file": "modules/hall-ui/public/government.html",
4579
+ "line": 127,
4580
+ "origin": "html-text",
4581
+ "confidence": "certain"
4582
+ },
4564
4583
  {
4565
4584
  "id": "fd1903586e8e",
4566
4585
  "surface": "builders-hall",
@@ -4737,7 +4756,7 @@
4737
4756
  "surface": "builders-hall",
4738
4757
  "text": "Capabilities held by a machine identity, never by a person or a rank. Shown for completeness; nothing here can be granted.",
4739
4758
  "file": "modules/hall-ui/public/government.html",
4740
- "line": 121,
4759
+ "line": 141,
4741
4760
  "origin": "html-text",
4742
4761
  "confidence": "certain"
4743
4762
  },
@@ -9120,7 +9139,7 @@
9120
9139
  "surface": "builders-hall",
9121
9140
  "text": "Machine principals",
9122
9141
  "file": "modules/hall-ui/public/government.html",
9123
- "line": 120,
9142
+ "line": 140,
9124
9143
  "origin": "html-text",
9125
9144
  "confidence": "certain"
9126
9145
  },
@@ -10168,6 +10187,15 @@
10168
10187
  "origin": "js-markup",
10169
10188
  "confidence": "certain"
10170
10189
  },
10190
+ {
10191
+ "id": "380b924cd4a0",
10192
+ "surface": "builders-hall",
10193
+ "text": "Nobody recorded — it falls to the owner",
10194
+ "file": "modules/hall-ui/public/government-access.js",
10195
+ "line": 35,
10196
+ "origin": "js-markup",
10197
+ "confidence": "certain"
10198
+ },
10171
10199
  {
10172
10200
  "id": "91c409a4aea5",
10173
10201
  "surface": "builders-hall",
@@ -11149,6 +11177,24 @@
11149
11177
  "origin": "js-markup",
11150
11178
  "confidence": "certain"
11151
11179
  },
11180
+ {
11181
+ "id": "8b1e7aae1e5f",
11182
+ "surface": "builders-hall",
11183
+ "text": "Outside access",
11184
+ "file": "modules/hall-ui/public/government.html",
11185
+ "line": 123,
11186
+ "origin": "html-text",
11187
+ "confidence": "certain"
11188
+ },
11189
+ {
11190
+ "id": "20cf1adf4154",
11191
+ "surface": "builders-hall",
11192
+ "text": "Outside access could not be read just now.",
11193
+ "file": "modules/hall-ui/public/government-access.js",
11194
+ "line": 72,
11195
+ "origin": "js-markup",
11196
+ "confidence": "certain"
11197
+ },
11152
11198
  {
11153
11199
  "id": "d89d1923ecf9",
11154
11200
  "surface": "builders-hall",
@@ -16630,6 +16676,15 @@
16630
16676
  "origin": "html-text",
16631
16677
  "confidence": "certain"
16632
16678
  },
16679
+ {
16680
+ "id": "c6d830ca9e08",
16681
+ "surface": "builders-hall",
16682
+ "text": "Who holds the keys outside the project, so a blocker that needs one reaches them. This is a record of who to ask: it grants nothing here and stores no password or token.",
16683
+ "file": "modules/hall-ui/public/government.html",
16684
+ "line": 124,
16685
+ "origin": "html-text",
16686
+ "confidence": "certain"
16687
+ },
16633
16688
  {
16634
16689
  "id": "045c629729b7",
16635
16690
  "surface": "builders-hall",
@@ -3264,5 +3264,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
3264
3264
  The lifecycle feed and claim gate apply it. Additive, so MINOR.
3265
3265
  1.24.1 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3266
3266
  landed since 1.24.0 with no explicit bump. run 40985289858. (task 1002620)
3267
+ 1.24.2 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3268
+ landed since 1.24.1 with no explicit bump. run 41000649913. (task 1002620)
3267
3269
  ---------------------------------------------------------------------------
3268
3270
  ```
@@ -611,7 +611,8 @@
611
611
  "modules/hall-ui/public/palette.js",
612
612
  "modules/hall-ui/public/hall-kit.js",
613
613
  "modules/hall-ui/public/board-lib.js",
614
- "modules/hall-ui/public/government.js"
614
+ "modules/hall-ui/public/government.js",
615
+ "modules/hall-ui/public/government-access.js"
615
616
  ],
616
617
  "states_files": [
617
618
  "modules/hall-ui/public/government.states.json"
@@ -5,7 +5,7 @@
5
5
  "adr": "0341 D3",
6
6
  "reread": "node scripts/bongos/page-reader.js --stale",
7
7
  "how": ["Generated by node scripts/bongos/page-reader.js (BV2.TW04, task 1004315; ADR 0341 D3). Do not hand-edit.","One entry per page of docs/page-inventory.json. Lines are in page order: the default state first, and a line only another state shows sits where that state shows it, with its state named.","placement: placed = a docs/copy-registry.json row renders it (file, line, string_id); shared = shell text every page of the surface carries (top bar, sidebar, footer), with its row when the registry has one; unplaced = the registry cannot place it (a rewrite becomes an engineer task, ADR 0341 D4).","text keeps {…} where the registry row has a hole, or where a value the page read from its API was filled in. A string that is only such data is counted under omitted.data, not listed.","reading_hash is over the non-shared lines (key, section, text); files_hash is over the page's inventory files. --check compares files_hash only and never opens a browser.","Rendered through the hall-preview harness (hall) and the ui-design kit stub (landing, status), at 1280x800 with the clock pinned, so a reading is the fixture world's page."],
8
- "counts": {"pages":48,"lines":5447,"placed":1678,"shared":427,"unplaced":3342,"omitted_data":3705},
8
+ "counts": {"pages":48,"lines":5456,"placed":1683,"shared":427,"unplaced":3346,"omitted_data":3730},
9
9
  "pages": [
10
10
  {
11
11
  "id": "builders:ai-studio",
@@ -160,7 +160,7 @@
160
160
  "surface": "builders",
161
161
  "title": "Blockers",
162
162
  "states_read": ["default"],
163
- "files_hash": "dd1eef188b4d3bf8",
163
+ "files_hash": "439934ddd9d1a4d2",
164
164
  "reading_hash": "c393e54845d545ee",
165
165
  "counts": {"lines":34,"placed":14,"shared":8,"unplaced":12},
166
166
  "omitted": {"data":49},
@@ -327,7 +327,7 @@
327
327
  "surface": "builders",
328
328
  "title": "Collab",
329
329
  "states_read": ["board","out"],
330
- "files_hash": "75d4fa4c09ed937c",
330
+ "files_hash": "2e20ca6774af82fc",
331
331
  "reading_hash": "71803b40b959a9b9",
332
332
  "counts": {"lines":80,"placed":31,"shared":7,"unplaced":42},
333
333
  "omitted": {"data":21},
@@ -1456,10 +1456,10 @@
1456
1456
  "surface": "builders",
1457
1457
  "title": "Government",
1458
1458
  "states_read": ["constitution","out","permissions","board","constitution-tab"],
1459
- "files_hash": "a7bba5b1bc058a3c",
1460
- "reading_hash": "165c2459edf3678c",
1461
- "counts": {"lines":54,"placed":19,"shared":8,"unplaced":27},
1462
- "omitted": {"data":249},
1459
+ "files_hash": "25c1dc0003420a7c",
1460
+ "reading_hash": "18d8e460088c2550",
1461
+ "counts": {"lines":63,"placed":24,"shared":8,"unplaced":31},
1462
+ "omitted": {"data":274},
1463
1463
  "notes": ["permissions: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","board: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood","constitution-tab: an action failed (page.click: Timeout 6000ms exceeded.); read as it stood"],
1464
1464
  "lines": [
1465
1465
  {"key":"L0001","section":"skip link","text":"Skip to content","placement":"shared","file":"modules/hall-ui/public/shell.js","line":868,"string_id":"87cee78ca134"},
@@ -1503,19 +1503,28 @@
1503
1503
  {"key":"L0039","section":"Assignments","text":"{…} · via rank","placement":"unplaced","occurrences":2},
1504
1504
  {"key":"L0040","section":"Assignments","text":"rank","placement":"unplaced","occurrences":3},
1505
1505
  {"key":"L0041","section":"Assignments","text":"Remove","placement":"unplaced"},
1506
- {"key":"L0042","section":"Machine principals","text":"Machine principals","placement":"placed","file":"modules/hall-ui/public/government.html","line":120,"string_id":"06b4ede3fb58"},
1507
- {"key":"L0043","section":"Machine principals","text":"Capabilities held by a machine identity, never by a person or a rank. Shown for completeness; nothing here can be granted.","placement":"placed","file":"modules/hall-ui/public/government.html","line":121,"string_id":"084eb64bce72"},
1508
- {"key":"L0044","section":"Machine principals","text":"holds","placement":"unplaced","occurrences":2},
1509
- {"key":"L0045","section":"footer","text":"Sign out","placement":"shared","file":"modules/hall-ui/public/shell.js","line":516,"string_id":"1883ad7f07fc"},
1510
- {"key":"L0046","section":"Machine principals","text":"Builders hall · this session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":670,"string_id":"fd1903586e8e"},
1511
- {"key":"L0047","section":"What are you doing now?","text":"What are you doing now?","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":671,"string_id":"60853319f290"},
1512
- {"key":"L0048","section":"What are you doing now?","text":"Pick one. You can switch any time from the top bar.","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":672,"string_id":"4f87d5b17b54"},
1513
- {"key":"L0049","section":"What are you doing now?","text":"Building","placement":"unplaced"},
1514
- {"key":"L0050","section":"What are you doing now?","text":"{…} and {…}","placement":"placed","file":"modules/hall-ui/public/profile-nudge.js","line":77,"string_id":"56b00d773232","occurrences":3},
1515
- {"key":"L0051","section":"What are you doing now?","text":"Artistry","placement":"unplaced"},
1516
- {"key":"L0052","section":"What are you doing now?","text":"Ideating","placement":"unplaced"},
1517
- {"key":"L0053","section":"What are you doing now?","text":"Turn thoughts into ideas.","placement":"unplaced"},
1518
- {"key":"L0054","section":"What are you doing now?","text":"Ask me again next session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":675,"string_id":"fa86bf403775"}
1506
+ {"key":"L0042","section":"Outside access","text":"Outside access","placement":"placed","file":"modules/hall-ui/public/government.html","line":123,"string_id":"8b1e7aae1e5f"},
1507
+ {"key":"L0043","section":"Outside access","text":"Who holds the keys outside the project, so a blocker that needs one reaches them. This is a record of who to ask: it grants nothing here and stores no password or token.","placement":"placed","file":"modules/hall-ui/public/government.html","line":124,"string_id":"c6d830ca9e08"},
1508
+ {"key":"L0044","section":"Outside access","text":"Builder id","placement":"placed","file":"modules/hall-ui/public/government.html","line":107,"string_id":"08bfcc9198ba"},
1509
+ {"key":"L0045","section":"Outside access","text":"e.g. 95","placement":"unplaced","attr":"placeholder"},
1510
+ {"key":"L0046","section":"Outside access","text":"Access","placement":"unplaced"},
1511
+ {"key":"L0047","section":"Outside access","text":"Record","placement":"unplaced"},
1512
+ {"key":"L0048","section":"Outside access","text":"Remove","placement":"unplaced","occurrences":2},
1513
+ {"key":"L0049","section":"Outside access","text":"Nobody recorded — it falls to the owner","placement":"placed","file":"modules/hall-ui/public/government-access.js","line":35,"string_id":"380b924cd4a0","occurrences":4},
1514
+ {"key":"L0050","section":"Outside access","text":"{…} and {…}","placement":"placed","file":"modules/hall-ui/public/profile-nudge.js","line":77,"string_id":"56b00d773232"},
1515
+ {"key":"L0051","section":"Machine principals","text":"Machine principals","placement":"placed","file":"modules/hall-ui/public/government.html","line":140,"string_id":"06b4ede3fb58"},
1516
+ {"key":"L0052","section":"Machine principals","text":"Capabilities held by a machine identity, never by a person or a rank. Shown for completeness; nothing here can be granted.","placement":"placed","file":"modules/hall-ui/public/government.html","line":141,"string_id":"084eb64bce72"},
1517
+ {"key":"L0053","section":"Machine principals","text":"holds","placement":"unplaced","occurrences":2},
1518
+ {"key":"L0054","section":"footer","text":"Sign out","placement":"shared","file":"modules/hall-ui/public/shell.js","line":516,"string_id":"1883ad7f07fc"},
1519
+ {"key":"L0055","section":"Machine principals","text":"Builders hall · this session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":670,"string_id":"fd1903586e8e"},
1520
+ {"key":"L0056","section":"What are you doing now?","text":"What are you doing now?","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":671,"string_id":"60853319f290"},
1521
+ {"key":"L0057","section":"What are you doing now?","text":"Pick one. You can switch any time from the top bar.","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":672,"string_id":"4f87d5b17b54"},
1522
+ {"key":"L0058","section":"What are you doing now?","text":"Building","placement":"unplaced"},
1523
+ {"key":"L0059","section":"What are you doing now?","text":"{…} and {…}","placement":"placed","file":"modules/hall-ui/public/profile-nudge.js","line":77,"string_id":"56b00d773232","occurrences":3},
1524
+ {"key":"L0060","section":"What are you doing now?","text":"Artistry","placement":"unplaced"},
1525
+ {"key":"L0061","section":"What are you doing now?","text":"Ideating","placement":"unplaced"},
1526
+ {"key":"L0062","section":"What are you doing now?","text":"Turn thoughts into ideas.","placement":"unplaced"},
1527
+ {"key":"L0063","section":"What are you doing now?","text":"Ask me again next session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":675,"string_id":"fa86bf403775"}
1519
1528
  ]
1520
1529
  },
1521
1530
  {
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ // modules/government/access-routes.js — the handlers behind the three ACCESS HOLDERS
4
+ // routes (task 1005000). The routes themselves — path, permission gate and pin — are
5
+ // declared in routes/government.js, where route-rank-check reads them; only the bodies
6
+ // live here, so the router file stays under the size ratchet.
7
+ //
8
+ // GET /government/access (board.vote.cast) — read
9
+ // POST /government/access/:accessKey/holders (government.manage) — record
10
+ // DELETE /government/access/:accessKey/holders/:builderId (government.manage) — remove
11
+ //
12
+ // ⚠ A record of who to ask, never authority (access.js). Not act-alone routes: they
13
+ // record a fact about the world outside the project; they decide nothing a sitting
14
+ // votes on.
15
+
16
+ const access = require('./access');
17
+
18
+ // deps: { db, log, parseId, validateOrRespond } — the router's own, passed in so a
19
+ // test that stubs the db by property reaches these handlers too.
20
+ function accessHandlers({ db, log, parseId, validateOrRespond }) {
21
+ const keyOf = (req, res) => {
22
+ const key = String(req.params.accessKey || '');
23
+ if (access.isAccessKey(key)) return key;
24
+ res.fail('unknown_access', { status: 400, message: `Name one of: ${access.KEYS.join(', ')}.` });
25
+ return null;
26
+ };
27
+
28
+ async function read(req, res) {
29
+ try {
30
+ res.json({ access: access.accessView(await db.listAccessHolders()) });
31
+ } catch (err) {
32
+ log.error('[gds] GET /government/access', err);
33
+ res.fail('access_read_failed', { status: 500, message: 'internal error' });
34
+ }
35
+ }
36
+
37
+ async function grant(req, res) {
38
+ if (validateOrRespond(req, res, { builder_id: { required: true, type: 'integer', min: 1 } })) return;
39
+ const key = keyOf(req, res);
40
+ if (key === null) return;
41
+ const builderId = req.body.builder_id;
42
+ try {
43
+ const who = await db.getActiveBuilder(builderId);
44
+ if (!who) return res.fail('builder_not_found', { status: 404, message: 'No active builder with that id.' });
45
+ const added = await db.grantAccess(key, builderId, req.builder.id);
46
+ res.status(added ? 201 : 200).json({ ok: true, access_key: key, builder_id: String(builderId), added });
47
+ } catch (err) {
48
+ log.error('[gds] POST /government/access/:accessKey/holders', err);
49
+ res.fail('access_grant_failed', { status: 500, message: 'internal error' });
50
+ }
51
+ }
52
+
53
+ async function revoke(req, res) {
54
+ const key = keyOf(req, res);
55
+ if (key === null) return;
56
+ const builderId = parseId(req, res, { param: 'builderId', code: 'bad_builder_id', positiveInt: true });
57
+ if (builderId === null) return;
58
+ try {
59
+ const removed = await db.revokeAccess(key, builderId);
60
+ res.json({ ok: true, access_key: key, builder_id: String(builderId), removed });
61
+ } catch (err) {
62
+ log.error('[gds] DELETE /government/access/:accessKey/holders/:builderId', err);
63
+ res.fail('access_revoke_failed', { status: 500, message: 'internal error' });
64
+ }
65
+ }
66
+
67
+ return { read, grant, revoke };
68
+ }
69
+
70
+ module.exports = { accessHandlers };
@@ -0,0 +1,88 @@
1
+ 'use strict';
2
+
3
+ // modules/government/access.js — ACCESS HOLDERS: who holds the keys that live OUTSIDE
4
+ // Bongos, so a blocker that needs one reaches the person who has it (task 1005000,
5
+ // goal 1000111).
6
+ //
7
+ // WHY. Some blockers can only be cleared by whoever holds access no rank or Lead
8
+ // models: the GitHub repository's admin (token scopes, branch protection, the App's
9
+ // permissions), the server, the domain's DNS, the hosting account, package publishing,
10
+ // payments. An Archon in Bongos may not hold the GitHub admin, and a Lead never does.
11
+ // It lived only in prose (docs/credentials.md "account logins the owner holds") and in
12
+ // blocker text — blocker 1000199, the publish token missing GitHub's `workflow` scope,
13
+ // could only be filed to "the owner". The builder's words: repo owner is a category of
14
+ // blockers that go beyond normality.
15
+ //
16
+ // WHAT IT IS. A fixed vocabulary of outside-access keys (ACCESS, extendable here and in
17
+ // the migration's CHECK together), a record of which builders hold each
18
+ // (government_access_holders, written by the owner only), and a seat, `access:<key>`,
19
+ // that an ask can wait on. routing.js resolves that seat to the recorded holders, and
20
+ // to the owner when nobody is recorded — the same fall every other seat takes.
21
+ //
22
+ // ⚠ A RECORD OF WHO TO ASK, NEVER AN AUTHORITY GRANT. Holding github-admin here unlocks
23
+ // no Bongos route and no rank: this module is not read by requireRank, the permission
24
+ // resolver, or any gate, and an access key is not a permission atom. No secret is
25
+ // stored either — the row says WHO holds an access, never a credential or a token.
26
+ //
27
+ // PURE: no db.
28
+
29
+ const ACCESS = Object.freeze([
30
+ // name: how the seat reads after "waits on the"; needs: what a blocker on it needs.
31
+ Object.freeze({ key: 'github-admin', name: 'repo owner', needs: 'repo-owner access', what: 'Admin on the GitHub repository: token scopes, branch protection, the GitHub App.' }),
32
+ Object.freeze({ key: 'server-ssh', name: 'server access', needs: 'server access', what: 'A login on the server the project runs on.' }),
33
+ Object.freeze({ key: 'dns', name: 'domain settings', needs: 'access to the domain settings', what: 'The DNS account for the project\'s domain.' }),
34
+ Object.freeze({ key: 'hosting', name: 'hosting account', needs: 'access to the hosting account', what: 'The account that pays for and runs the project\'s servers.' }),
35
+ Object.freeze({ key: 'npm-publish', name: 'package publishing', needs: 'package publishing rights', what: 'Publishing rights for the project\'s packages.' }),
36
+ Object.freeze({ key: 'payments', name: 'payments', needs: 'access to payments', what: 'The account money moves through.' }),
37
+ ]);
38
+ const KEYS = Object.freeze(ACCESS.map((a) => a.key));
39
+ const isAccessKey = (k) => KEYS.includes(String(k || ''));
40
+ const byKey = (k) => ACCESS.find((a) => a.key === k) || null;
41
+
42
+ // The seat grammar's access half, for asks.js SEAT and the migration's CHECK.
43
+ const SEAT_PART = `access:(${KEYS.join('|')})`;
44
+
45
+ // "the repo owner" — how the seat reads after "waits on".
46
+ function accessLabel(key) {
47
+ const a = byKey(key);
48
+ return a ? `the ${a.name}` : 'the owner';
49
+ }
50
+
51
+ // "needs repo-owner access" — how a blocker on this seat reads under its holder.
52
+ function needsWords(key) {
53
+ const a = byKey(key);
54
+ return a ? `needs ${a.needs}` : 'needs outside access';
55
+ }
56
+
57
+ // The holders read as the seat resolver wants them: { key: [{ builder_id, github_login, display_name }] }.
58
+ function holdersByKey(rows) {
59
+ const out = {};
60
+ for (const k of KEYS) out[k] = [];
61
+ for (const r of rows || []) {
62
+ if (r && isAccessKey(r.access_key) && r.builder_id != null) out[r.access_key].push(r);
63
+ }
64
+ return out;
65
+ }
66
+
67
+ // The read GET /government/access answers: every access, its words, who holds it.
68
+ function accessView(rows) {
69
+ const held = holdersByKey(rows);
70
+ return ACCESS.map((a) => ({
71
+ key: a.key,
72
+ name: a.name,
73
+ // the one display form, for the list and the picker alike
74
+ title: a.name.charAt(0).toUpperCase() + a.name.slice(1),
75
+ what: a.what,
76
+ seat: `access:${a.key}`,
77
+ holders: held[a.key].map((r) => ({
78
+ id: String(r.builder_id),
79
+ login: r.github_login || null,
80
+ name: r.display_name || r.github_login || null,
81
+ since: r.granted_at || null,
82
+ })),
83
+ // nobody recorded: the seat falls to the owner, as every unheld seat does
84
+ falls_to_owner: held[a.key].length === 0,
85
+ }));
86
+ }
87
+
88
+ module.exports = { ACCESS, KEYS, SEAT_PART, isAccessKey, accessLabel, needsWords, holdersByKey, accessView };
@@ -44,10 +44,12 @@ const MARKUP = /\*\*|`|^#|\[[^\]]*\]\(|https?:\/\/|<\/?[a-z]/im;
44
44
  // Control characters other than tab and line breaks (line breaks have their own rule).
45
45
  // eslint-disable-next-line no-control-regex
46
46
  const CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
47
+ const access = require('./access');
47
48
  const BUILDINGS = Object.freeze(['charter', 'rules', 'disputes', 'people', 'planning', 'automation', 'review', 'release', 'treasury']);
48
49
  // The seats an ask can wait on (gov-living.js seatResolver's grammar). An
49
50
  // unseated building falls to the owner when it is routed (owner ruling).
50
- const SEAT = new RegExp(`^(owner|(lead|board):(${BUILDINGS.join('|')})|committee:(${BUILDINGS.join('|')})\\.[a-z][a-z0-9_]{0,31})$`);
51
+ // `access:<key>` (task 1005000): whoever holds that outside access (access.js).
52
+ const SEAT = new RegExp(`^(owner|(lead|board):(${BUILDINGS.join('|')})|committee:(${BUILDINGS.join('|')})\\.[a-z][a-z0-9_]{0,31}|${access.SEAT_PART})$`);
51
53
  // An answer's key: what a tap sends. Its act, optional: the matter's own act it
52
54
  // takes ('go ahead', 'record', 'force:tested_by_hand'); the deck maps it onto
53
55
  // the desk row's real route and refuses one the row does not offer.
@@ -150,7 +152,7 @@ function checkAsk(raw) {
150
152
  // ---- who it waits on: a seat the direction desk can route ----
151
153
  const seat = str(c.waits_on).trim();
152
154
  if (!seat) err('waits_on', 'missing', 'Say who this waits on.');
153
- else if (!SEAT.test(seat)) err('waits_on', 'unknown_seat', `"${seat.slice(0, 40)}" is not a seat: say owner, lead:<building>, committee:<building>.<name> or board:<building>.`);
155
+ else if (!SEAT.test(seat)) err('waits_on', 'unknown_seat', `"${seat.slice(0, 40)}" is not a seat: say owner, lead:<building>, committee:<building>.<name>, board:<building> or access:<key>.`);
154
156
 
155
157
  // ---- what happens if nobody answers ----
156
158
  lineRule('if_nobody_answers', c.if_nobody_answers, 'Say what happens if nobody answers.');
@@ -206,6 +208,7 @@ const ASK_REQUIRED_MESSAGE = 'An AI session files a blocker with an ask: a quest
206
208
  function seatWords(seat) {
207
209
  const s = str(seat);
208
210
  if (s === 'owner') return 'the owner';
211
+ if (/^access:/.test(s)) return access.accessLabel(s.slice(7));
209
212
  const m = /^(lead|board|committee):([a-z]+)(?:\.([a-z0-9_]+))?$/.exec(s);
210
213
  if (!m) return s;
211
214
  const building = m[2].charAt(0).toUpperCase() + m[2].slice(1);
@@ -888,6 +888,41 @@ async function listActiveArchons() {
888
888
  return rows;
889
889
  }
890
890
 
891
+ // ACCESS HOLDERS (task 1005000, government_036): who holds each outside access.
892
+ // Active builders only — someone who left the project is nobody to ask. A record
893
+ // of who to ask, never authority: no gate reads these rows (access.js).
894
+ async function listAccessHolders() {
895
+ const { rows } = await pool.query(
896
+ `SELECT h.access_key, h.builder_id, h.granted_at, b.github_login, b.display_name
897
+ FROM government_access_holders h
898
+ JOIN builders b ON b.id = h.builder_id
899
+ WHERE b.status = 'active' AND b.system_role IS NULL
900
+ ORDER BY h.access_key, h.granted_at, h.builder_id`,
901
+ );
902
+ return rows;
903
+ }
904
+
905
+ // Record that a builder holds an access. Idempotent: an existing row is kept as it
906
+ // was (its original granted_at/by). → true when a row was added.
907
+ async function grantAccess(accessKey, builderId, grantedBy) {
908
+ const { rowCount } = await pool.query(
909
+ `INSERT INTO government_access_holders (access_key, builder_id, granted_by)
910
+ VALUES ($1, $2, $3)
911
+ ON CONFLICT (access_key, builder_id) DO NOTHING`,
912
+ [accessKey, builderId, grantedBy],
913
+ );
914
+ return rowCount > 0;
915
+ }
916
+
917
+ // → true when a row was removed.
918
+ async function revokeAccess(accessKey, builderId) {
919
+ const { rowCount } = await pool.query(
920
+ 'DELETE FROM government_access_holders WHERE access_key = $1 AND builder_id = $2',
921
+ [accessKey, builderId],
922
+ );
923
+ return rowCount > 0;
924
+ }
925
+
891
926
  // One builder of this project, or null: who a Lead appointment names. Only an
892
927
  // active person (no machine identity) can be appointed.
893
928
  async function getActiveBuilder(builderId) {
@@ -1306,6 +1341,9 @@ module.exports = {
1306
1341
  applyLaws,
1307
1342
  listLeads,
1308
1343
  listActiveArchons,
1344
+ listAccessHolders,
1345
+ grantAccess,
1346
+ revokeAccess,
1309
1347
  getActiveBuilder,
1310
1348
  applyLeadAct,
1311
1349
  listBuildouts,
@@ -86,6 +86,7 @@ const governmentConfig = require('./config');
86
86
  const asksDb = require('./asks-db');
87
87
  const routing = require('./routing');
88
88
  const waiting = require('./waiting');
89
+ const access = require('./access');
89
90
  const { ownerOf } = require('./leads');
90
91
 
91
92
  const log = api.logger('government');
@@ -840,8 +841,17 @@ async function docketFor(builder) {
840
841
  // nothing — the docket then says `routed: false` rather than guess whose it is.
841
842
  async function loadSeats(government) {
842
843
  try {
843
- const [leads, archons] = await Promise.all([db.listLeads(), db.listActiveArchons()]);
844
- return { leads, owner: ownerOf(archons), holders: (government && government.holders) || {} };
844
+ // task 1005000: who holds each outside access. Its own read fails OPEN to nobody
845
+ // recorded (every access seat then falls to the owner), so a missing table on an
846
+ // instance that has not migrated yet never stops the City routing. Started inside
847
+ // a .then so a db layer without the function (an older stub, the walk's store)
848
+ // fails open too rather than throwing before the catch is attached.
849
+ const accessRows = Promise.resolve().then(() => db.listAccessHolders()).catch((err) => {
850
+ log.warn({ err: err && err.message }, 'governor docket: access holders unreadable, access seats fall to the owner');
851
+ return [];
852
+ });
853
+ const [leads, archons, held] = await Promise.all([db.listLeads(), db.listActiveArchons(), accessRows]);
854
+ return { leads, owner: ownerOf(archons), holders: (government && government.holders) || {}, access: access.holdersByKey(held) };
845
855
  } catch (err) {
846
856
  log.warn({ err: err && err.message }, 'governor docket: seats unreadable, nothing routed');
847
857
  return null;
@@ -0,0 +1,33 @@
1
+ -- government_036_access_holders.sql — who holds the keys OUTSIDE Bongos, and an ask
2
+ -- that waits on them (task 1005000, goal 1000111).
3
+ --
4
+ -- WHAT THIS ADDS
5
+ --
6
+ -- government_access_holders — one row per (outside access, builder): the GitHub
7
+ -- repository's admin, the server, the domain's DNS, the hosting account, package
8
+ -- publishing, payments. The vocabulary is modules/government/access.js `ACCESS`,
9
+ -- held equal to the CHECK below by tests/access_holders.mjs. Written by the owner
10
+ -- only (the routes gate government.manage); who granted it and when ride the row.
11
+ --
12
+ -- It is a RECORD OF WHO TO ASK, never an authority grant: no permission atom, rank
13
+ -- check or gate reads this table, and it stores no credential — only who holds one.
14
+ --
15
+ -- government_asks.waits_on gains the seat `access:<key>`, so an ask can wait on
16
+ -- whoever holds an outside access (routing.js; nobody recorded → the owner).
17
+
18
+ BEGIN;
19
+
20
+ CREATE TABLE IF NOT EXISTS government_access_holders (
21
+ access_key text NOT NULL
22
+ CHECK (access_key IN ('github-admin', 'server-ssh', 'dns', 'hosting', 'npm-publish', 'payments')),
23
+ builder_id bigint NOT NULL REFERENCES builders(id) ON DELETE CASCADE,
24
+ granted_by bigint REFERENCES builders(id) ON DELETE SET NULL,
25
+ granted_at timestamptz NOT NULL DEFAULT now(),
26
+ PRIMARY KEY (access_key, builder_id)
27
+ );
28
+
29
+ ALTER TABLE government_asks DROP CONSTRAINT IF EXISTS government_asks_waits_on_check;
30
+ ALTER TABLE government_asks ADD CONSTRAINT government_asks_waits_on_check
31
+ CHECK (waits_on ~ '^(owner|(lead|board):(charter|rules|disputes|people|planning|automation|review|release|treasury)|committee:(charter|rules|disputes|people|planning|automation|review|release|treasury)\.[a-z][a-z0-9_]{0,31}|access:(github-admin|server-ssh|dns|hosting|npm-publish|payments))$');
32
+
33
+ COMMIT;