@bongos/core 1.19.614 → 1.19.615
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 +31 -21
- package/docs/adr/0267-unanimity-and-the-revise-and-re-sit-loop.md +124 -0
- package/docs/adr/README.md +1 -0
- package/docs/copy-inventory.md +36 -34
- package/docs/copy-registry.json +53 -35
- package/docs/module-api-changelog.md +2 -0
- package/modules/government/board.js +83 -12
- package/modules/government/config.js +49 -8
- package/modules/hall-ui/public/government.js +30 -5
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +1 -1
- package/tests/government_board_amendment.mjs +4 -4
- package/tests/government_board_close.mjs +1 -1
- package/tests/government_config.mjs +5 -4
- package/tests/government_unanimous.mjs +319 -0
package/docs/copy-registry.json
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
"generator": "scripts/gds/copy-inventory.js",
|
|
4
4
|
"goal": 1000074,
|
|
5
5
|
"counts": {
|
|
6
|
-
"strings":
|
|
7
|
-
"unique_ids":
|
|
8
|
-
"certain":
|
|
6
|
+
"strings": 1731,
|
|
7
|
+
"unique_ids": 1499,
|
|
8
|
+
"certain": 1731,
|
|
9
9
|
"heuristic": 0,
|
|
10
10
|
"duplicate_ids": 163
|
|
11
11
|
},
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"builders-hall": {
|
|
14
14
|
"at": "modules/hall-ui/public",
|
|
15
15
|
"files": 78,
|
|
16
|
-
"strings":
|
|
16
|
+
"strings": 1258
|
|
17
17
|
},
|
|
18
18
|
"status-dashboard": {
|
|
19
19
|
"at": "modules/status-ui/public",
|
|
@@ -241,7 +241,7 @@
|
|
|
241
241
|
"occurrences": 4,
|
|
242
242
|
"at": [
|
|
243
243
|
"modules/hall-ui/public/gate.js:219",
|
|
244
|
-
"modules/hall-ui/public/government.js:
|
|
244
|
+
"modules/hall-ui/public/government.js:1129",
|
|
245
245
|
"modules/hall-ui/public/harbor.js:337",
|
|
246
246
|
"modules/hall-ui/public/watch.js:1241"
|
|
247
247
|
]
|
|
@@ -301,7 +301,7 @@
|
|
|
301
301
|
"occurrences": 3,
|
|
302
302
|
"at": [
|
|
303
303
|
"modules/hall-ui/public/gate.js:222",
|
|
304
|
-
"modules/hall-ui/public/government.js:
|
|
304
|
+
"modules/hall-ui/public/government.js:1132",
|
|
305
305
|
"modules/hall-ui/public/harbor.js:340"
|
|
306
306
|
]
|
|
307
307
|
},
|
|
@@ -1915,7 +1915,7 @@
|
|
|
1915
1915
|
"surface": "builders-hall",
|
|
1916
1916
|
"text": ", // membership",
|
|
1917
1917
|
"file": "modules/hall-ui/public/government.js",
|
|
1918
|
-
"line":
|
|
1918
|
+
"line": 899,
|
|
1919
1919
|
"origin": "js-markup",
|
|
1920
1920
|
"confidence": "certain"
|
|
1921
1921
|
},
|
|
@@ -1946,6 +1946,15 @@
|
|
|
1946
1946
|
"origin": "js-markup",
|
|
1947
1947
|
"confidence": "certain"
|
|
1948
1948
|
},
|
|
1949
|
+
{
|
|
1950
|
+
"id": "574acf2f93bb",
|
|
1951
|
+
"surface": "builders-hall",
|
|
1952
|
+
"text": ", never quietly ratified — so a clock is worth proposing. The recommended sitting is",
|
|
1953
|
+
"file": "modules/hall-ui/public/government.js",
|
|
1954
|
+
"line": 619,
|
|
1955
|
+
"origin": "js-markup",
|
|
1956
|
+
"confidence": "certain"
|
|
1957
|
+
},
|
|
1949
1958
|
{
|
|
1950
1959
|
"id": "741e355954ac",
|
|
1951
1960
|
"surface": "builders-hall",
|
|
@@ -2491,7 +2500,7 @@
|
|
|
2491
2500
|
"surface": "builders-hall",
|
|
2492
2501
|
"text": "As configuration:",
|
|
2493
2502
|
"file": "modules/hall-ui/public/government.js",
|
|
2494
|
-
"line":
|
|
2503
|
+
"line": 771,
|
|
2495
2504
|
"origin": "js-markup",
|
|
2496
2505
|
"confidence": "certain"
|
|
2497
2506
|
},
|
|
@@ -2563,7 +2572,7 @@
|
|
|
2563
2572
|
"surface": "builders-hall",
|
|
2564
2573
|
"text": "Available from the {…} rank up.",
|
|
2565
2574
|
"file": "modules/hall-ui/public/government.js",
|
|
2566
|
-
"line":
|
|
2575
|
+
"line": 1132,
|
|
2567
2576
|
"origin": "js-text-assign",
|
|
2568
2577
|
"confidence": "certain"
|
|
2569
2578
|
},
|
|
@@ -2860,7 +2869,7 @@
|
|
|
2860
2869
|
"surface": "builders-hall",
|
|
2861
2870
|
"text": "Cast objection",
|
|
2862
2871
|
"file": "modules/hall-ui/public/government.js",
|
|
2863
|
-
"line":
|
|
2872
|
+
"line": 987,
|
|
2864
2873
|
"origin": "js-markup",
|
|
2865
2874
|
"confidence": "certain"
|
|
2866
2875
|
},
|
|
@@ -2941,7 +2950,7 @@
|
|
|
2941
2950
|
"surface": "builders-hall",
|
|
2942
2951
|
"text": "Charter library",
|
|
2943
2952
|
"file": "modules/hall-ui/public/government.js",
|
|
2944
|
-
"line":
|
|
2953
|
+
"line": 741,
|
|
2945
2954
|
"origin": "js-markup",
|
|
2946
2955
|
"confidence": "certain"
|
|
2947
2956
|
},
|
|
@@ -4111,7 +4120,7 @@
|
|
|
4111
4120
|
"surface": "builders-hall",
|
|
4112
4121
|
"text": "Could not vote: {…}",
|
|
4113
4122
|
"file": "modules/hall-ui/public/government.js",
|
|
4114
|
-
"line":
|
|
4123
|
+
"line": 1015,
|
|
4115
4124
|
"origin": "js-notice",
|
|
4116
4125
|
"confidence": "certain"
|
|
4117
4126
|
},
|
|
@@ -4615,7 +4624,7 @@
|
|
|
4615
4624
|
"surface": "builders-hall",
|
|
4616
4625
|
"text": "Every closed amendment sitting, ratified and returned alike, with the rule it was decided under.",
|
|
4617
4626
|
"file": "modules/hall-ui/public/government.js",
|
|
4618
|
-
"line":
|
|
4627
|
+
"line": 780,
|
|
4619
4628
|
"origin": "js-markup",
|
|
4620
4629
|
"confidence": "certain"
|
|
4621
4630
|
},
|
|
@@ -5254,7 +5263,7 @@
|
|
|
5254
5263
|
"surface": "builders-hall",
|
|
5255
5264
|
"text": "How it has changed",
|
|
5256
5265
|
"file": "modules/hall-ui/public/government.js",
|
|
5257
|
-
"line":
|
|
5266
|
+
"line": 780,
|
|
5258
5267
|
"origin": "js-markup",
|
|
5259
5268
|
"confidence": "certain"
|
|
5260
5269
|
},
|
|
@@ -5326,7 +5335,7 @@
|
|
|
5326
5335
|
"surface": "builders-hall",
|
|
5327
5336
|
"text": "How this project is governed.",
|
|
5328
5337
|
"file": "modules/hall-ui/public/government.js",
|
|
5329
|
-
"line":
|
|
5338
|
+
"line": 1167,
|
|
5330
5339
|
"origin": "js-text-assign",
|
|
5331
5340
|
"confidence": "certain"
|
|
5332
5341
|
},
|
|
@@ -5587,7 +5596,7 @@
|
|
|
5587
5596
|
"surface": "builders-hall",
|
|
5588
5597
|
"text": "It cleared the completeness grade at {…}/100.",
|
|
5589
5598
|
"file": "modules/hall-ui/public/government.js",
|
|
5590
|
-
"line":
|
|
5599
|
+
"line": 957,
|
|
5591
5600
|
"origin": "js-markup",
|
|
5592
5601
|
"confidence": "certain"
|
|
5593
5602
|
},
|
|
@@ -7252,7 +7261,7 @@
|
|
|
7252
7261
|
"surface": "builders-hall",
|
|
7253
7262
|
"text": "Permissions is the Archon administration surface. The Constitution and the Board Room are yours to read.",
|
|
7254
7263
|
"file": "modules/hall-ui/public/government.js",
|
|
7255
|
-
"line":
|
|
7264
|
+
"line": 1146,
|
|
7256
7265
|
"origin": "js-markup",
|
|
7257
7266
|
"confidence": "certain"
|
|
7258
7267
|
},
|
|
@@ -8062,7 +8071,7 @@
|
|
|
8062
8071
|
"surface": "builders-hall",
|
|
8063
8072
|
"text": "Restricted.",
|
|
8064
8073
|
"file": "modules/hall-ui/public/government.js",
|
|
8065
|
-
"line":
|
|
8074
|
+
"line": 1129,
|
|
8066
8075
|
"origin": "js-text-assign",
|
|
8067
8076
|
"confidence": "certain"
|
|
8068
8077
|
},
|
|
@@ -8359,7 +8368,7 @@
|
|
|
8359
8368
|
"surface": "builders-hall",
|
|
8360
8369
|
"text": "Selectable today",
|
|
8361
8370
|
"file": "modules/hall-ui/public/government.js",
|
|
8362
|
-
"line":
|
|
8371
|
+
"line": 741,
|
|
8363
8372
|
"origin": "js-markup",
|
|
8364
8373
|
"confidence": "certain"
|
|
8365
8374
|
},
|
|
@@ -9133,7 +9142,7 @@
|
|
|
9133
9142
|
"surface": "builders-hall",
|
|
9134
9143
|
"text": "The developmental ladder: {…}. Formality is adopted only where informal coordination breaks.",
|
|
9135
9144
|
"file": "modules/hall-ui/public/government.js",
|
|
9136
|
-
"line":
|
|
9145
|
+
"line": 748,
|
|
9137
9146
|
"origin": "js-markup",
|
|
9138
9147
|
"confidence": "certain"
|
|
9139
9148
|
},
|
|
@@ -9412,7 +9421,7 @@
|
|
|
9412
9421
|
"surface": "builders-hall",
|
|
9413
9422
|
"text": "The seven government structures, for getting a team up to speed before deciding when to flip a switch.",
|
|
9414
9423
|
"file": "modules/hall-ui/public/government.js",
|
|
9415
|
-
"line":
|
|
9424
|
+
"line": 741,
|
|
9416
9425
|
"origin": "js-markup",
|
|
9417
9426
|
"confidence": "certain"
|
|
9418
9427
|
},
|
|
@@ -9763,7 +9772,7 @@
|
|
|
9763
9772
|
"surface": "builders-hall",
|
|
9764
9773
|
"text": "This sends the idea back to its author. Say why, and which section.",
|
|
9765
9774
|
"file": "modules/hall-ui/public/government.js",
|
|
9766
|
-
"line":
|
|
9775
|
+
"line": 987,
|
|
9767
9776
|
"origin": "js-markup",
|
|
9768
9777
|
"confidence": "certain"
|
|
9769
9778
|
},
|
|
@@ -9952,7 +9961,7 @@
|
|
|
9952
9961
|
"surface": "builders-hall",
|
|
9953
9962
|
"text": "Vote yes",
|
|
9954
9963
|
"file": "modules/hall-ui/public/government.js",
|
|
9955
|
-
"line":
|
|
9964
|
+
"line": 987,
|
|
9956
9965
|
"origin": "js-markup",
|
|
9957
9966
|
"confidence": "certain"
|
|
9958
9967
|
},
|
|
@@ -10294,7 +10303,7 @@
|
|
|
10294
10303
|
"surface": "builders-hall",
|
|
10295
10304
|
"text": "Why — the reason the author will act on",
|
|
10296
10305
|
"file": "modules/hall-ui/public/government.js",
|
|
10297
|
-
"line":
|
|
10306
|
+
"line": 987,
|
|
10298
10307
|
"origin": "js-markup",
|
|
10299
10308
|
"confidence": "certain"
|
|
10300
10309
|
},
|
|
@@ -10492,7 +10501,7 @@
|
|
|
10492
10501
|
"surface": "builders-hall",
|
|
10493
10502
|
"text": "You {…}. One vote per member.",
|
|
10494
10503
|
"file": "modules/hall-ui/public/government.js",
|
|
10495
|
-
"line":
|
|
10504
|
+
"line": 1059,
|
|
10496
10505
|
"origin": "js-markup",
|
|
10497
10506
|
"confidence": "certain"
|
|
10498
10507
|
},
|
|
@@ -10654,7 +10663,7 @@
|
|
|
10654
10663
|
"surface": "builders-hall",
|
|
10655
10664
|
"text": "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 karma.",
|
|
10656
10665
|
"file": "modules/hall-ui/public/government.js",
|
|
10657
|
-
"line":
|
|
10666
|
+
"line": 985,
|
|
10658
10667
|
"origin": "js-markup",
|
|
10659
10668
|
"confidence": "certain"
|
|
10660
10669
|
},
|
|
@@ -10843,7 +10852,7 @@
|
|
|
10843
10852
|
"surface": "builders-hall",
|
|
10844
10853
|
"text": "active builder{…}: {…}",
|
|
10845
10854
|
"file": "modules/hall-ui/public/government.js",
|
|
10846
|
-
"line":
|
|
10855
|
+
"line": 741,
|
|
10847
10856
|
"origin": "js-markup",
|
|
10848
10857
|
"confidence": "certain"
|
|
10849
10858
|
},
|
|
@@ -10915,7 +10924,7 @@
|
|
|
10915
10924
|
"surface": "builders-hall",
|
|
10916
10925
|
"text": "as configuration: {…}",
|
|
10917
10926
|
"file": "modules/hall-ui/public/government.js",
|
|
10918
|
-
"line":
|
|
10927
|
+
"line": 727,
|
|
10919
10928
|
"origin": "js-markup",
|
|
10920
10929
|
"confidence": "certain"
|
|
10921
10930
|
},
|
|
@@ -11455,7 +11464,7 @@
|
|
|
11455
11464
|
"surface": "builders-hall",
|
|
11456
11465
|
"text": "means expressible through the real configuration — a rank-based membership plus a pass rule — with no new code. This project has",
|
|
11457
11466
|
"file": "modules/hall-ui/public/government.js",
|
|
11458
|
-
"line":
|
|
11467
|
+
"line": 741,
|
|
11459
11468
|
"origin": "js-markup",
|
|
11460
11469
|
"confidence": "certain"
|
|
11461
11470
|
},
|
|
@@ -11527,7 +11536,7 @@
|
|
|
11527
11536
|
"surface": "builders-hall",
|
|
11528
11537
|
"text": "no clock",
|
|
11529
11538
|
"file": "modules/hall-ui/public/government.js",
|
|
11530
|
-
"line":
|
|
11539
|
+
"line": 621,
|
|
11531
11540
|
"origin": "js-markup",
|
|
11532
11541
|
"confidence": "certain"
|
|
11533
11542
|
},
|
|
@@ -11635,7 +11644,7 @@
|
|
|
11635
11644
|
"surface": "builders-hall",
|
|
11636
11645
|
"text": "objected to",
|
|
11637
11646
|
"file": "modules/hall-ui/public/government.js",
|
|
11638
|
-
"line":
|
|
11647
|
+
"line": 847,
|
|
11639
11648
|
"origin": "js-markup",
|
|
11640
11649
|
"confidence": "certain"
|
|
11641
11650
|
},
|
|
@@ -12080,6 +12089,15 @@
|
|
|
12080
12089
|
"origin": "js-markup",
|
|
12081
12090
|
"confidence": "certain"
|
|
12082
12091
|
},
|
|
12092
|
+
{
|
|
12093
|
+
"id": "edd5e9f56c25",
|
|
12094
|
+
"surface": "builders-hall",
|
|
12095
|
+
"text": "unanimous agreement",
|
|
12096
|
+
"file": "modules/hall-ui/public/government.js",
|
|
12097
|
+
"line": 601,
|
|
12098
|
+
"origin": "js-markup",
|
|
12099
|
+
"confidence": "certain"
|
|
12100
|
+
},
|
|
12083
12101
|
{
|
|
12084
12102
|
"id": "f98966dc7cac",
|
|
12085
12103
|
"surface": "builders-hall",
|
|
@@ -12202,7 +12220,7 @@
|
|
|
12202
12220
|
"surface": "builders-hall",
|
|
12203
12221
|
"text": "your vote",
|
|
12204
12222
|
"file": "modules/hall-ui/public/government.js",
|
|
12205
|
-
"line":
|
|
12223
|
+
"line": 842,
|
|
12206
12224
|
"origin": "js-markup",
|
|
12207
12225
|
"confidence": "certain"
|
|
12208
12226
|
},
|
|
@@ -12454,7 +12472,7 @@
|
|
|
12454
12472
|
"surface": "builders-hall",
|
|
12455
12473
|
"text": "{…} proposed",
|
|
12456
12474
|
"file": "modules/hall-ui/public/government.js",
|
|
12457
|
-
"line":
|
|
12475
|
+
"line": 645,
|
|
12458
12476
|
"origin": "js-markup",
|
|
12459
12477
|
"confidence": "certain"
|
|
12460
12478
|
},
|
|
@@ -12634,7 +12652,7 @@
|
|
|
12634
12652
|
"surface": "builders-hall",
|
|
12635
12653
|
"text": "{…} · who votes, and how it {…}",
|
|
12636
12654
|
"file": "modules/hall-ui/public/government.js",
|
|
12637
|
-
"line":
|
|
12655
|
+
"line": 1068,
|
|
12638
12656
|
"origin": "js-markup",
|
|
12639
12657
|
"confidence": "certain"
|
|
12640
12658
|
},
|
|
@@ -12706,7 +12724,7 @@
|
|
|
12706
12724
|
"surface": "builders-hall",
|
|
12707
12725
|
"text": "{…}s only",
|
|
12708
12726
|
"file": "modules/hall-ui/public/government.js",
|
|
12709
|
-
"line":
|
|
12727
|
+
"line": 1146,
|
|
12710
12728
|
"origin": "js-markup",
|
|
12711
12729
|
"confidence": "certain"
|
|
12712
12730
|
},
|
|
@@ -1677,5 +1677,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1677
1677
|
landed since 1.19.612 with no explicit bump. run 34311691842. (task 1002620)
|
|
1678
1678
|
1.19.614 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1679
1679
|
landed since 1.19.613 with no explicit bump. run 34314327601. (task 1002620)
|
|
1680
|
+
1.19.615 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1681
|
+
landed since 1.19.614 with no explicit bump. run 34314595235. (task 1002620)
|
|
1680
1682
|
---------------------------------------------------------------------------
|
|
1681
1683
|
```
|
|
@@ -47,7 +47,10 @@ 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
|
-
const {
|
|
50
|
+
const {
|
|
51
|
+
loadGovernmentConfig, PASS_RULES, MAX_WINDOW_MINUTES,
|
|
52
|
+
RECOMMENDED_WINDOW_MINUTES, CLOCK_SAFE_PASS_RULES, isClockSafe,
|
|
53
|
+
} = governmentConfig;
|
|
51
54
|
const {
|
|
52
55
|
isBoardMember, resolveBoardMembers, parseMembershipPredicate, wouldBeBoardMember,
|
|
53
56
|
} = require('./board-membership');
|
|
@@ -288,8 +291,11 @@ async function castVote({ itemId, voterId, direction, reasonMd = null, sectionKe
|
|
|
288
291
|
|
|
289
292
|
// ── close + tally (BV1.R10 of goal 1000069, ADR 0175 §3) ────────────────────
|
|
290
293
|
//
|
|
291
|
-
// EVERY RULE DECLARED, NONE EMERGENT.
|
|
292
|
-
// objection returns the idea; anything else —
|
|
294
|
+
// EVERY RULE DECLARED, NONE EMERGENT. Under the two ORIGINAL rules the tally is
|
|
295
|
+
// one line: a single reasoned objection returns the idea; anything else —
|
|
296
|
+
// including ZERO votes — passes. `majority` and `unanimous` (ADR 0191, ADR 0267)
|
|
297
|
+
// are the rules where absence stops being free, each in its own way; read
|
|
298
|
+
// tallyOutcome for the whole counting story.
|
|
293
299
|
// Absent members are NULLED by construction rather than by branch: the tally
|
|
294
300
|
// only ever reads the votes that were cast, so a member who never voted simply
|
|
295
301
|
// does not exist in it, and absence can never block (§2 — the sitting model).
|
|
@@ -301,7 +307,9 @@ async function castVote({ itemId, voterId, direction, reasonMd = null, sectionKe
|
|
|
301
307
|
// Archon ratifies or returns, done); consent closes early only on FULL
|
|
302
308
|
// TURNOUT (every current member has voted — recorded positions and the
|
|
303
309
|
// vote karma are worth the wait, and no later vote can change the outcome
|
|
304
|
-
// anyway since there is no vote-changing in v1);
|
|
310
|
+
// anyway since there is no vote-changing in v1); majority and unanimous
|
|
311
|
+
// close the moment the outcome is FIXED (carried, or unreachable, or —
|
|
312
|
+
// under unanimous — objected to), and otherwise on full turnout;
|
|
305
313
|
// • on expiry — R11's poller calls the same closeItem, so the clock and the
|
|
306
314
|
// turnout can never disagree about what a close means.
|
|
307
315
|
// An unknown pass_rule closes NOTHING after a vote (fail-closed — only the
|
|
@@ -390,6 +398,24 @@ function tallyOutcome(votes, { constitution = {}, members = null, authorId = nul
|
|
|
390
398
|
&& yes.some((v) => !isAuthorVote(v, authorId));
|
|
391
399
|
return carried ? 'passed' : 'returned';
|
|
392
400
|
}
|
|
401
|
+
if (constitution.pass_rule === 'unanimous') {
|
|
402
|
+
// TWO CONDITIONS, AND THE SECOND IS THE ONE PEOPLE FORGET. Unanimity is not
|
|
403
|
+
// "nobody objected" — an empty room objects to nothing. It is "everyone who
|
|
404
|
+
// spoke said yes, AND somebody said yes", so a sitting that nobody attended
|
|
405
|
+
// RETURNS rather than passing on silence. That is what makes the clock safe
|
|
406
|
+
// here (ADR 0191 §4's condition) and what separates this rule from consent.
|
|
407
|
+
//
|
|
408
|
+
// ABSENCE IS NOT COUNTED AT ALL — there is deliberately no membership
|
|
409
|
+
// denominator (owner decision 2026-09-08: silence must not block, which is
|
|
410
|
+
// why the clock exists). A member who never voted is neither a yes nor a no;
|
|
411
|
+
// they simply are not in the reckoning, exactly as under first_ratifier.
|
|
412
|
+
//
|
|
413
|
+
// The ADR 0191 author rule is unchanged and rides the same two helpers: an
|
|
414
|
+
// author's 'no' cannot return their own idea (decidingVotes drops it), and
|
|
415
|
+
// the affirmative voice that carries it must not be the author's own.
|
|
416
|
+
if (decidingVotes(list, authorId).some((v) => v.direction === 'no')) return 'returned';
|
|
417
|
+
return list.some((v) => v.direction === 'yes' && !isAuthorVote(v, authorId)) ? 'passed' : 'returned';
|
|
418
|
+
}
|
|
393
419
|
// first_ratifier / consent / anything unknown: one reasoned objection returns
|
|
394
420
|
// the idea, anything else — including ZERO votes — passes. The author's own
|
|
395
421
|
// vote is excluded so they cannot return their own idea.
|
|
@@ -418,6 +444,16 @@ function majorityDecided({ votes, members, authorId }) {
|
|
|
418
444
|
return !hasMajority(yes.length + notYetVoted, memberCount);
|
|
419
445
|
}
|
|
420
446
|
|
|
447
|
+
// Has every CURRENT member cast a vote? The full-turnout courtesy three of the
|
|
448
|
+
// four rules grant, in one place rather than three: by then every position is on
|
|
449
|
+
// the record and no later vote can change the outcome (there is no vote-changing
|
|
450
|
+
// in v1). An empty board has no turnout to complete, so it is never full.
|
|
451
|
+
function fullTurnout(votes, members) {
|
|
452
|
+
if (!members || !members.length) return false;
|
|
453
|
+
const voters = new Set((votes || []).map((v) => String(v.voter_id)));
|
|
454
|
+
return members.every((m) => voters.has(String(m.builder_id)));
|
|
455
|
+
}
|
|
456
|
+
|
|
421
457
|
// Should a just-cast vote close the item now, under the SNAPSHOTTED rule?
|
|
422
458
|
//
|
|
423
459
|
// EVERY RULE NEEDS A NON-AUTHOR VOTE FIRST (task 1003273): an author's cast is
|
|
@@ -431,18 +467,29 @@ function shouldCloseAfterVote({ constitution, votes, members, authorId = null }
|
|
|
431
467
|
if (!deciding.length) return false; // nobody but the author has spoken
|
|
432
468
|
if (majorityDecided({ votes, members, authorId })) return true;
|
|
433
469
|
// Full turnout closes it too, when the constitution says so — the same
|
|
434
|
-
// courtesy consent gets
|
|
470
|
+
// courtesy consent gets.
|
|
471
|
+
if (!(constitution || {}).close_early_on_full_turnout) return false;
|
|
472
|
+
return fullTurnout(votes, members);
|
|
473
|
+
}
|
|
474
|
+
if (rule === 'unanimous') {
|
|
475
|
+
if (!deciding.length) return false; // nobody but the author has spoken
|
|
476
|
+
// A REASONED OBJECTION ENDS IT IMMEDIATELY, and that is a kindness rather
|
|
477
|
+
// than impatience: there is no vote-changing in v1, so once a non-author
|
|
478
|
+
// 'no' is on the record the outcome is fixed at 'returned' whatever the
|
|
479
|
+
// absent members do. Holding the sitting open would only delay the feedback
|
|
480
|
+
// the author needs to revise and re-sit — which is the whole point of this
|
|
481
|
+
// rule (ADR 0267). Same reasoning as majorityDecided's unreachable branch.
|
|
482
|
+
if (deciding.some((v) => v.direction === 'no')) return true;
|
|
483
|
+
// A single yes does NOT end it. Unlike first_ratifier, a later member must
|
|
484
|
+
// still be able to object — the sitting runs its clock, and only full
|
|
485
|
+
// turnout (every position on the record) can close it early.
|
|
435
486
|
if (!(constitution || {}).close_early_on_full_turnout) return false;
|
|
436
|
-
|
|
437
|
-
const voters = new Set((votes || []).map((v) => String(v.voter_id)));
|
|
438
|
-
return members.every((m) => voters.has(String(m.builder_id)));
|
|
487
|
+
return fullTurnout(votes, members);
|
|
439
488
|
}
|
|
440
489
|
if (rule === 'consent') {
|
|
441
490
|
if (!(constitution || {}).close_early_on_full_turnout) return false;
|
|
442
|
-
if (!members || !members.length) return false; // an empty board has no turnout to complete
|
|
443
491
|
if (!deciding.length) return false;
|
|
444
|
-
|
|
445
|
-
return members.every((m) => voters.has(String(m.builder_id)));
|
|
492
|
+
return fullTurnout(votes, members);
|
|
446
493
|
}
|
|
447
494
|
return false; // unknown rule: only the clock closes it
|
|
448
495
|
}
|
|
@@ -510,6 +557,10 @@ async function closeItem(item, { cause = 'vote' } = {}) {
|
|
|
510
557
|
: null;
|
|
511
558
|
// Only `majority` needs the membership denominator; the absence-passes rules
|
|
512
559
|
// read nothing but the cast votes, so we do not spend the query on them.
|
|
560
|
+
// `unanimous` does not need it either — it has no denominator by design — and
|
|
561
|
+
// is listed nowhere here on purpose: the members list it DOES use is the
|
|
562
|
+
// full-turnout early close, which lives on the vote path (shouldCloseAfterVote
|
|
563
|
+
// resolves it there), never in the tally.
|
|
513
564
|
const members = constitution.pass_rule === 'majority'
|
|
514
565
|
? await resolveBoardMembers(constitution)
|
|
515
566
|
: null;
|
|
@@ -859,7 +910,27 @@ async function constitutionView({ historyLimit = 50 } = {}) {
|
|
|
859
910
|
outcome: r.outcome,
|
|
860
911
|
decided_under: r.decided_under || null,
|
|
861
912
|
}));
|
|
862
|
-
return {
|
|
913
|
+
return {
|
|
914
|
+
board: live,
|
|
915
|
+
history,
|
|
916
|
+
headcount,
|
|
917
|
+
recommended: recommendedCharter(headcount),
|
|
918
|
+
// THE CLOCK ADVICE THE AMENDMENT FORM NEEDS (task 1003733, ADR 0267 §3).
|
|
919
|
+
// A proposer choosing a pass rule has to choose a window in the same breath
|
|
920
|
+
// — a constitution is ratified whole, never by diff — and the two choices
|
|
921
|
+
// are not independent: a deadline is only safe under a rule whose expiry
|
|
922
|
+
// means RETURN (ADR 0191 §4). So the server states which rules that is and
|
|
923
|
+
// what to put in the box, rather than leaving the hall to hardcode a number
|
|
924
|
+
// and the proposer to guess whether a clock is even honest here.
|
|
925
|
+
// `window_minutes: null` stays a legal answer under a clock-safe rule; it
|
|
926
|
+
// means "wait indefinitely for a voice", which the hall says out loud.
|
|
927
|
+
clock: {
|
|
928
|
+
recommended_window_minutes: RECOMMENDED_WINDOW_MINUTES,
|
|
929
|
+
max_window_minutes: MAX_WINDOW_MINUTES,
|
|
930
|
+
clock_safe_pass_rules: CLOCK_SAFE_PASS_RULES,
|
|
931
|
+
live_rule_is_clock_safe: isClockSafe(live.pass_rule),
|
|
932
|
+
},
|
|
933
|
+
};
|
|
863
934
|
}
|
|
864
935
|
|
|
865
936
|
// ── R13's read: the feedback a returned idea carries back to its author ─────
|
|
@@ -155,6 +155,35 @@ const BOARD_DEFAULTS = Object.freeze({
|
|
|
155
155
|
close_early_on_full_turnout: true,
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
+
// THE CLOCK, AS A NUMBER SOMEBODY CAN ADOPT (task 1003733, ADR 0267 §3).
|
|
159
|
+
//
|
|
160
|
+
// The note above says why BOARD_DEFAULTS ships CLOCKLESS and must keep doing so:
|
|
161
|
+
// its rule is `first_ratifier`, under which expiry means PASS, so a default
|
|
162
|
+
// clock would ratify what nobody read. But "the platform must not arm this for
|
|
163
|
+
// you" is not the same as "you must invent a number" — and the owner asking for
|
|
164
|
+
// unanimity asked for the timer in the same breath, because under a rule where
|
|
165
|
+
// absence is nulled the deadline is the only thing that ends a quiet sitting.
|
|
166
|
+
//
|
|
167
|
+
// So the recommended sitting length lives here as a named constant rather than
|
|
168
|
+
// as a magic number the Board Room's charter library retypes. Twenty-four hours
|
|
169
|
+
// matches the pace the rest of the pipeline runs at (the 2026-08-25 amendment
|
|
170
|
+
// reached for the same figure independently), and is long enough that a member
|
|
171
|
+
// asleep in another timezone still gets a vote.
|
|
172
|
+
//
|
|
173
|
+
// A constitution may still legitimately name `window_minutes: null` under a
|
|
174
|
+
// clock-safe rule — that is "wait indefinitely for a voice", a real state a
|
|
175
|
+
// project can choose, and the hall says so out loud rather than pretending.
|
|
176
|
+
const RECOMMENDED_WINDOW_MINUTES = 1440;
|
|
177
|
+
|
|
178
|
+
// Which rules a clock is SAFE under — ADR 0191 §4's condition, as data instead
|
|
179
|
+
// of as a paragraph: a deadline may only be given to a rule whose expiry means
|
|
180
|
+
// RETURN. Under `first_ratifier` and `consent` the tally passes on absence, so
|
|
181
|
+
// the expiry sweep would ratify unread items; under `majority` and `unanimous`
|
|
182
|
+
// it returns them. The Board Room reads this to decide whether offering a clock
|
|
183
|
+
// alongside a proposed rule is honest.
|
|
184
|
+
const CLOCK_SAFE_PASS_RULES = Object.freeze(['majority', 'unanimous']);
|
|
185
|
+
const isClockSafe = (rule) => CLOCK_SAFE_PASS_RULES.includes(rule);
|
|
186
|
+
|
|
158
187
|
// The pass rules that EXIST, not the ones a project should pick. `first_ratifier`
|
|
159
188
|
// is the day-one default and what an unreadable value falls back to.
|
|
160
189
|
//
|
|
@@ -166,14 +195,23 @@ const BOARD_DEFAULTS = Object.freeze({
|
|
|
166
195
|
// UNOPPOSED. Silence is assent, deliberately (owner decision 6).
|
|
167
196
|
// • majority — strictly more than half of the RESOLVED MEMBERSHIP must
|
|
168
197
|
// vote yes. Silence is NOT assent: an item that never reaches a majority
|
|
169
|
-
// RETURNS at expiry.
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
|
|
198
|
+
// RETURNS at expiry. Not voting is a meaningful act here, and a clock is
|
|
199
|
+
// safe — see the note on window_minutes below.
|
|
200
|
+
// • unanimous — nobody who spoke objected, and somebody spoke. One
|
|
201
|
+
// reasoned objection returns it; so does a sitting that ends with no
|
|
202
|
+
// affirmative voice at all. Silence neither blocks nor assents: an absent
|
|
203
|
+
// member drops out of the reckoning entirely (there is NO membership
|
|
204
|
+
// denominator here, which is exactly what separates it from `majority`),
|
|
205
|
+
// and absence alone can never carry an item either. A clock is safe under
|
|
206
|
+
// this rule for the same reason it is safe under `majority` — expiry with
|
|
207
|
+
// nothing cast RETURNS. Owner decision 2026-09-08 (ADR 0267): a Full Idea
|
|
208
|
+
// is ratified by unanimity, and a sitting that does not carry goes back to
|
|
209
|
+
// its author to be revised and re-sat, as many times as it takes.
|
|
210
|
+
// Adding `majority` or `unanimous` cannot widen the fail-closed fallback:
|
|
211
|
+
// sanitizeBoard still falls back to first_ratifier, and both of the added rules
|
|
212
|
+
// sit at the STRICTER end of this axis anyway (each demands an affirmative
|
|
213
|
+
// vote, where the original two pass on absence).
|
|
214
|
+
const PASS_RULES = Object.freeze(['first_ratifier', 'consent', 'majority', 'unanimous']);
|
|
177
215
|
|
|
178
216
|
// A membership predicate names WHO SITS. The grammar and its parser live in
|
|
179
217
|
// ./membership-predicate — ONE implementation, shared with R06's resolver
|
|
@@ -321,6 +359,9 @@ module.exports = {
|
|
|
321
359
|
applyBoardAmendment,
|
|
322
360
|
BOARD_DEFAULTS,
|
|
323
361
|
PASS_RULES,
|
|
362
|
+
RECOMMENDED_WINDOW_MINUTES,
|
|
363
|
+
CLOCK_SAFE_PASS_RULES,
|
|
364
|
+
isClockSafe,
|
|
324
365
|
MAX_WINDOW_MINUTES,
|
|
325
366
|
NEUTRAL_PATH,
|
|
326
367
|
INSTANCE_PATH,
|
|
@@ -594,12 +594,31 @@
|
|
|
594
594
|
+ 'Silence is <em>not</em> agreement here, so an item that never reaches a majority is returned to its author. '
|
|
595
595
|
+ 'An author may vote on their own idea, but it can never be carried by their vote alone.';
|
|
596
596
|
}
|
|
597
|
+
// ADR 0267. Silence is the axis again, and unanimity sits between the other
|
|
598
|
+
// three on it: an absent member does not block (that is what the clock is
|
|
599
|
+
// for), but absence alone cannot carry anything either.
|
|
600
|
+
if (board.pass_rule === 'unanimous') {
|
|
601
|
+
return 'Items pass by <strong>unanimous agreement</strong>: everyone who votes must vote yes, and at least one member must. '
|
|
602
|
+
+ 'A single reasoned objection returns the item to its author to be revised and put back to the board — as many rounds as it takes. '
|
|
603
|
+
+ 'Being away does not block anything, but a sitting that ends with nobody in favour is returned rather than passed. '
|
|
604
|
+
+ 'An author may vote on their own idea, but it can never be carried by their vote alone.';
|
|
605
|
+
}
|
|
597
606
|
return `Pass rule: <code>${escapeHtml(String(board.pass_rule || 'unset'))}</code>.`;
|
|
598
607
|
}
|
|
599
608
|
|
|
600
|
-
|
|
609
|
+
// The clock, and the advice the SERVER computes about it (task 1003733). A
|
|
610
|
+
// proposer choosing a pass rule must choose a window in the same breath — a
|
|
611
|
+
// constitution is ratified whole — and the two are not independent: a deadline
|
|
612
|
+
// is only honest under a rule whose expiry RETURNS the item (ADR 0191 §4).
|
|
613
|
+
// Which rules those are, and what to put in the box, come from
|
|
614
|
+
// GET /government/constitution's `clock` block; nothing is derived here, so
|
|
615
|
+
// the page cannot drift from what config.js actually considers safe.
|
|
616
|
+
function windowSentence(board, clock) {
|
|
601
617
|
if (board.window_minutes === null || board.window_minutes === undefined) {
|
|
602
|
-
|
|
618
|
+
const advice = clock && clock.live_rule_is_clock_safe && clock.recommended_window_minutes
|
|
619
|
+
? ` Under this pass rule a deadline would be safe — an expired sitting is <em>returned</em>, never quietly ratified — so a clock is worth proposing. The recommended sitting is <strong>${fmtWindow(clock.recommended_window_minutes)}</strong>.`
|
|
620
|
+
: '';
|
|
621
|
+
return `There is <strong>no clock</strong> — an item stays open until a vote decides it.${advice}`;
|
|
603
622
|
}
|
|
604
623
|
const early = board.close_early_on_full_turnout
|
|
605
624
|
? ', closing early the moment every member has voted'
|
|
@@ -636,8 +655,9 @@
|
|
|
636
655
|
// trajectories, the ladder — and deliberately not the configuration machinery
|
|
637
656
|
// (the scale argument in ADR 0175: formality is adopted only where informal
|
|
638
657
|
// coordination breaks). `selectable` means EXPRESSIBLE TODAY through the real
|
|
639
|
-
// config — a rank-predicate membership plus
|
|
640
|
-
//
|
|
658
|
+
// config — a rank-predicate membership plus one of the four pass rules
|
|
659
|
+
// (first_ratifier | consent | majority | unanimous) — so nobody is misled
|
|
660
|
+
// about what a settings change can and cannot reach. Source: the
|
|
641
661
|
// Six-Variables synthesis (Verro Labs, Aug 2026), Parts III–IV, via the spec.
|
|
642
662
|
// EVERY SELECTABLE CHARTER CARRIES A MACHINE-CHECKABLE PREDICATE (task 1003095).
|
|
643
663
|
//
|
|
@@ -752,7 +772,7 @@
|
|
|
752
772
|
<ul class="ov-rows">
|
|
753
773
|
${dialRow('Who sits on the board', membershipSentence(b))}
|
|
754
774
|
${dialRow('What counts as passing', passRuleSentence(b))}
|
|
755
|
-
${dialRow('How long a sitting runs', windowSentence(b))}
|
|
775
|
+
${dialRow('How long a sitting runs', windowSentence(b, data.clock))}
|
|
756
776
|
</ul>
|
|
757
777
|
<p class="ov-foot">As configuration: <code>${escapeHtml(JSON.stringify(b))}</code>. Changing it is a board act — propose an amendment, and it is decided under the rule above.</p>
|
|
758
778
|
`;
|
|
@@ -805,6 +825,9 @@
|
|
|
805
825
|
// it waits indefinitely for a majority, which is a real state a project can
|
|
806
826
|
// configure itself into (task 1003273).
|
|
807
827
|
if (rule === 'majority') return 'no clock — waits for a majority';
|
|
828
|
+
// Same honesty for unanimity: with no deadline it waits indefinitely for
|
|
829
|
+
// someone to be in favour, which is a state a project can configure (ADR 0267).
|
|
830
|
+
if (rule === 'unanimous') return 'no clock — waits for a unanimous yes';
|
|
808
831
|
return 'no clock';
|
|
809
832
|
}
|
|
810
833
|
|
|
@@ -907,12 +930,14 @@
|
|
|
907
930
|
first_ratifier: 'by first ratifier',
|
|
908
931
|
consent: 'by consent',
|
|
909
932
|
majority: 'by majority',
|
|
933
|
+
unanimous: 'by unanimous agreement',
|
|
910
934
|
};
|
|
911
935
|
|
|
912
936
|
const PASS_RULE_PLAIN = {
|
|
913
937
|
first_ratifier: 'The first vote decides it: a yes ratifies, an objection returns it to its author.',
|
|
914
938
|
consent: 'It passes unless someone objects — silence is agreement, and one reasoned objection returns it.',
|
|
915
939
|
majority: 'It passes only when more than half the board votes yes. Silence is not agreement here.',
|
|
940
|
+
unanimous: 'Everyone who votes must vote yes, and at least one member must. One objection sends it back for revision.',
|
|
916
941
|
};
|
|
917
942
|
|
|
918
943
|
function decidedByHtml(item) {
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.615",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.615",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|