@bongos/core 1.21.8 → 1.21.10
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 +59 -34
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -0
- package/clients/bongos-client/index.cjs +4 -0
- package/clients/bongos-client/index.d.ts +8 -0
- package/clients/bongos-client/index.mjs +4 -0
- package/docs/adr/0363-who-decides-what-is-a-table-changed-only-by-amendment.md +52 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +182 -3
- package/docs/api-reference.md +4 -2
- package/docs/copy-inventory.md +8 -27
- package/docs/copy-registry.json +6 -199
- package/docs/module-api-changelog.md +4 -0
- package/modules/discord/board-broadcast.js +2 -0
- package/modules/government/board.js +82 -23
- package/modules/government/db.js +81 -6
- package/modules/government/decision-board.js +300 -0
- package/modules/government/decision-rules.js +312 -0
- package/modules/government/docket.js +49 -9
- package/modules/government/migrations/government_023_decision_rules.sql +190 -0
- package/modules/government/routes/government.js +115 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/gds/copy-inventory.js +24 -7
- package/src/bongos/route-rank-check.js +6 -0
- package/src/module-api.js +1 -1
- package/tests/copy_inventory.mjs +62 -0
- package/tests/government_abuse_matrix.mjs +3 -0
- package/tests/government_decision_rules.mjs +701 -0
- package/tests/government_routes.mjs +4 -0
- package/tests/government_seed.mjs +1 -0
package/docs/copy-registry.json
CHANGED
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
"generator": "scripts/gds/copy-inventory.js",
|
|
4
4
|
"goal": 1000074,
|
|
5
5
|
"counts": {
|
|
6
|
-
"strings":
|
|
7
|
-
"unique_ids":
|
|
8
|
-
"certain":
|
|
6
|
+
"strings": 2417,
|
|
7
|
+
"unique_ids": 2091,
|
|
8
|
+
"certain": 2417,
|
|
9
9
|
"heuristic": 0,
|
|
10
|
-
"duplicate_ids":
|
|
10
|
+
"duplicate_ids": 220
|
|
11
11
|
},
|
|
12
12
|
"surfaces": {
|
|
13
13
|
"builders-hall": {
|
|
14
14
|
"at": "modules/hall-ui/public",
|
|
15
15
|
"files": 125,
|
|
16
|
-
"strings":
|
|
16
|
+
"strings": 1751
|
|
17
17
|
},
|
|
18
18
|
"status-dashboard": {
|
|
19
19
|
"at": "modules/status-ui/public",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"landing": {
|
|
24
24
|
"at": "modules/public-landing/public",
|
|
25
25
|
"files": 6,
|
|
26
|
-
"strings":
|
|
26
|
+
"strings": 566
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
"blind_spots": [
|
|
@@ -368,28 +368,6 @@
|
|
|
368
368
|
"modules/public-landing/public/projects.html:883"
|
|
369
369
|
]
|
|
370
370
|
},
|
|
371
|
-
{
|
|
372
|
-
"id": "ed3e9f97ed31",
|
|
373
|
-
"surface": "builders-hall",
|
|
374
|
-
"text": ", not a",
|
|
375
|
-
"occurrences": 3,
|
|
376
|
-
"at": [
|
|
377
|
-
"modules/hall-ui/public/studio.html:92",
|
|
378
|
-
"modules/hall-ui/public/thinking.html:40",
|
|
379
|
-
"modules/hall-ui/public/tweak-editor.html:49"
|
|
380
|
-
]
|
|
381
|
-
},
|
|
382
|
-
{
|
|
383
|
-
"id": "20b0408512ae",
|
|
384
|
-
"surface": "builders-hall",
|
|
385
|
-
"text": ": the shell's top bar is the page's one",
|
|
386
|
-
"occurrences": 3,
|
|
387
|
-
"at": [
|
|
388
|
-
"modules/hall-ui/public/studio.html:92",
|
|
389
|
-
"modules/hall-ui/public/thinking.html:41",
|
|
390
|
-
"modules/hall-ui/public/tweak-editor.html:49"
|
|
391
|
-
]
|
|
392
|
-
},
|
|
393
371
|
{
|
|
394
372
|
"id": "08afb1257b71",
|
|
395
373
|
"surface": "builders-hall",
|
|
@@ -2422,15 +2400,6 @@
|
|
|
2422
2400
|
"origin": "js-markup",
|
|
2423
2401
|
"confidence": "certain"
|
|
2424
2402
|
},
|
|
2425
|
-
{
|
|
2426
|
-
"id": "1ee8125b806c",
|
|
2427
|
-
"surface": "builders-hall",
|
|
2428
|
-
"text": "(README \"Where the screens live\"; task 1004526 builds them). A pin opens the matter's short card (task 1004528 makes it act). The drawing is street-draw.js (the prototype's front-on kit, verbatim, as pure data) over city-draw.js (the glyphs and the seeded random, shared with the city); street.js reads the feeds and builds the nodes; street.css is the template's",
|
|
2429
|
-
"file": "modules/hall-ui/public/street.html",
|
|
2430
|
-
"line": 31,
|
|
2431
|
-
"origin": "html-text",
|
|
2432
|
-
"confidence": "certain"
|
|
2433
|
-
},
|
|
2434
2403
|
{
|
|
2435
2404
|
"id": "af615fa9f404",
|
|
2436
2405
|
"surface": "builders-hall",
|
|
@@ -2440,15 +2409,6 @@
|
|
|
2440
2409
|
"origin": "js-markup",
|
|
2441
2410
|
"confidence": "certain"
|
|
2442
2411
|
},
|
|
2443
|
-
{
|
|
2444
|
-
"id": "544794f1ec8f",
|
|
2445
|
-
"surface": "builders-hall",
|
|
2446
|
-
"text": "(later in",
|
|
2447
|
-
"file": "modules/hall-ui/public/index.html",
|
|
2448
|
-
"line": 9,
|
|
2449
|
-
"origin": "html-text",
|
|
2450
|
-
"confidence": "certain"
|
|
2451
|
-
},
|
|
2452
2412
|
{
|
|
2453
2413
|
"id": "3ac2eb36ba97",
|
|
2454
2414
|
"surface": "builders-hall",
|
|
@@ -2476,15 +2436,6 @@
|
|
|
2476
2436
|
"origin": "js-markup",
|
|
2477
2437
|
"confidence": "certain"
|
|
2478
2438
|
},
|
|
2479
|
-
{
|
|
2480
|
-
"id": "04e34924e1c4",
|
|
2481
|
-
"surface": "builders-hall",
|
|
2482
|
-
"text": ") to where its interior lives — /builders/street#building=",
|
|
2483
|
-
"file": "modules/hall-ui/public/street.html",
|
|
2484
|
-
"line": 30,
|
|
2485
|
-
"origin": "html-text",
|
|
2486
|
-
"confidence": "certain"
|
|
2487
|
-
},
|
|
2488
2439
|
{
|
|
2489
2440
|
"id": "c81e3a97b531",
|
|
2490
2441
|
"surface": "builders-hall",
|
|
@@ -2575,33 +2526,6 @@
|
|
|
2575
2526
|
"origin": "js-markup",
|
|
2576
2527
|
"confidence": "certain"
|
|
2577
2528
|
},
|
|
2578
|
-
{
|
|
2579
|
-
"id": "ed3e9f97ed31",
|
|
2580
|
-
"surface": "builders-hall",
|
|
2581
|
-
"text": ", not a",
|
|
2582
|
-
"file": "modules/hall-ui/public/studio.html",
|
|
2583
|
-
"line": 92,
|
|
2584
|
-
"origin": "html-text",
|
|
2585
|
-
"confidence": "certain"
|
|
2586
|
-
},
|
|
2587
|
-
{
|
|
2588
|
-
"id": "ed3e9f97ed31",
|
|
2589
|
-
"surface": "builders-hall",
|
|
2590
|
-
"text": ", not a",
|
|
2591
|
-
"file": "modules/hall-ui/public/thinking.html",
|
|
2592
|
-
"line": 40,
|
|
2593
|
-
"origin": "html-text",
|
|
2594
|
-
"confidence": "certain"
|
|
2595
|
-
},
|
|
2596
|
-
{
|
|
2597
|
-
"id": "ed3e9f97ed31",
|
|
2598
|
-
"surface": "builders-hall",
|
|
2599
|
-
"text": ", not a",
|
|
2600
|
-
"file": "modules/hall-ui/public/tweak-editor.html",
|
|
2601
|
-
"line": 49,
|
|
2602
|
-
"origin": "html-text",
|
|
2603
|
-
"confidence": "certain"
|
|
2604
|
-
},
|
|
2605
2529
|
{
|
|
2606
2530
|
"id": "741e355954ac",
|
|
2607
2531
|
"surface": "builders-hall",
|
|
@@ -2611,15 +2535,6 @@
|
|
|
2611
2535
|
"origin": "html-text",
|
|
2612
2536
|
"confidence": "certain"
|
|
2613
2537
|
},
|
|
2614
|
-
{
|
|
2615
|
-
"id": "6025eb36b204",
|
|
2616
|
-
"surface": "builders-hall",
|
|
2617
|
-
"text": ", not the prototype's",
|
|
2618
|
-
"file": "modules/hall-ui/public/city.html",
|
|
2619
|
-
"line": 31,
|
|
2620
|
-
"origin": "html-text",
|
|
2621
|
-
"confidence": "certain"
|
|
2622
|
-
},
|
|
2623
2538
|
{
|
|
2624
2539
|
"id": "b31a6bd7edc5",
|
|
2625
2540
|
"surface": "builders-hall",
|
|
@@ -2629,15 +2544,6 @@
|
|
|
2629
2544
|
"origin": "js-markup",
|
|
2630
2545
|
"confidence": "certain"
|
|
2631
2546
|
},
|
|
2632
|
-
{
|
|
2633
|
-
"id": "cc4fe2daf372",
|
|
2634
|
-
"surface": "builders-hall",
|
|
2635
|
-
"text": ", so the brand is a",
|
|
2636
|
-
"file": "modules/hall-ui/public/goal-map.html",
|
|
2637
|
-
"line": 66,
|
|
2638
|
-
"origin": "html-text",
|
|
2639
|
-
"confidence": "certain"
|
|
2640
|
-
},
|
|
2641
2547
|
{
|
|
2642
2548
|
"id": "c7c9b0afc38e",
|
|
2643
2549
|
"surface": "builders-hall",
|
|
@@ -2647,15 +2553,6 @@
|
|
|
2647
2553
|
"origin": "html-text",
|
|
2648
2554
|
"confidence": "certain"
|
|
2649
2555
|
},
|
|
2650
|
-
{
|
|
2651
|
-
"id": "7a8111edba0a",
|
|
2652
|
-
"surface": "builders-hall",
|
|
2653
|
-
"text": ", which is a containing block for its position: fixed layers (thinking.css), so the template's fixed layout is unchanged and simply measures from the stage instead of the window. The sky's heading is a",
|
|
2654
|
-
"file": "modules/hall-ui/public/thinking.html",
|
|
2655
|
-
"line": 37,
|
|
2656
|
-
"origin": "html-text",
|
|
2657
|
-
"confidence": "certain"
|
|
2658
|
-
},
|
|
2659
2556
|
{
|
|
2660
2557
|
"id": "24174ce79df3",
|
|
2661
2558
|
"surface": "builders-hall",
|
|
@@ -2701,33 +2598,6 @@
|
|
|
2701
2598
|
"origin": "html-text",
|
|
2702
2599
|
"confidence": "certain"
|
|
2703
2600
|
},
|
|
2704
|
-
{
|
|
2705
|
-
"id": "20b0408512ae",
|
|
2706
|
-
"surface": "builders-hall",
|
|
2707
|
-
"text": ": the shell's top bar is the page's one",
|
|
2708
|
-
"file": "modules/hall-ui/public/studio.html",
|
|
2709
|
-
"line": 92,
|
|
2710
|
-
"origin": "html-text",
|
|
2711
|
-
"confidence": "certain"
|
|
2712
|
-
},
|
|
2713
|
-
{
|
|
2714
|
-
"id": "20b0408512ae",
|
|
2715
|
-
"surface": "builders-hall",
|
|
2716
|
-
"text": ": the shell's top bar is the page's one",
|
|
2717
|
-
"file": "modules/hall-ui/public/thinking.html",
|
|
2718
|
-
"line": 41,
|
|
2719
|
-
"origin": "html-text",
|
|
2720
|
-
"confidence": "certain"
|
|
2721
|
-
},
|
|
2722
|
-
{
|
|
2723
|
-
"id": "20b0408512ae",
|
|
2724
|
-
"surface": "builders-hall",
|
|
2725
|
-
"text": ": the shell's top bar is the page's one",
|
|
2726
|
-
"file": "modules/hall-ui/public/tweak-editor.html",
|
|
2727
|
-
"line": 49,
|
|
2728
|
-
"origin": "html-text",
|
|
2729
|
-
"confidence": "certain"
|
|
2730
|
-
},
|
|
2731
2601
|
{
|
|
2732
2602
|
"id": "353fff36068f",
|
|
2733
2603
|
"surface": "builders-hall",
|
|
@@ -2737,15 +2607,6 @@
|
|
|
2737
2607
|
"origin": "js-markup",
|
|
2738
2608
|
"confidence": "certain"
|
|
2739
2609
|
},
|
|
2740
|
-
{
|
|
2741
|
-
"id": "2a5aa36f4d29",
|
|
2742
|
-
"surface": "builders-hall",
|
|
2743
|
-
"text": "; the sky's first build made only the BRAND the header (the kit's top-bar-80px floor). In the hall the shell's top bar is the page's header, so the brand is a",
|
|
2744
|
-
"file": "modules/hall-ui/public/thinking.html",
|
|
2745
|
-
"line": 66,
|
|
2746
|
-
"origin": "html-text",
|
|
2747
|
-
"confidence": "certain"
|
|
2748
|
-
},
|
|
2749
2610
|
{
|
|
2750
2611
|
"id": "8ffdf4995e81",
|
|
2751
2612
|
"surface": "builders-hall",
|
|
@@ -14851,15 +14712,6 @@
|
|
|
14851
14712
|
"origin": "js-markup",
|
|
14852
14713
|
"confidence": "certain"
|
|
14853
14714
|
},
|
|
14854
|
-
{
|
|
14855
|
-
"id": "c1367ee35fec",
|
|
14856
|
-
"surface": "builders-hall",
|
|
14857
|
-
"text": "and #district=",
|
|
14858
|
-
"file": "modules/hall-ui/public/street.html",
|
|
14859
|
-
"line": 31,
|
|
14860
|
-
"origin": "html-text",
|
|
14861
|
-
"confidence": "certain"
|
|
14862
|
-
},
|
|
14863
14715
|
{
|
|
14864
14716
|
"id": "754babaae931",
|
|
14865
14717
|
"surface": "builders-hall",
|
|
@@ -15607,15 +15459,6 @@
|
|
|
15607
15459
|
"origin": "html-text",
|
|
15608
15460
|
"confidence": "certain"
|
|
15609
15461
|
},
|
|
15610
|
-
{
|
|
15611
|
-
"id": "af0777cd098b",
|
|
15612
|
-
"surface": "builders-hall",
|
|
15613
|
-
"text": "is hardcoded back inside this",
|
|
15614
|
-
"file": "modules/hall-ui/public/index.html",
|
|
15615
|
-
"line": 60,
|
|
15616
|
-
"origin": "html-text",
|
|
15617
|
-
"confidence": "certain"
|
|
15618
|
-
},
|
|
15619
15462
|
{
|
|
15620
15463
|
"id": "d0e722302840",
|
|
15621
15464
|
"surface": "builders-hall",
|
|
@@ -15625,15 +15468,6 @@
|
|
|
15625
15468
|
"origin": "js-markup",
|
|
15626
15469
|
"confidence": "certain"
|
|
15627
15470
|
},
|
|
15628
|
-
{
|
|
15629
|
-
"id": "aa530bd15088",
|
|
15630
|
-
"surface": "builders-hall",
|
|
15631
|
-
"text": "itself). The registry stays the single source of truth for what the hall shows; a fitness check (scripts/gds/fitness.js checkHallSectionRegistry) reds the build if a",
|
|
15632
|
-
"file": "modules/hall-ui/public/index.html",
|
|
15633
|
-
"line": 57,
|
|
15634
|
-
"origin": "html-text",
|
|
15635
|
-
"confidence": "certain"
|
|
15636
|
-
},
|
|
15637
15471
|
{
|
|
15638
15472
|
"id": "6b530a7007bc",
|
|
15639
15473
|
"surface": "builders-hall",
|
|
@@ -16021,15 +15855,6 @@
|
|
|
16021
15855
|
"origin": "js-markup",
|
|
16022
15856
|
"confidence": "certain"
|
|
16023
15857
|
},
|
|
16024
|
-
{
|
|
16025
|
-
"id": "8e888f28b08f",
|
|
16026
|
-
"surface": "builders-hall",
|
|
16027
|
-
"text": "on the hall's tokens. The button is a link to /builders/street, the street's own hall page, which task 1004525 builds. It is an",
|
|
16028
|
-
"file": "modules/hall-ui/public/city.html",
|
|
16029
|
-
"line": 27,
|
|
16030
|
-
"origin": "html-text",
|
|
16031
|
-
"confidence": "certain"
|
|
16032
|
-
},
|
|
16033
15858
|
{
|
|
16034
15859
|
"id": "f5a9579bbc36",
|
|
16035
15860
|
"surface": "builders-hall",
|
|
@@ -16318,15 +16143,6 @@
|
|
|
16318
16143
|
"origin": "js-markup",
|
|
16319
16144
|
"confidence": "certain"
|
|
16320
16145
|
},
|
|
16321
|
-
{
|
|
16322
|
-
"id": "594962dc9ec3",
|
|
16323
|
-
"surface": "builders-hall",
|
|
16324
|
-
"text": "s into the view containers below (falling back to",
|
|
16325
|
-
"file": "modules/hall-ui/public/index.html",
|
|
16326
|
-
"line": 56,
|
|
16327
|
-
"origin": "html-text",
|
|
16328
|
-
"confidence": "certain"
|
|
16329
|
-
},
|
|
16330
16146
|
{
|
|
16331
16147
|
"id": "2a42549b34ae",
|
|
16332
16148
|
"surface": "builders-hall",
|
|
@@ -22942,15 +22758,6 @@
|
|
|
22942
22758
|
"origin": "html-text",
|
|
22943
22759
|
"confidence": "certain"
|
|
22944
22760
|
},
|
|
22945
|
-
{
|
|
22946
|
-
"id": "c50f6ad6e51e",
|
|
22947
|
-
"surface": "landing",
|
|
22948
|
-
"text": "shipped on",
|
|
22949
|
-
"file": "modules/public-landing/public/index.html",
|
|
22950
|
-
"line": 634,
|
|
22951
|
-
"origin": "html-text",
|
|
22952
|
-
"confidence": "certain"
|
|
22953
|
-
},
|
|
22954
22761
|
{
|
|
22955
22762
|
"id": "d06d9d2f5fb7",
|
|
22956
22763
|
"surface": "landing",
|
|
@@ -2843,5 +2843,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2843
2843
|
landed since 1.21.6 with no explicit bump. run 37038340446. (task 1002620)
|
|
2844
2844
|
1.21.8 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2845
2845
|
landed since 1.21.7 with no explicit bump. run 37041143274. (task 1002620)
|
|
2846
|
+
1.21.9 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2847
|
+
landed since 1.21.8 with no explicit bump. run 37042188671. (task 1002620)
|
|
2848
|
+
1.21.10 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2849
|
+
landed since 1.21.9 with no explicit bump. run 37048132870. (task 1002620)
|
|
2846
2850
|
---------------------------------------------------------------------------
|
|
2847
2851
|
```
|
|
@@ -149,6 +149,8 @@ const CHANNEL = 'board_room';
|
|
|
149
149
|
|
|
150
150
|
function subjectLabel(subjectType) {
|
|
151
151
|
if (subjectType === 'genesis_stage') return 'A genesis stage'; // task 1004429
|
|
152
|
+
// task 1004529: a building's matter (rules_matter, people_matter, ...)
|
|
153
|
+
if (typeof subjectType === 'string' && subjectType.endsWith('_matter')) return 'A matter';
|
|
152
154
|
return subjectType === 'constitutional_amendment' ? 'A constitutional amendment' : 'A Full Idea';
|
|
153
155
|
}
|
|
154
156
|
|
|
@@ -61,6 +61,8 @@ const { describeMembership } = require('./membership-predicate');
|
|
|
61
61
|
// and variable names, and the name a constitution reads as.
|
|
62
62
|
const charter = require('./charter');
|
|
63
63
|
const recommendations = require('./recommendations');
|
|
64
|
+
const decisionRules = require('./decision-rules');
|
|
65
|
+
const decisionBoard = require('./decision-board');
|
|
64
66
|
|
|
65
67
|
// ADR 0175 §5: the completeness bar a Full Idea must clear to reach the board.
|
|
66
68
|
// Held equal to economy's IDEA_FULL_PASS_BAR by test — see the header.
|
|
@@ -296,7 +298,7 @@ async function castVote({ itemId, voterId, direction, reasonMd = null, sectionKe
|
|
|
296
298
|
// owner's rule is that a self-vote confers no advantage.
|
|
297
299
|
// Not even asked for an amendment — the carve-out means there is nothing to
|
|
298
300
|
// learn, and the read is a query on the vote path.
|
|
299
|
-
const subjectAuthorId = authorRuleApplies(item.subject_type)
|
|
301
|
+
const subjectAuthorId = authorRuleApplies(item.subject_type, constitution)
|
|
300
302
|
? await db.getBoardItemAuthor(item)
|
|
301
303
|
: null;
|
|
302
304
|
const isSelfCast = subjectAuthorId != null && String(subjectAuthorId) === String(voterId);
|
|
@@ -389,12 +391,17 @@ async function castVote({ itemId, voterId, direction, reasonMd = null, sectionKe
|
|
|
389
391
|
// Everything downstream reads the author through this: for an amendment the
|
|
390
392
|
// deciding author is NULL, so the trigger and the tally behave exactly as they
|
|
391
393
|
// did before task 1003273.
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
(
|
|
394
|
+
// One more scope (task 1004529): a building's matter sat under SIGN-OFF, which
|
|
395
|
+
// IS the author rule (someone who did not build it signs). Other matters are in
|
|
396
|
+
// the amendment's position.
|
|
397
|
+
const authorRuleApplies = (subjectType, constitution = null) =>
|
|
398
|
+
subjectType === 'full_idea'
|
|
399
|
+
|| (decisionRules.isMatterSubject(subjectType) && !!constitution && constitution.rule === 'signoff');
|
|
400
|
+
|
|
401
|
+
// The author id the DECIDING rules see: real for a full idea or a sign-off
|
|
402
|
+
// matter, nobody for an amendment.
|
|
403
|
+
const decidingAuthorId = (subjectType, authorId, constitution = null) =>
|
|
404
|
+
(authorRuleApplies(subjectType, constitution) ? authorId : null);
|
|
398
405
|
|
|
399
406
|
const isAuthorVote = (v, authorId) =>
|
|
400
407
|
authorId != null && String(v.voter_id) === String(authorId);
|
|
@@ -581,7 +588,7 @@ async function closeItem(item, { cause = 'vote' } = {}) {
|
|
|
581
588
|
// the read stays where it always was — after the tally, only on a pass, for the
|
|
582
589
|
// payload and the ratification karma. Same query count as before this task on
|
|
583
590
|
// the amendment path, one extra on an idea close.
|
|
584
|
-
let authorId = authorRuleApplies(item.subject_type)
|
|
591
|
+
let authorId = authorRuleApplies(item.subject_type, constitution)
|
|
585
592
|
? await db.getBoardItemAuthor(item)
|
|
586
593
|
: null;
|
|
587
594
|
// Only `majority` needs the membership denominator; the absence-passes rules
|
|
@@ -596,7 +603,7 @@ async function closeItem(item, { cause = 'vote' } = {}) {
|
|
|
596
603
|
const outcome = tallyOutcome(votes, {
|
|
597
604
|
constitution, members,
|
|
598
605
|
// Subject-typed: an amendment's proposer is not held to the author rule (§7).
|
|
599
|
-
authorId: decidingAuthorId(item.subject_type, authorId),
|
|
606
|
+
authorId: decidingAuthorId(item.subject_type, authorId, constitution),
|
|
600
607
|
});
|
|
601
608
|
|
|
602
609
|
// The payload's remaining reads (reads never ride a tx they don't need). Only a
|
|
@@ -616,16 +623,28 @@ async function closeItem(item, { cause = 'vote' } = {}) {
|
|
|
616
623
|
: null;
|
|
617
624
|
const payMoney = reward && typeof reward.awardIdeaGovernancePassCredit === 'function';
|
|
618
625
|
|
|
626
|
+
// Read BEFORE the close (task 1004529): a decision-rules amendment is written
|
|
627
|
+
// INSIDE the closing transaction, so a failed write rolls the close back and
|
|
628
|
+
// the sitting is retried. A board-block amendment still applies after commit.
|
|
629
|
+
const amendment = outcome === 'passed' && item.subject_type === 'constitutional_amendment'
|
|
630
|
+
? await db.getAmendment(item.subject_id)
|
|
631
|
+
: null;
|
|
632
|
+
const rulesAmendment = !!amendment && amendment.kind === 'decision_rules';
|
|
633
|
+
let appliedDecisionRules = null;
|
|
634
|
+
|
|
619
635
|
const closed = await db.closeBoardItem({
|
|
620
636
|
itemId: item.id,
|
|
621
637
|
outcome,
|
|
622
|
-
afterClose: payMoney
|
|
623
|
-
? async (client) => {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
638
|
+
afterClose: (payMoney || rulesAmendment)
|
|
639
|
+
? async (client, closedRow) => {
|
|
640
|
+
if (payMoney) {
|
|
641
|
+
credit = await reward.awardIdeaGovernancePassCredit(client, {
|
|
642
|
+
ideaId: item.subject_id,
|
|
643
|
+
authorId,
|
|
644
|
+
completenessScore,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
if (rulesAmendment) appliedDecisionRules = await decisionBoard.applyPassedDecisionRules(client, closedRow, amendment);
|
|
629
648
|
}
|
|
630
649
|
: null,
|
|
631
650
|
});
|
|
@@ -633,8 +652,10 @@ async function closeItem(item, { cause = 'vote' } = {}) {
|
|
|
633
652
|
|
|
634
653
|
// A genesis stage ratifies no authored work — closing it is the project's
|
|
635
654
|
// decision that a stage is done, not a reward for the founder who put it up
|
|
636
|
-
// (task 1004429). Ideas and amendments pay exactly as before
|
|
655
|
+
// (task 1004429). Ideas and amendments pay exactly as before; a building's
|
|
656
|
+
// matter pays nothing either (task 1004529).
|
|
637
657
|
const authorKarma = outcome === 'passed' && closed.subject_type !== 'genesis_stage'
|
|
658
|
+
&& !decisionRules.isMatterSubject(closed.subject_type)
|
|
638
659
|
? await awardAuthorRatifyKarma(closed)
|
|
639
660
|
: null;
|
|
640
661
|
|
|
@@ -643,9 +664,8 @@ async function closeItem(item, { cause = 'vote' } = {}) {
|
|
|
643
664
|
// failure, never silently absorbed. The apply is re-drivable from the passed
|
|
644
665
|
// item + amendment rows. A returned amendment applies nothing.
|
|
645
666
|
let appliedConstitution = null;
|
|
646
|
-
if (outcome === 'passed' && closed.subject_type === 'constitutional_amendment') {
|
|
667
|
+
if (outcome === 'passed' && closed.subject_type === 'constitutional_amendment' && !rulesAmendment) {
|
|
647
668
|
try {
|
|
648
|
-
const amendment = await db.getAmendment(closed.subject_id);
|
|
649
669
|
if (amendment) {
|
|
650
670
|
appliedConstitution = governmentConfig.applyBoardAmendment(amendment.proposed);
|
|
651
671
|
} else {
|
|
@@ -656,12 +676,21 @@ async function closeItem(item, { cause = 'vote' } = {}) {
|
|
|
656
676
|
}
|
|
657
677
|
}
|
|
658
678
|
|
|
679
|
+
// A PASSED building matter: the building acts where it can by itself (People
|
|
680
|
+
// giving or taking a custom rank, task 1004529), after the commit, loudly on
|
|
681
|
+
// failure — the passed row is the durable decision.
|
|
682
|
+
let acted = null;
|
|
683
|
+
if (outcome === 'passed' && decisionRules.isMatterSubject(closed.subject_type)) {
|
|
684
|
+
acted = await decisionBoard.actOnPassedMatter(closed);
|
|
685
|
+
}
|
|
686
|
+
|
|
659
687
|
if (outcome === 'passed') {
|
|
660
688
|
const payload = passPayload(closed, { authorId, completenessScore, passedAt: closed.closed_at });
|
|
661
689
|
try {
|
|
662
690
|
if (api.emitAsync) await api.emitAsync('board.item.passed', payload);
|
|
663
691
|
} catch (_) { /* emitAsync isolates listener errors; nothing that moves credits subscribes */ }
|
|
664
692
|
if (closed.subject_type === 'genesis_stage') await emitGenesisStagePassed(closed);
|
|
693
|
+
if (acted) await decisionBoard.announcePassedMatter(acted);
|
|
665
694
|
}
|
|
666
695
|
|
|
667
696
|
// R22's mirror event: BOTH outcomes, after commit, emitAsync. Separate from
|
|
@@ -678,6 +707,8 @@ async function closeItem(item, { cause = 'vote' } = {}) {
|
|
|
678
707
|
closed: true, item: closed, outcome, cause, author_karma: authorKarma,
|
|
679
708
|
...(credit ? { credit } : {}),
|
|
680
709
|
...(appliedConstitution ? { applied_constitution: appliedConstitution } : {}),
|
|
710
|
+
...(appliedDecisionRules ? { applied_decision_rules: appliedDecisionRules } : {}),
|
|
711
|
+
...(acted ? { acted: acted.result } : {}),
|
|
681
712
|
};
|
|
682
713
|
}
|
|
683
714
|
|
|
@@ -692,7 +723,7 @@ async function maybeCloseAfterVote(itemId) {
|
|
|
692
723
|
const members = await resolveBoardMembers(constitution);
|
|
693
724
|
// task 1003273: the author is an input to the trigger, not just the tally — an
|
|
694
725
|
// author's cast must never be the vote that ends the sitting.
|
|
695
|
-
const authorId = authorRuleApplies(item.subject_type)
|
|
726
|
+
const authorId = authorRuleApplies(item.subject_type, constitution)
|
|
696
727
|
? await db.getBoardItemAuthor(item)
|
|
697
728
|
: null;
|
|
698
729
|
if (!shouldCloseAfterVote({ constitution, votes, members, authorId })) {
|
|
@@ -866,6 +897,17 @@ async function proposeCharter({
|
|
|
866
897
|
return out.ok ? { ...out, reads_as: readsAs } : out;
|
|
867
898
|
}
|
|
868
899
|
|
|
900
|
+
// ── who decides what (task 1004529, ADR 0363) ───────────────────────────────
|
|
901
|
+
// The board side lives in decision-board.js; these wrappers add the window-open
|
|
902
|
+
// nudge every open emits.
|
|
903
|
+
const withOpenNudge = (fn) => async (args) => {
|
|
904
|
+
const out = await fn(args);
|
|
905
|
+
if (out.ok && out.item) await emitWindowOpened(out.item);
|
|
906
|
+
return out;
|
|
907
|
+
};
|
|
908
|
+
const proposeDecisionRules = withOpenNudge((a) => decisionBoard.proposeDecisionRules(a));
|
|
909
|
+
const openMatter = withOpenNudge((a) => decisionBoard.openMatter(a));
|
|
910
|
+
|
|
869
911
|
// ── the board's third subject: closing a genesis stage (task 1004429) ───────
|
|
870
912
|
//
|
|
871
913
|
// Spec decision D7 (docs/specs/bongos-v2-project-startup.md): a new project is
|
|
@@ -1071,9 +1113,10 @@ async function boardRoomView({ closedLimit = 20 } = {}) {
|
|
|
1071
1113
|
const votes = await db.getVotesForItems(all.map((i) => i.id));
|
|
1072
1114
|
const ideaIds = [...new Set(all.filter((i) => i.subject_type === 'full_idea').map((i) => i.subject_id))];
|
|
1073
1115
|
const amendmentIds = [...new Set(all.filter((i) => i.subject_type === 'constitutional_amendment').map((i) => i.subject_id))];
|
|
1074
|
-
const [ideas, amendments] = await Promise.all([
|
|
1116
|
+
const [ideas, amendments, matterById] = await Promise.all([
|
|
1075
1117
|
db.getIdeaSubjects(ideaIds),
|
|
1076
1118
|
db.getAmendmentSubjects(amendmentIds),
|
|
1119
|
+
decisionBoard.matterSubjectsById(all), // task 1004529: no query when no matter is on the board
|
|
1077
1120
|
]);
|
|
1078
1121
|
const ideaById = new Map(ideas.map((r) => [String(r.id), r]));
|
|
1079
1122
|
const amendmentById = new Map(amendments.map((r) => [String(r.id), r]));
|
|
@@ -1118,7 +1161,9 @@ async function boardRoomView({ closedLimit = 20 } = {}) {
|
|
|
1118
1161
|
recommendation: r.recommendation ?? null,
|
|
1119
1162
|
} : null;
|
|
1120
1163
|
})()
|
|
1121
|
-
:
|
|
1164
|
+
: decisionRules.isMatterSubject(item.subject_type)
|
|
1165
|
+
? decisionBoard.projectMatterSubject(matterById.get(String(item.subject_id)))
|
|
1166
|
+
: (() => { const r = amendmentById.get(String(item.subject_id)); return r ? { proposed: r.proposed, rationale_md: r.rationale_md, ...(r.kind ? { kind: r.kind } : {}), author_id: r.proposed_by == null ? null : String(r.proposed_by) } : null; })();
|
|
1122
1167
|
return {
|
|
1123
1168
|
id: String(item.id),
|
|
1124
1169
|
subject_type: item.subject_type,
|
|
@@ -1226,7 +1271,9 @@ async function constitutionView({ historyLimit = 50, type = null, headcount: ask
|
|
|
1226
1271
|
closed_at: r.closed_at,
|
|
1227
1272
|
outcome: r.outcome,
|
|
1228
1273
|
decided_under: r.decided_under || null,
|
|
1274
|
+
kind: r.kind || 'constitution',
|
|
1229
1275
|
}));
|
|
1276
|
+
const rulesTable = await decisionBoard.decisionRulesFor(live); // task 1004529; null if unreadable
|
|
1230
1277
|
return {
|
|
1231
1278
|
board: live,
|
|
1232
1279
|
history,
|
|
@@ -1244,7 +1291,11 @@ async function constitutionView({ historyLimit = 50, type = null, headcount: ask
|
|
|
1244
1291
|
// `historyLimit` in principle; a board with 50 amendments newer than its
|
|
1245
1292
|
// last passed one is not a shape this system has, and the honest failure
|
|
1246
1293
|
// there is a missed warning rather than a wrong one.
|
|
1247
|
-
|
|
1294
|
+
// Only board-block amendments say what the board block should be (a rules
|
|
1295
|
+
// amendment changes the table, task 1004529).
|
|
1296
|
+
divergence: constitutionDivergence(live, history.filter((h) => h.kind !== 'decision_rules')),
|
|
1297
|
+
// Who decides what at each building, as today or set by a passed amendment.
|
|
1298
|
+
decision_rules: decisionBoard.constitutionBlock(rulesTable),
|
|
1248
1299
|
// The genesis charter + legislation stages' payload (task 1004427): every
|
|
1249
1300
|
// form and variable by its real name with its plain explanation, and the
|
|
1250
1301
|
// form the constitution in force reads as (or Custom). Pure — no query.
|
|
@@ -1400,6 +1451,14 @@ async function pendingVotesFor(builderId) {
|
|
|
1400
1451
|
}
|
|
1401
1452
|
|
|
1402
1453
|
module.exports = {
|
|
1454
|
+
proposeDecisionRules,
|
|
1455
|
+
openMatter,
|
|
1456
|
+
decisionRulesView: (...a) => decisionBoard.decisionRulesView(...a),
|
|
1457
|
+
decisionRulesWriteAllowed: decisionBoard.decisionRulesWriteAllowed,
|
|
1458
|
+
applyPassedDecisionRules: decisionBoard.applyPassedDecisionRules,
|
|
1459
|
+
validateMatterAction: decisionBoard.validateMatterAction,
|
|
1460
|
+
checkActAlone: (...a) => decisionBoard.checkActAlone(...a),
|
|
1461
|
+
authorRuleApplies,
|
|
1403
1462
|
GENESIS_STAGES,
|
|
1404
1463
|
PASS_RULE_WORDS,
|
|
1405
1464
|
proposeCharter,
|