@bongos/core 1.24.72 → 1.24.74
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.
- package/.bongos-core.json +107 -77
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +2 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/api/openapi.json +36 -2
- package/docs/api-reference.md +3 -2
- package/docs/copy-inventory.md +5 -5
- package/docs/copy-registry.json +5 -5
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +33 -0
- package/docs/page-readings.json +33 -33
- package/modules/government/board.js +19 -7
- package/modules/government/config.js +21 -9
- package/modules/government/constitution.js +224 -0
- package/modules/government/db.js +28 -0
- package/modules/government/migrations/government_037_constitution.sql +33 -0
- package/modules/government/module.json +2 -1
- package/modules/government/pollers/constitution-restore.js +45 -0
- package/modules/government/routes/government.js +7 -7
- package/modules/hall-ui/public/ai-studio.html +2 -1
- package/modules/hall-ui/public/atlas.html +2 -1
- package/modules/hall-ui/public/blockers.html +2 -1
- package/modules/hall-ui/public/board-room.html +2 -1
- package/modules/hall-ui/public/board-room.js +7 -3
- package/modules/hall-ui/public/city.html +2 -1
- package/modules/hall-ui/public/collab.html +2 -1
- package/modules/hall-ui/public/commands.html +2 -1
- package/modules/hall-ui/public/copy-desk.html +2 -1
- package/modules/hall-ui/public/deploy.html +2 -1
- package/modules/hall-ui/public/goal-map.html +2 -1
- package/modules/hall-ui/public/goals.html +2 -1
- package/modules/hall-ui/public/government.html +2 -1
- package/modules/hall-ui/public/idea.html +2 -1
- package/modules/hall-ui/public/ideas.html +2 -1
- package/modules/hall-ui/public/index.html +2 -1
- package/modules/hall-ui/public/market-watch-item.html +2 -1
- package/modules/hall-ui/public/market-watch-rules.html +2 -1
- package/modules/hall-ui/public/market-watch.html +2 -1
- package/modules/hall-ui/public/modules.html +2 -1
- package/modules/hall-ui/public/money.html +2 -1
- package/modules/hall-ui/public/my-agents.html +2 -1
- package/modules/hall-ui/public/primer.html +2 -1
- package/modules/hall-ui/public/profile.html +2 -1
- package/modules/hall-ui/public/project-settings.html +2 -1
- package/modules/hall-ui/public/roster.html +2 -1
- package/modules/hall-ui/public/settings.html +2 -1
- package/modules/hall-ui/public/shell-game.js +50 -0
- package/modules/hall-ui/public/shell.js +11 -11
- package/modules/hall-ui/public/street-card.js +2 -0
- package/modules/hall-ui/public/street.html +2 -1
- package/modules/hall-ui/public/studio.html +2 -1
- package/modules/hall-ui/public/task.html +2 -1
- package/modules/hall-ui/public/thinking.html +2 -1
- package/modules/hall-ui/public/tweak-editor.html +2 -1
- package/modules/hall-ui/public/watch.html +2 -1
- package/modules/hall-ui/public/work.html +2 -1
- package/modules/hall-ui/records/game-mode.md +20 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/bongos/government-walk.js +7 -0
- package/src/bongos/route-rank-check.js +4 -0
- package/src/module-api.js +1 -1
- package/tests/genesis_government.mjs +11 -0
- package/tests/goal_map_page.mjs +2 -2
- package/tests/government_board_amendment.mjs +10 -0
- package/tests/government_charter_forms.mjs +10 -0
- package/tests/government_constitution_durable.mjs +304 -0
- package/tests/government_seed.mjs +1 -0
- package/tests/hall_city.mjs +1 -1
- package/tests/hall_fullscreen.mjs +101 -0
- package/tests/hall_my_agents.mjs +1 -1
- package/tests/hall_street.mjs +1 -1
- package/tests/nav_permission_atoms.mjs +1 -1
package/docs/page-readings.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"surface": "builders",
|
|
13
13
|
"title": "Studio",
|
|
14
14
|
"states_read": ["pick","describe","make","waiting","result","result-own","failed","balance","balance-all","out"],
|
|
15
|
-
"files_hash": "
|
|
15
|
+
"files_hash": "4f71b6714f7e2d61",
|
|
16
16
|
"reading_hash": "e381a3141dc6b9cb",
|
|
17
17
|
"counts": {"lines":105,"placed":38,"shared":9,"unplaced":58},
|
|
18
18
|
"omitted": {"data":15},
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
"surface": "builders",
|
|
131
131
|
"title": "Repo Atlas",
|
|
132
132
|
"states_read": ["map","truth","detail","index","out"],
|
|
133
|
-
"files_hash": "
|
|
133
|
+
"files_hash": "5c3b63261c77b636",
|
|
134
134
|
"reading_hash": "94058beb635218ba",
|
|
135
135
|
"counts": {"lines":24,"placed":11,"shared":9,"unplaced":4},
|
|
136
136
|
"omitted": {"data":3},
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
"surface": "builders",
|
|
168
168
|
"title": "Blockers",
|
|
169
169
|
"states_read": ["default"],
|
|
170
|
-
"files_hash": "
|
|
170
|
+
"files_hash": "ab533cabec76122e",
|
|
171
171
|
"reading_hash": "ab984bd8e91a5a9d",
|
|
172
172
|
"counts": {"lines":35,"placed":14,"shared":9,"unplaced":12},
|
|
173
173
|
"omitted": {"data":49},
|
|
@@ -215,7 +215,7 @@
|
|
|
215
215
|
"surface": "builders",
|
|
216
216
|
"title": "Board Room",
|
|
217
217
|
"states_read": ["room","out","sitting-meta","objection-form"],
|
|
218
|
-
"files_hash": "
|
|
218
|
+
"files_hash": "9461cd6cbc270ae2",
|
|
219
219
|
"reading_hash": "020553cb2a438d56",
|
|
220
220
|
"counts": {"lines":51,"placed":18,"shared":9,"unplaced":24},
|
|
221
221
|
"omitted": {"data":15},
|
|
@@ -279,7 +279,7 @@
|
|
|
279
279
|
"surface": "builders",
|
|
280
280
|
"title": "The city",
|
|
281
281
|
"states_read": ["city","fullscreen","switched","focus","pins","living","card","card-ask"],
|
|
282
|
-
"files_hash": "
|
|
282
|
+
"files_hash": "9ed0417e2e4dfb92",
|
|
283
283
|
"reading_hash": "11234462e18a0037",
|
|
284
284
|
"counts": {"lines":44,"placed":10,"shared":9,"unplaced":25},
|
|
285
285
|
"omitted": {"data":18},
|
|
@@ -336,7 +336,7 @@
|
|
|
336
336
|
"surface": "builders",
|
|
337
337
|
"title": "Collab",
|
|
338
338
|
"states_read": ["board","out"],
|
|
339
|
-
"files_hash": "
|
|
339
|
+
"files_hash": "887a503d80d5c0e2",
|
|
340
340
|
"reading_hash": "2d5df14f73c44d86",
|
|
341
341
|
"counts": {"lines":81,"placed":33,"shared":8,"unplaced":40},
|
|
342
342
|
"omitted": {"data":21},
|
|
@@ -430,7 +430,7 @@
|
|
|
430
430
|
"surface": "builders",
|
|
431
431
|
"title": "Commands",
|
|
432
432
|
"states_read": ["in","out"],
|
|
433
|
-
"files_hash": "
|
|
433
|
+
"files_hash": "687bb6af0eff6e0c",
|
|
434
434
|
"reading_hash": "b7107deecbf6bf46",
|
|
435
435
|
"counts": {"lines":70,"placed":46,"shared":9,"unplaced":15},
|
|
436
436
|
"omitted": {"data":175},
|
|
@@ -513,7 +513,7 @@
|
|
|
513
513
|
"surface": "builders",
|
|
514
514
|
"title": "Copy desk",
|
|
515
515
|
"states_read": ["queue","out","search","composer","closer","proposer"],
|
|
516
|
-
"files_hash": "
|
|
516
|
+
"files_hash": "6d4903527b0a1c30",
|
|
517
517
|
"reading_hash": "4476deef6b37ffe5",
|
|
518
518
|
"counts": {"lines":78,"placed":39,"shared":9,"unplaced":30},
|
|
519
519
|
"omitted": {"data":119},
|
|
@@ -604,7 +604,7 @@
|
|
|
604
604
|
"surface": "builders",
|
|
605
605
|
"title": "Deploy",
|
|
606
606
|
"states_read": ["work","out"],
|
|
607
|
-
"files_hash": "
|
|
607
|
+
"files_hash": "a0ab8445ee5bf294",
|
|
608
608
|
"reading_hash": "76c11d0fbc739610",
|
|
609
609
|
"counts": {"lines":19,"placed":5,"shared":9,"unplaced":5},
|
|
610
610
|
"omitted": {"data":3},
|
|
@@ -636,7 +636,7 @@
|
|
|
636
636
|
"surface": "builders",
|
|
637
637
|
"title": "The sky",
|
|
638
638
|
"states_read": ["tour","sky","wells","ledger","full","full-back","out"],
|
|
639
|
-
"files_hash": "
|
|
639
|
+
"files_hash": "b47d4ef94ef6d2c5",
|
|
640
640
|
"reading_hash": "2ae796b3e72c6d26",
|
|
641
641
|
"counts": {"lines":49,"placed":22,"shared":8,"unplaced":19},
|
|
642
642
|
"omitted": {"data":8},
|
|
@@ -698,7 +698,7 @@
|
|
|
698
698
|
"surface": "builders",
|
|
699
699
|
"title": "Goals",
|
|
700
700
|
"states_read": ["list","filters","cards","detail","dependencies","out","uat","uat-panel","author"],
|
|
701
|
-
"files_hash": "
|
|
701
|
+
"files_hash": "ddc111d1819224ad",
|
|
702
702
|
"reading_hash": "82f9b0ae157d2700",
|
|
703
703
|
"counts": {"lines":186,"placed":58,"shared":9,"unplaced":119},
|
|
704
704
|
"omitted": {"data":313},
|
|
@@ -897,7 +897,7 @@
|
|
|
897
897
|
"surface": "builders",
|
|
898
898
|
"title": "Government",
|
|
899
899
|
"states_read": ["constitution","out","permissions","board","constitution-tab"],
|
|
900
|
-
"files_hash": "
|
|
900
|
+
"files_hash": "3a9b128ca492be07",
|
|
901
901
|
"reading_hash": "0d1ee732e57189fb",
|
|
902
902
|
"counts": {"lines":64,"placed":24,"shared":9,"unplaced":31},
|
|
903
903
|
"omitted": {"data":274},
|
|
@@ -974,7 +974,7 @@
|
|
|
974
974
|
"surface": "builders",
|
|
975
975
|
"title": "Builders' Hall",
|
|
976
976
|
"states_read": ["home","out","founding","genesis","decide","demo"],
|
|
977
|
-
"files_hash": "
|
|
977
|
+
"files_hash": "5c8e267006cb62e1",
|
|
978
978
|
"reading_hash": "4cc2e5e86483a66e",
|
|
979
979
|
"counts": {"lines":28,"placed":9,"shared":10,"unplaced":9},
|
|
980
980
|
"omitted": {"data":4},
|
|
@@ -1015,7 +1015,7 @@
|
|
|
1015
1015
|
"surface": "builders",
|
|
1016
1016
|
"title": "Idea",
|
|
1017
1017
|
"states_read": ["record","missing"],
|
|
1018
|
-
"files_hash": "
|
|
1018
|
+
"files_hash": "d31ed5e538fa57ac",
|
|
1019
1019
|
"reading_hash": "1924057e1bb86631",
|
|
1020
1020
|
"counts": {"lines":51,"placed":22,"shared":10,"unplaced":19},
|
|
1021
1021
|
"omitted": {"data":23},
|
|
@@ -1079,7 +1079,7 @@
|
|
|
1079
1079
|
"surface": "builders",
|
|
1080
1080
|
"title": "Ideas",
|
|
1081
1081
|
"states_read": ["compose","full","develop"],
|
|
1082
|
-
"files_hash": "
|
|
1082
|
+
"files_hash": "59081864d4eabae8",
|
|
1083
1083
|
"reading_hash": "9ed251f7cc60a885",
|
|
1084
1084
|
"counts": {"lines":67,"placed":31,"shared":9,"unplaced":27},
|
|
1085
1085
|
"omitted": {"data":116},
|
|
@@ -1159,7 +1159,7 @@
|
|
|
1159
1159
|
"surface": "builders",
|
|
1160
1160
|
"title": "Market Watch",
|
|
1161
1161
|
"states_read": ["desk","settings","out"],
|
|
1162
|
-
"files_hash": "
|
|
1162
|
+
"files_hash": "a46496a5cf19a695",
|
|
1163
1163
|
"reading_hash": "de1a8fdf7ebcc959",
|
|
1164
1164
|
"counts": {"lines":13,"placed":3,"shared":9,"unplaced":1},
|
|
1165
1165
|
"omitted": {"data":2},
|
|
@@ -1185,7 +1185,7 @@
|
|
|
1185
1185
|
"surface": "builders",
|
|
1186
1186
|
"title": "Market Watch",
|
|
1187
1187
|
"states_read": ["dossier","out","propose","correct","filtered"],
|
|
1188
|
-
"files_hash": "
|
|
1188
|
+
"files_hash": "f3b5b24581756454",
|
|
1189
1189
|
"reading_hash": "12483255342bd007",
|
|
1190
1190
|
"counts": {"lines":14,"placed":4,"shared":9,"unplaced":1},
|
|
1191
1191
|
"omitted": {"data":2},
|
|
@@ -1212,7 +1212,7 @@
|
|
|
1212
1212
|
"surface": "builders",
|
|
1213
1213
|
"title": "Market Watch rules",
|
|
1214
1214
|
"states_read": ["in","scoring","names","propose","out"],
|
|
1215
|
-
"files_hash": "
|
|
1215
|
+
"files_hash": "d37cdb4cc97ae6a3",
|
|
1216
1216
|
"reading_hash": "bf661622dd283b83",
|
|
1217
1217
|
"counts": {"lines":48,"placed":13,"shared":9,"unplaced":26},
|
|
1218
1218
|
"omitted": {"data":45},
|
|
@@ -1273,7 +1273,7 @@
|
|
|
1273
1273
|
"surface": "builders",
|
|
1274
1274
|
"title": "Modules",
|
|
1275
1275
|
"states_read": ["browse","search-empty","lens","details","out"],
|
|
1276
|
-
"files_hash": "
|
|
1276
|
+
"files_hash": "85e1f48677ea96f7",
|
|
1277
1277
|
"reading_hash": "cdf89441f9e9516e",
|
|
1278
1278
|
"counts": {"lines":60,"placed":26,"shared":9,"unplaced":25},
|
|
1279
1279
|
"omitted": {"data":105},
|
|
@@ -1346,7 +1346,7 @@
|
|
|
1346
1346
|
"surface": "builders",
|
|
1347
1347
|
"title": "Money",
|
|
1348
1348
|
"states_read": ["money","hosting","out"],
|
|
1349
|
-
"files_hash": "
|
|
1349
|
+
"files_hash": "cbf3f8ed30ccfff6",
|
|
1350
1350
|
"reading_hash": "285d714c7aba1fc9",
|
|
1351
1351
|
"counts": {"lines":82,"placed":25,"shared":9,"unplaced":48},
|
|
1352
1352
|
"omitted": {"data":25},
|
|
@@ -1441,7 +1441,7 @@
|
|
|
1441
1441
|
"surface": "builders",
|
|
1442
1442
|
"title": "My agents",
|
|
1443
1443
|
"states_read": ["desk","factory","saved","what-it-knows","speciality-walk","speciality-forms","nobody"],
|
|
1444
|
-
"files_hash": "
|
|
1444
|
+
"files_hash": "25a31340d16b79d9",
|
|
1445
1445
|
"reading_hash": "74d10b6c2a24ce2e",
|
|
1446
1446
|
"counts": {"lines":138,"placed":60,"shared":9,"unplaced":69},
|
|
1447
1447
|
"omitted": {"data":171},
|
|
@@ -1592,7 +1592,7 @@
|
|
|
1592
1592
|
"surface": "builders",
|
|
1593
1593
|
"title": "Primer",
|
|
1594
1594
|
"states_read": ["in","toc-open","jump","out"],
|
|
1595
|
-
"files_hash": "
|
|
1595
|
+
"files_hash": "93efc9b10054a26a",
|
|
1596
1596
|
"reading_hash": "395c98d876001b5a",
|
|
1597
1597
|
"counts": {"lines":486,"placed":54,"shared":9,"unplaced":423},
|
|
1598
1598
|
"omitted": {"data":16},
|
|
@@ -2091,7 +2091,7 @@
|
|
|
2091
2091
|
"surface": "builders",
|
|
2092
2092
|
"title": "Profile",
|
|
2093
2093
|
"states_read": ["own","works","activity","yours","activity-artist","activity-ideator","other","artist","ideator","out"],
|
|
2094
|
-
"files_hash": "
|
|
2094
|
+
"files_hash": "fd066c9c1add890e",
|
|
2095
2095
|
"reading_hash": "fe899712427882ff",
|
|
2096
2096
|
"counts": {"lines":219,"placed":77,"shared":9,"unplaced":133},
|
|
2097
2097
|
"omitted": {"data":250},
|
|
@@ -2323,7 +2323,7 @@
|
|
|
2323
2323
|
"surface": "builders",
|
|
2324
2324
|
"title": "Project settings",
|
|
2325
2325
|
"states_read": ["default"],
|
|
2326
|
-
"files_hash": "
|
|
2326
|
+
"files_hash": "2c0f365687004dda",
|
|
2327
2327
|
"reading_hash": "849399ad428ae9c7",
|
|
2328
2328
|
"counts": {"lines":42,"placed":22,"shared":9,"unplaced":11},
|
|
2329
2329
|
"omitted": {"data":31},
|
|
@@ -2378,7 +2378,7 @@
|
|
|
2378
2378
|
"surface": "builders",
|
|
2379
2379
|
"title": "Builders",
|
|
2380
2380
|
"states_read": ["roster","artists","search","out"],
|
|
2381
|
-
"files_hash": "
|
|
2381
|
+
"files_hash": "562ab7294ed7994a",
|
|
2382
2382
|
"reading_hash": "ab1f237b3ce6f134",
|
|
2383
2383
|
"counts": {"lines":58,"placed":14,"shared":9,"unplaced":35},
|
|
2384
2384
|
"omitted": {"data":135},
|
|
@@ -2449,7 +2449,7 @@
|
|
|
2449
2449
|
"surface": "builders",
|
|
2450
2450
|
"title": "Settings",
|
|
2451
2451
|
"states_read": ["account","access","preferences","install-reports","sound","update"],
|
|
2452
|
-
"files_hash": "
|
|
2452
|
+
"files_hash": "685465c1780ddf1b",
|
|
2453
2453
|
"reading_hash": "085bb32894909ee0",
|
|
2454
2454
|
"counts": {"lines":137,"placed":90,"shared":9,"unplaced":38},
|
|
2455
2455
|
"omitted": {"data":16},
|
|
@@ -2599,7 +2599,7 @@
|
|
|
2599
2599
|
"surface": "builders",
|
|
2600
2600
|
"title": "The street",
|
|
2601
2601
|
"states_read": ["street","fullscreen","card","switched","focus","here","inside-rules","inside-disputes","inside-people","inside-planning","inside-automation","inside-review","inside-release","inside-treasury","floor","amend","district","desk","desk-facts","desk-quiet","desk-people-builders","desk-people-tier","desk-disputes","desk-automation-goals","desk-release-core","desk-rules","desk-rules-category","desk-planning-priorities","desk-planning-ideas","desk-planning-blockers","pins","pins-pager","living","deck","deck-treasury","sitting","object","voted","admission","settled","blocker","blocker-more","report","report-more","core","core-more","recheck","recheck-more","idea","idea-more","queue","queue-more","automation","automation-more"],
|
|
2602
|
-
"files_hash": "
|
|
2602
|
+
"files_hash": "bcc43de9ec38fb26",
|
|
2603
2603
|
"reading_hash": "8e9e3422f6b72884",
|
|
2604
2604
|
"counts": {"lines":460,"placed":65,"shared":9,"unplaced":386},
|
|
2605
2605
|
"omitted": {"data":433},
|
|
@@ -3072,7 +3072,7 @@
|
|
|
3072
3072
|
"surface": "builders",
|
|
3073
3073
|
"title": "Studio",
|
|
3074
3074
|
"states_read": ["home","out","flow","flow-pick","status","words-offer","words-edit","reviews","full","full-back","approvals","approvals-before","approvals-phone","approvals-note","approved","sent-back","approvals-page"],
|
|
3075
|
-
"files_hash": "
|
|
3075
|
+
"files_hash": "6b37ea0de369e584",
|
|
3076
3076
|
"reading_hash": "55463fb0c09b5d71",
|
|
3077
3077
|
"counts": {"lines":50,"placed":26,"shared":9,"unplaced":15},
|
|
3078
3078
|
"omitted": {"data":21},
|
|
@@ -3135,7 +3135,7 @@
|
|
|
3135
3135
|
"surface": "builders",
|
|
3136
3136
|
"title": "Task",
|
|
3137
3137
|
"states_read": ["record","from-goal","missing"],
|
|
3138
|
-
"files_hash": "
|
|
3138
|
+
"files_hash": "7f1c8fcc8e9f83d5",
|
|
3139
3139
|
"reading_hash": "ecaa5fba5bc119a8",
|
|
3140
3140
|
"counts": {"lines":150,"placed":43,"shared":11,"unplaced":96},
|
|
3141
3141
|
"omitted": {"data":109},
|
|
@@ -3298,7 +3298,7 @@
|
|
|
3298
3298
|
"surface": "builders",
|
|
3299
3299
|
"title": "Your thinking",
|
|
3300
3300
|
"states_read": ["tour","sky","list","simple","full-idea","full","full-list","full-back","out"],
|
|
3301
|
-
"files_hash": "
|
|
3301
|
+
"files_hash": "ec5269e1f8585592",
|
|
3302
3302
|
"reading_hash": "8113aa9e50ec5600",
|
|
3303
3303
|
"counts": {"lines":49,"placed":22,"shared":8,"unplaced":19},
|
|
3304
3304
|
"omitted": {"data":8},
|
|
@@ -3360,7 +3360,7 @@
|
|
|
3360
3360
|
"surface": "builders",
|
|
3361
3361
|
"title": "Tweak a page",
|
|
3362
3362
|
"states_read": ["doc","typed","chrome","submitted","picking","out"],
|
|
3363
|
-
"files_hash": "
|
|
3363
|
+
"files_hash": "cbd96ef300b7e801",
|
|
3364
3364
|
"reading_hash": "711c540ebc7ef977",
|
|
3365
3365
|
"counts": {"lines":23,"placed":15,"shared":1,"unplaced":7},
|
|
3366
3366
|
"omitted": {"data":35},
|
|
@@ -3396,7 +3396,7 @@
|
|
|
3396
3396
|
"surface": "builders",
|
|
3397
3397
|
"title": "Watch",
|
|
3398
3398
|
"states_read": ["board","out","operations","security","sessions","autobongos"],
|
|
3399
|
-
"files_hash": "
|
|
3399
|
+
"files_hash": "08e46562439e6d7b",
|
|
3400
3400
|
"reading_hash": "2e2e07eab2e3e772",
|
|
3401
3401
|
"counts": {"lines":69,"placed":28,"shared":9,"unplaced":32},
|
|
3402
3402
|
"omitted": {"data":50},
|
|
@@ -3478,7 +3478,7 @@
|
|
|
3478
3478
|
"surface": "builders",
|
|
3479
3479
|
"title": "Tasks",
|
|
3480
3480
|
"states_read": ["ready","in-progress","backlog","completed","filters-open","out","founding"],
|
|
3481
|
-
"files_hash": "
|
|
3481
|
+
"files_hash": "469be6bdd4773fb7",
|
|
3482
3482
|
"reading_hash": "f1d7a8ed45e60ffa",
|
|
3483
3483
|
"counts": {"lines":122,"placed":46,"shared":9,"unplaced":67},
|
|
3484
3484
|
"omitted": {"data":321},
|
|
@@ -47,6 +47,9 @@ const db = require('./db');
|
|
|
47
47
|
// file (the reward-port delegation lesson: a destructured binding is frozen at
|
|
48
48
|
// load and un-patchable).
|
|
49
49
|
const governmentConfig = require('./config');
|
|
50
|
+
// The durable half of applying a passed amendment (task 1005140), required as a
|
|
51
|
+
// namespace for the same reason.
|
|
52
|
+
const constitutionStore = require('./constitution');
|
|
50
53
|
const {
|
|
51
54
|
loadGovernmentConfig, PASS_RULES, MAX_WINDOW_MINUTES,
|
|
52
55
|
RECOMMENDED_WINDOW_MINUTES, CLOCK_SAFE_PASS_RULES, isClockSafe,
|
|
@@ -668,17 +671,25 @@ async function closeItem(item, { cause = 'vote' } = {}) {
|
|
|
668
671
|
// close commits (the board's decision is the durable record), loudly on
|
|
669
672
|
// failure, never silently absorbed. The apply is re-drivable from the passed
|
|
670
673
|
// item + amendment rows. A returned amendment applies nothing.
|
|
674
|
+
//
|
|
675
|
+
// Task 1005140: the ROW is written first (a container's file does not survive
|
|
676
|
+
// a restart), then this process's file, and then both are READ BACK. An
|
|
677
|
+
// amendment that did not take comes back as `constitution_not_applied`, which
|
|
678
|
+
// the vote route hands to the page the voter is standing on.
|
|
671
679
|
let appliedConstitution = null;
|
|
680
|
+
let constitutionNotApplied = null;
|
|
672
681
|
// A rules or laws amendment changes its own table, never the board block.
|
|
673
682
|
if (outcome === 'passed' && closed.subject_type === 'constitutional_amendment' && !rulesAmendment && !lawsAmendment) {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
683
|
+
if (amendment) {
|
|
684
|
+
const out = await constitutionStore.recordRatified(amendment.proposed, { itemId: closed.id });
|
|
685
|
+
appliedConstitution = out.applied;
|
|
686
|
+
constitutionNotApplied = out.failure;
|
|
687
|
+
if (out.failure) {
|
|
688
|
+
console.error(`[government] amendment ${closed.subject_id} ratified but NOT in force (re-apply with POST /government/constitution/reapply): ${JSON.stringify(out.failure.problems)}`);
|
|
679
689
|
}
|
|
680
|
-
}
|
|
681
|
-
console.error(`[government] amendment ${closed.
|
|
690
|
+
} else {
|
|
691
|
+
console.error(`[government] passed amendment item ${closed.id} names amendment ${closed.subject_id}, which does not exist — NOT applied`);
|
|
692
|
+
constitutionNotApplied = Object.freeze({ should: null, problems: [{ where: 'database', reason: 'the amendment row does not exist' }] });
|
|
682
693
|
}
|
|
683
694
|
}
|
|
684
695
|
|
|
@@ -713,6 +724,7 @@ async function closeItem(item, { cause = 'vote' } = {}) {
|
|
|
713
724
|
closed: true, item: closed, outcome, cause, author_karma: authorKarma,
|
|
714
725
|
...(credit ? { credit } : {}),
|
|
715
726
|
...(appliedConstitution ? { applied_constitution: appliedConstitution } : {}),
|
|
727
|
+
...(constitutionNotApplied ? { constitution_not_applied: constitutionNotApplied } : {}),
|
|
716
728
|
...(appliedDecisionRules ? { applied_decision_rules: appliedDecisionRules } : {}),
|
|
717
729
|
...(appliedLaws ? { applied_laws: appliedLaws } : {}),
|
|
718
730
|
...(acted ? { acted: acted.result } : {}),
|
|
@@ -366,16 +366,20 @@ function loadGovernmentConfig({ neutralPath = NEUTRAL_PATH, instancePath = INSTA
|
|
|
366
366
|
// the proposal was strictly validated at propose time, but a file on disk must
|
|
367
367
|
// never hold a shape the resolver would have to guess at.
|
|
368
368
|
//
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
//
|
|
369
|
+
// THE FILE IS NOT THE DURABLE RECORD (task 1005140). It used to be, on the
|
|
370
|
+
// reasoning that the instance pack is host-owned and untracked so a deploy pull
|
|
371
|
+
// never clobbers it. True on a long-lived checkout; false on every instance this
|
|
372
|
+
// platform provisions, whose container filesystem is wiped on each deploy,
|
|
373
|
+
// restart and idle wake — toast's ratified Timocratic Republic vanished that way.
|
|
374
|
+
// The durable record is now the government_constitution ROW, written first by
|
|
375
|
+
// constitution.js recordRatified; this write is the running process's copy of
|
|
376
|
+
// it, and constitution.js reconcile restores it at boot. Resolution is still
|
|
377
|
+
// ONE path (env → instance → neutral): only where the instance layer comes from
|
|
378
|
+
// changed.
|
|
375
379
|
//
|
|
376
|
-
// The caller (board.js closeItem) runs this
|
|
377
|
-
//
|
|
378
|
-
//
|
|
380
|
+
// The caller (constitution.recordRatified, from board.js closeItem) runs this
|
|
381
|
+
// AFTER the close commits and re-reads what is in force afterwards, so an
|
|
382
|
+
// amendment that did not take is reported at the close itself.
|
|
379
383
|
function applyBoardAmendment(proposed, { instancePath = INSTANCE_PATH } = {}) {
|
|
380
384
|
const instance = readJson(instancePath) || {};
|
|
381
385
|
const next = { ...instance, board: { ...sanitizeBoard(proposed) } };
|
|
@@ -392,8 +396,16 @@ function applyBoardAmendment(proposed, { instancePath = INSTANCE_PATH } = {}) {
|
|
|
392
396
|
return next.board;
|
|
393
397
|
}
|
|
394
398
|
|
|
399
|
+
// The instance pack as it is on disk (null when absent; a file that does not
|
|
400
|
+
// parse throws, as the loader does). constitution.js reads the board block
|
|
401
|
+
// through this so absent and malformed keep one meaning.
|
|
402
|
+
function readInstancePack(instancePath = INSTANCE_PATH) {
|
|
403
|
+
return readJson(instancePath);
|
|
404
|
+
}
|
|
405
|
+
|
|
395
406
|
module.exports = {
|
|
396
407
|
resolveGovernmentConfig,
|
|
408
|
+
readInstancePack,
|
|
397
409
|
loadGovernmentConfig,
|
|
398
410
|
grantableOnly,
|
|
399
411
|
sanitizeBoard,
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/government/constitution.js — the constitution in force survives a
|
|
4
|
+
// restart (task 1005140).
|
|
5
|
+
//
|
|
6
|
+
// THE BUG THIS EXISTS FOR. A ratified amendment was written only to
|
|
7
|
+
// <instance root>/config/government.json. Every instance this platform
|
|
8
|
+
// provisions runs on a container whose filesystem is wiped on each deploy,
|
|
9
|
+
// restart and idle wake, so the write took effect and then vanished at the next
|
|
10
|
+
// container start: toast's board ratified a Timocratic Republic on 2026-11-06
|
|
11
|
+
// and the constitution went on answering the day-one monarchy. ADR 0268 fixed
|
|
12
|
+
// the PATH of that write; this fixes its MEDIUM.
|
|
13
|
+
//
|
|
14
|
+
// THE SHAPE. The database row (government_037) is the durable record of the
|
|
15
|
+
// instance's board block. The file stays as the RUNNING PROCESS's copy of that
|
|
16
|
+
// row, so `config.loadGovernmentConfig()` keeps its one synchronous path
|
|
17
|
+
// (env → instance → neutral) and none of its callers change:
|
|
18
|
+
//
|
|
19
|
+
// • a passed amendment writes the ROW, then the file (`recordRatified`), then
|
|
20
|
+
// re-reads both and compares (`verifyInForce`) — an amendment that did not
|
|
21
|
+
// take is reported at the close, not weeks later by the divergence banner;
|
|
22
|
+
// • at boot, and on a slow interval after, `reconcile` brings the file in step
|
|
23
|
+
// with the row (a fresh container has no file; the row restores it). If the
|
|
24
|
+
// row does not exist yet but a file does, the file's board block is ADOPTED
|
|
25
|
+
// into the row and the log says so — nobody loses a constitution that
|
|
26
|
+
// happens to still be on disk. If there is NEITHER a row NOR a file but a
|
|
27
|
+
// constitutional amendment has passed, that amendment is put back in force:
|
|
28
|
+
// an instance whose ratified constitution a restart already erased (toast,
|
|
29
|
+
// 2026-11-06) repairs itself on its first boot with this code. That is not
|
|
30
|
+
// the auto-heal ADR 0268 refused — with no file there is no hand edit to
|
|
31
|
+
// revert; the only thing the process could answer is a default nobody chose;
|
|
32
|
+
// • `reapplyNewestPassed` is the operator's supported path: it writes the
|
|
33
|
+
// newest PASSED constitutional amendment back into force. It never takes a
|
|
34
|
+
// free-form block — changing the constitution is a board act (owner
|
|
35
|
+
// decision 11); this only makes a decision the board already took stick.
|
|
36
|
+
//
|
|
37
|
+
// The ROW WINS over the file. A hand edit of the file on a long-lived checkout
|
|
38
|
+
// is therefore overwritten at the next reconcile once a row exists — on a
|
|
39
|
+
// container that edit was never durable anyway, and a constitution that quietly
|
|
40
|
+
// differed between the database and one process is the failure being fixed.
|
|
41
|
+
|
|
42
|
+
const api = require('../../src/module-api');
|
|
43
|
+
const governmentConfig = require('./config');
|
|
44
|
+
const db = require('./db');
|
|
45
|
+
|
|
46
|
+
const logger = api.logger('government');
|
|
47
|
+
|
|
48
|
+
const BOARD_KEYS = Object.freeze(['membership', 'pass_rule', 'window_minutes', 'close_early_on_full_turnout']);
|
|
49
|
+
|
|
50
|
+
// Field-by-field over the four keys — the same comparison board.js
|
|
51
|
+
// constitutionDivergence makes, so "in step" here and "no divergence" there
|
|
52
|
+
// mean the same thing. The two answer different questions (is this process in
|
|
53
|
+
// step with the row / is the newest passed amendment in force); if the board
|
|
54
|
+
// block ever gains a key, change both.
|
|
55
|
+
function differingFields(should, actual) {
|
|
56
|
+
const a = actual || {};
|
|
57
|
+
return BOARD_KEYS.filter((k) => should[k] !== a[k]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// The file's own board block, or null when there is none. Read through the
|
|
61
|
+
// config loader's reader so absent and malformed keep their meanings (absent is
|
|
62
|
+
// null; a file that does not parse throws — a human must fix it).
|
|
63
|
+
function fileBoard(instancePath) {
|
|
64
|
+
const pack = governmentConfig.readInstancePack(instancePath);
|
|
65
|
+
return pack && pack.board && typeof pack.board === 'object' ? pack.board : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// A missing table means this instance has not run government_037 yet: the
|
|
69
|
+
// caller reports it rather than pretending the row is empty.
|
|
70
|
+
const isMissingTable = (err) => !!(err && err.code === '42P01');
|
|
71
|
+
|
|
72
|
+
// The newest PASSED constitution amendment (not a rules or laws one, which
|
|
73
|
+
// change their own tables), or null. listAmendmentHistory is newest first.
|
|
74
|
+
async function newestPassed(historyLimit = 50) {
|
|
75
|
+
const rows = await db.listAmendmentHistory(historyLimit);
|
|
76
|
+
return (rows || []).find((r) => r.outcome === 'passed' && r.proposed && r.kind === 'constitution') || null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// A failure the caller hands to a voter carries a fixed reason word, never a
|
|
80
|
+
// driver message (that is logged here, for the operator).
|
|
81
|
+
function problem(where, reason, err) {
|
|
82
|
+
if (err) logger.error(`[government] constitution: ${where} ${reason}: ${err.message || err}`);
|
|
83
|
+
return { where, reason };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Bring this process's file in step with the row, or adopt the file into an
|
|
87
|
+
// empty row. Never throws; returns what it did.
|
|
88
|
+
async function reconcile({ instancePath = governmentConfig.INSTANCE_PATH, log = console.log, error = console.error } = {}) {
|
|
89
|
+
let row;
|
|
90
|
+
try {
|
|
91
|
+
row = await db.getConstitutionRow();
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if (isMissingTable(err)) return { action: 'no_table' };
|
|
94
|
+
error(`[government] constitution: could not read the constitution row (the file stays as it is): ${err && err.message}`);
|
|
95
|
+
return { action: 'read_failed' };
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
if (row && row.board) {
|
|
99
|
+
const should = governmentConfig.sanitizeBoard(row.board);
|
|
100
|
+
const current = fileBoard(instancePath);
|
|
101
|
+
if (current && differingFields(should, governmentConfig.sanitizeBoard(current)).length === 0) {
|
|
102
|
+
return { action: 'in_step', board: should };
|
|
103
|
+
}
|
|
104
|
+
governmentConfig.applyBoardAmendment(should, { instancePath });
|
|
105
|
+
log(`[government] constitution: restored the constitution in force from the database (${should.pass_rule}, ${should.membership})`);
|
|
106
|
+
return { action: 'restored', board: should };
|
|
107
|
+
}
|
|
108
|
+
const current = fileBoard(instancePath);
|
|
109
|
+
if (!current) {
|
|
110
|
+
// Neither a row nor a file: if the board passed a constitution, the only
|
|
111
|
+
// reason it is not in force is that a restart erased it. Put it back.
|
|
112
|
+
const last = await newestPassed();
|
|
113
|
+
if (!last) return { action: 'none' };
|
|
114
|
+
const out = await recordRatified(last.proposed, { itemId: last.item_id, source: 'reapply', instancePath });
|
|
115
|
+
if (out.failure) {
|
|
116
|
+
error(`[government] constitution: could not put back passed amendment ${last.amendment_id}: ${JSON.stringify(out.failure.problems)}`);
|
|
117
|
+
return { action: 'failed', failure: out.failure };
|
|
118
|
+
}
|
|
119
|
+
log(`[government] constitution: no constitution survived the restart — put back passed amendment ${last.amendment_id} (item ${last.item_id}: ${out.applied.pass_rule}, ${out.applied.membership})`);
|
|
120
|
+
return { action: 'recovered', board: out.applied, item_id: String(last.item_id) };
|
|
121
|
+
}
|
|
122
|
+
const adopted = governmentConfig.sanitizeBoard(current);
|
|
123
|
+
await db.saveConstitutionRow({ board: adopted, source: 'adopted_file' });
|
|
124
|
+
log(`[government] constitution: adopted the board block from ${instancePath} into the database (${adopted.pass_rule}, ${adopted.membership}) — it now survives a restart`);
|
|
125
|
+
return { action: 'adopted', board: adopted };
|
|
126
|
+
} catch (err) {
|
|
127
|
+
error(`[government] constitution: reconcile failed: ${err && err.message}`);
|
|
128
|
+
return { action: 'failed', error: err && err.message };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Re-read the row and the constitution this process resolves, and compare both
|
|
133
|
+
// to what should be in force. Null when both match; otherwise what is wrong.
|
|
134
|
+
//
|
|
135
|
+
// The row is read back on purpose even though the save RETURNed it: the point
|
|
136
|
+
// is to prove what a fresh read sees, not what the driver said it wrote. Two
|
|
137
|
+
// small reads, only when a constitution sitting closes.
|
|
138
|
+
async function verifyInForce(should, { instancePath } = {}) {
|
|
139
|
+
const problems = [];
|
|
140
|
+
let row = null;
|
|
141
|
+
try { row = await db.getConstitutionRow(); }
|
|
142
|
+
catch (err) { problems.push(problem('database', 'read_back_failed', err)); }
|
|
143
|
+
if (!problems.length) {
|
|
144
|
+
const rowFields = row && row.board ? differingFields(should, governmentConfig.sanitizeBoard(row.board)) : BOARD_KEYS.slice();
|
|
145
|
+
if (rowFields.length) problems.push({ where: 'database', fields: rowFields });
|
|
146
|
+
}
|
|
147
|
+
let live = null;
|
|
148
|
+
try { live = governmentConfig.loadGovernmentConfig(instancePath ? { instancePath } : {}).board; }
|
|
149
|
+
catch (err) { problems.push(problem('in_force', 'resolve_failed', err)); }
|
|
150
|
+
if (live) {
|
|
151
|
+
const liveFields = differingFields(should, live);
|
|
152
|
+
if (liveFields.length) problems.push({ where: 'in_force', fields: liveFields });
|
|
153
|
+
}
|
|
154
|
+
return problems.length ? Object.freeze({ should, problems }) : null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Put a ratified board block into force: the ROW first (the durable record),
|
|
158
|
+
// then the file (this process), then verify. Never throws — the board's
|
|
159
|
+
// decision is already committed; the caller reports what this returns.
|
|
160
|
+
async function recordRatified(proposed, { itemId = null, source = 'amendment', updatedBy = null, instancePath } = {}) {
|
|
161
|
+
const should = governmentConfig.sanitizeBoard(proposed);
|
|
162
|
+
try {
|
|
163
|
+
await db.saveConstitutionRow({ board: should, source, itemId, updatedBy });
|
|
164
|
+
} catch (err) {
|
|
165
|
+
return { applied: null, failure: Object.freeze({ should, problems: [problem('database', 'save_failed', err)] }) };
|
|
166
|
+
}
|
|
167
|
+
let applied = null;
|
|
168
|
+
try {
|
|
169
|
+
// By property lookup at call time, so a test can redirect the file write.
|
|
170
|
+
applied = instancePath
|
|
171
|
+
? governmentConfig.applyBoardAmendment(should, { instancePath })
|
|
172
|
+
: governmentConfig.applyBoardAmendment(should);
|
|
173
|
+
} catch (err) {
|
|
174
|
+
return { applied: null, failure: Object.freeze({ should, problems: [problem('in_force', 'write_failed', err)] }) };
|
|
175
|
+
}
|
|
176
|
+
return { applied, failure: await verifyInForce(should, { instancePath }) };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// The operator's path: re-apply the newest PASSED constitutional amendment.
|
|
180
|
+
// Refuses when there is none — it never invents a constitution.
|
|
181
|
+
async function reapplyNewestPassed({ updatedBy = null, historyLimit = 50 } = {}) {
|
|
182
|
+
const last = await newestPassed(historyLimit);
|
|
183
|
+
if (!last) return { ok: false, reason: 'no_passed_amendment' };
|
|
184
|
+
const out = await recordRatified(last.proposed, { itemId: last.item_id, source: 'reapply', updatedBy });
|
|
185
|
+
return {
|
|
186
|
+
ok: !out.failure,
|
|
187
|
+
item_id: String(last.item_id),
|
|
188
|
+
amendment_id: String(last.amendment_id),
|
|
189
|
+
applied: out.applied,
|
|
190
|
+
...(out.failure ? { reason: 'not_applied', failure: out.failure } : {}),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// The handler behind POST /government/constitution/reapply. The route — its
|
|
195
|
+
// path, its government.manage gate and its pin — is declared in
|
|
196
|
+
// routes/government.js, where route-rank-check reads it; only the body lives
|
|
197
|
+
// here (the access-routes.js pattern, for the router's size ratchet). It takes
|
|
198
|
+
// NO block from the request: it can only re-apply what a sitting passed, never
|
|
199
|
+
// invent one (changing the constitution is a board act, owner decision 11).
|
|
200
|
+
function reapplyRoute(log) {
|
|
201
|
+
return async (req, res) => {
|
|
202
|
+
try {
|
|
203
|
+
const out = await module.exports.reapplyNewestPassed({ updatedBy: req.builder.id });
|
|
204
|
+
if (!out.ok && out.reason === 'no_passed_amendment') {
|
|
205
|
+
return res.fail('no_passed_amendment', 404, { message: 'No change to the constitution has passed yet, so there is nothing to re-apply. Propose one in the Board Room; once it passes it takes effect.' });
|
|
206
|
+
}
|
|
207
|
+
if (!out.ok) return res.fail('constitution_not_applied', 500, { item_id: out.item_id, failure: out.failure });
|
|
208
|
+
res.json({ ok: true, item_id: out.item_id, amendment_id: out.amendment_id, board: out.applied });
|
|
209
|
+
} catch (err) {
|
|
210
|
+
log.error('[gds] POST /government/constitution/reapply', err);
|
|
211
|
+
res.fail('constitution_reapply_failed', { status: 500, message: 'internal error' });
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
module.exports = {
|
|
217
|
+
reapplyRoute,
|
|
218
|
+
reconcile,
|
|
219
|
+
recordRatified,
|
|
220
|
+
verifyInForce,
|
|
221
|
+
reapplyNewestPassed,
|
|
222
|
+
differingFields,
|
|
223
|
+
BOARD_KEYS,
|
|
224
|
+
};
|